這依然是與《三體》有關的一篇文章。空間中三個星體在萬有引力作用下的運動被稱之為三體問題,參見我的上一篇文章:三體運動的程序模擬。而這一節,對三體問題進行了擴展,實現了空間中N個星體在萬有引力下的運動模擬。
程序中使用了兩個物理定律:
(1)萬有引力定律
這是牛頓發現的:任意兩個質點有通過連心線方向上的力相互吸引。該引力的大小與它們的質量乘積成正比,與它們距離的平方成反比,與兩物體的化學本質或物理狀態以及中介物質無關。
以數學表示為:
其中:
: 兩個物體之間的引力
: 萬有引力常數
: 物體1的質量
: 物體2的質量
: 兩個物體之間的距離
依照國際單位制,F的單位為牛頓(N),m1和m2的單位為千克(kg),r 的單位為米(m),常數G近似地等於6.67 × 10−11 N m2 kg−2(牛頓米的平方每千克的平方)。當然程序中G不可能設置為這么小的一個數,用戶可以自己設置萬有引力常數,以實現合理的運動.
(2)勢能與動能守恆定律
-
引力位能或引力勢能是指物體因為大質量物體的萬有引力而具有的位能,其大小與其到大質量的距離有關。
其中G為萬有引力常數,M、m分別為兩物體質量,r為兩者距離。
動能,簡單的說就是指物體因運動而具有的能量。數值上等於(1/2)Mv^2. 動能是能量的一種,它的國際單位制下單位是焦耳(J),簡稱焦。
勢能與動能守恆即在整個運動過程中,其總能量是不變的,即N體的動能與勢能之和為固定值.
本來我只打算使用萬有引力定律,沒有考慮勢能動能守恆定律.但程序運行后發現,由於沒有使用微積分,即使采樣間隔時間再小,誤差也很大.這讓我想起當年學大學物理的時候,大一上學期開高數,然后下學期開了物理.現在對大學物理講的東西,只記得是用高數中的微積分解物理問題.可那時,我高數學得就是個渣,所以物理也沒辦法學好.當然就算物理學好了,現在也早忘光了.沒有用微積分,這個程序中算法誤差不小,模擬一下吧,不要太計較.
核心代碼也發一下,希望有興趣的人可以對其做出優化:
1 /**************************************************************** 2 3 File name : NBodyKernel.h 4 Author : 葉峰 5 Version : 1.0a 6 Create Date : 2014/09/19 7 Description : 8 9 *****************************************************************/ 10 11 // -------------------------------------------------------------------------------------- 12 13 #ifndef _NBodyKernel_H_ 14 #define _NBodyKernel_H_ 15 16 // INCLUDES ----------------------------------------------------------------------------- 17 18 #include "..\..\..\Why\YCommon_h\WhyMath.h" 19 #include "..\..\..\Why\YInterface\Others\YdD3D9Math.h" 20 21 // -------------------------------------------------------------------------------------- 22 23 #define YD_ORBIT_VERTICES_COUNT 1024 24 25 // -------------------------------------------------------------------------------------- 26 27 class Star 28 { 29 public: 30 Vector3 vPosition; // 位置 31 Vector3 vVelocity; // 速度 32 Vector3 vForce; // 受力 33 Yreal fWeight; // 質量 34 Yreal fRadiusScale; // 縮放 35 Yreal fKineticEnergy; // 動能 36 Ycolor color; // 顏色 37 Ybool bOrbitVisible; // 軌跡可見 38 Yuint id; 39 Vector3 verticesOrbit[YD_ORBIT_VERTICES_COUNT]; 40 Star* pNext; 41 42 Star() 43 { 44 fWeight = 0.0f; 45 fRadiusScale = 1.0f; 46 fKineticEnergy = 0.0f; 47 color = 0xffffffff; 48 bOrbitVisible = YD_FALSE; 49 id = -1; 50 memset(verticesOrbit, 0, sizeof(verticesOrbit)); 51 pNext = NULL; 52 } 53 54 // 根據速度計算動能 55 void CalculateKineticEnergyByVelocity() 56 { 57 Yreal sqv = D3DXVec3LengthSq(&vVelocity); 58 fKineticEnergy = 0.5f*fWeight*sqv; 59 } 60 61 // 根據動能計算速度 62 void CalculateVelocityByKineticEnergy() 63 { 64 float v = sqrt(2*fKineticEnergy/fWeight); 65 float w = D3DXVec3Length(&vVelocity); 66 vVelocity *= v/w; 67 }; 68 }; 69 70 // -------------------------------------------------------------------------------------- 71 72 class CNBodyKernel 73 { 74 public: 75 CNBodyKernel(); 76 77 ~CNBodyKernel(); 78 79 // 更新N體 80 void UpdateNBody(Yreal deltaTime); 81 82 // 清空N體 83 void Clear(); 84 85 // 設置引力系數 86 void SetGravitationCoefficient(Yreal c); 87 88 // 獲得引力系數 89 Yreal GetGravitationCoefficient() const 90 { 91 return m_fGravitationCoefficient; 92 } 93 94 // 返回星體數目 95 Yuint GetStarsCount() const 96 { 97 return m_starsCount; 98 } 99 100 // 返回星體鏈表 101 const Star* GetStarsList() const 102 { 103 return m_starsList; 104 } 105 106 // 返回星體對象 107 const Star* GetStar(Yuint index) const; 108 109 // 移除星體 110 bool RemoveStar(Yuint index); 111 112 // 添加星體 113 void AddStar( 114 const Vector3& vPosition, 115 const Vector3& vVelocity, 116 const Yreal fWeight, 117 Ycolor color); 118 119 120 // 設置星體的重量 121 void SetStarWeight(Yuint index, Yreal weight); 122 123 // 設置星體的軌跡是否可見 124 void SetStarOrbitVisible(Yuint index, bool visible); 125 126 // 設置星體的顏色 127 void SetStarColor(Yuint index, Ycolor color); 128 129 Yuint GetCurrentOrbit() const 130 { 131 return m_currentOrbit; 132 } 133 134 const Vector3* GetStarPosition(Yuint index) const; 135 136 Yreal GetStarWeight(Yuint index) const; 137 138 bool GetStarOrbitVisible(Yuint index) const; 139 140 Ycolor GetStarColor(Yuint index) const; 141 142 Yuint GetStarID(Yuint index) const; 143 144 void GetSpaceCenter(Vector3& vCenter) const; 145 146 void GetWeightCenter(Vector3& vCenter) const; 147 148 private: 149 // 更新星體的位置,速度,動能 150 void UpdateStar(Star& star, Yreal t); 151 152 // 根據動能重新計算速度 153 void RecalculateStarVelocity(Star& star); 154 155 // 計算每個星體的當前受力 156 void CalculateStarsForce(); 157 158 // 計算總勢能 159 void CalculateAmountOfPotentialEnergy(); 160 161 // 計算總動能 162 void CalculateAmountOfKineticEnergy(); 163 164 // 計算總能量 165 void CalculateAmountEnergy() 166 { 167 CalculateAmountOfPotentialEnergy(); 168 CalculateAmountOfKineticEnergy(); 169 m_fAmountEnergy = m_fAmountOfPotentialEnergy + m_fAmountOfKineticEnergy; 170 } 171 172 private: 173 Yreal m_fGravitationCoefficient; // 引力系數 174 Yreal m_fAmountOfPotentialEnergy; // 當前總勢能 175 Yreal m_fAmountOfKineticEnergy; // 當前總動能 176 Yreal m_fAmountEnergy; // 當前能量m_fAmountOfPotentialEnergy + m_fAmountOfKineticEnergy; 177 178 Star* m_starsList; // N體鏈表 179 Yuint m_starsCount; // N體數目 180 181 Yuint m_currentOrbit; 182 }; 183 184 // -------------------------------------------------------------------------------------- 185 186 #endif

1 /**************************************************************** 2 3 File name : NBodyKernel.cpp 4 Author : 葉峰 5 Version : 1.0a 6 Create Date : 2014/09/19 7 Description : 8 9 *****************************************************************/ 10 11 // -------------------------------------------------------------------------------------- 12 13 #include "..\..\..\Why\YCommon_h\WhyMemoryDefines.h" 14 #include "NBodyKernel.h" 15 #include <assert.h> 16 17 // -------------------------------------------------------------------------------------- 18 19 #define YD_ESP 0.00001f 20 21 // -------------------------------------------------------------------------------------- 22 23 CNBodyKernel::CNBodyKernel() 24 { 25 m_fGravitationCoefficient = 100.0f; 26 m_fAmountOfPotentialEnergy = 0.0f; 27 m_fAmountOfKineticEnergy = 0.0f; 28 m_fAmountEnergy = 0.0f; 29 30 m_starsList = NULL; 31 m_starsCount = 0; 32 33 m_currentOrbit = 0; 34 } 35 36 CNBodyKernel::~CNBodyKernel() 37 { 38 Clear(); 39 } 40 41 // 清空N體 42 void CNBodyKernel::Clear() 43 { 44 Star* pDel = m_starsList; 45 Star* pNext; 46 while (pDel) 47 { 48 pNext = pDel->pNext; 49 YD_DELETE(pDel); 50 pDel = pNext; 51 } 52 53 m_starsList = NULL; 54 m_starsCount = 0; 55 } 56 57 // 獲得引力系數 58 void CNBodyKernel::SetGravitationCoefficient(Yreal c) 59 { 60 m_fGravitationCoefficient = c; 61 62 // 計算總能量 63 CalculateAmountEnergy(); 64 } 65 66 // 計算每個星體的當前受力 67 void CNBodyKernel::CalculateStarsForce() 68 { 69 // 清空所有引力 70 Star* pStar = m_starsList; 71 while (pStar) 72 { 73 pStar->vForce = Vector3(0.0f, 0.0f, 0.0f); 74 pStar = pStar->pNext; 75 } 76 77 // 計算引力 78 Star* pCurrent = m_starsList; 79 Star* pNext; 80 while (pCurrent) 81 { 82 pNext = pCurrent->pNext; 83 while (pNext) 84 { 85 Vector3 vAB = pNext->vPosition - pCurrent->vPosition; 86 float disSqAB = D3DXVec3LengthSq(&vAB); 87 if (disSqAB < YD_ESP) 88 { 89 disSqAB = YD_ESP; 90 } 91 float disAB = sqrt(disSqAB); 92 Vector3 vForceDir = vAB/disAB; 93 Yreal fForce = m_fGravitationCoefficient*pCurrent->fWeight*pNext->fWeight/disSqAB; 94 Vector3 vForce = vForceDir*fForce; 95 96 pCurrent->vForce += vForce; 97 pNext->vForce -= vForce; 98 99 pNext = pNext->pNext; 100 } 101 pCurrent = pCurrent->pNext; 102 } 103 } 104 105 void CNBodyKernel::UpdateStar(Star& star, Yreal t) 106 { 107 // 加速度 108 const Vector3 acc = star.vForce/star.fWeight; 109 110 star.vPosition.x = star.vPosition.x + star.vVelocity.x*t + 0.5f*acc.x*t*t; 111 star.vPosition.y = star.vPosition.y + star.vVelocity.y*t + 0.5f*acc.y*t*t; 112 star.vPosition.z = star.vPosition.z + star.vVelocity.z*t + 0.5f*acc.z*t*t; 113 114 star.vVelocity.x += acc.x*t; 115 star.vVelocity.y += acc.y*t; 116 star.vVelocity.z += acc.z*t; 117 118 // 重新計算動能 119 star.CalculateKineticEnergyByVelocity(); 120 } 121 122 void CNBodyKernel::UpdateNBody(Yreal deltaTime) 123 { 124 // 計算每個星體的當前受力 125 CalculateStarsForce(); 126 127 // 更新星體的位置與速度 128 Star* pStar = m_starsList; 129 while (pStar) 130 { 131 UpdateStar(*pStar, deltaTime); 132 pStar = pStar->pNext; 133 } 134 135 // 能量守恆 136 CalculateAmountOfKineticEnergy(); 137 CalculateAmountOfPotentialEnergy(); 138 139 // 理論上的動能 140 Yreal fKineticEnergy = m_fAmountEnergy - m_fAmountOfPotentialEnergy; 141 if (fKineticEnergy < 0.0f) 142 { 143 fKineticEnergy = 0.0f; 144 } 145 146 Yreal r = fKineticEnergy/m_fAmountOfKineticEnergy; 147 pStar = m_starsList; 148 while (pStar) 149 { 150 if (r > YD_ESP) 151 { 152 pStar->fKineticEnergy *= r; 153 } 154 else 155 { 156 pStar->fKineticEnergy = 1.0f; 157 } 158 // 根據動能計算速度 159 pStar->CalculateVelocityByKineticEnergy(); 160 pStar = pStar->pNext; 161 } 162 163 // 更新軌跡點 164 pStar = m_starsList; 165 while (pStar) 166 { 167 pStar->verticesOrbit[m_currentOrbit] = pStar->vPosition; 168 pStar = pStar->pNext; 169 } 170 m_currentOrbit++; 171 if (m_currentOrbit >= YD_ORBIT_VERTICES_COUNT) 172 { 173 m_currentOrbit = 0; 174 } 175 } 176 177 // 返回星體對象 178 const Star* CNBodyKernel::GetStar(Yuint index) const 179 { 180 if (index >= m_starsCount) 181 { 182 return NULL; 183 } 184 185 const Star* pStar = m_starsList; 186 while (index && pStar) 187 { 188 index--; 189 pStar = pStar->pNext; 190 } 191 192 return pStar; 193 } 194 195 // 移除星體 196 bool CNBodyKernel::RemoveStar(Yuint index) 197 { 198 if (index >= m_starsCount) 199 { 200 return false; 201 } 202 203 if (index == 0) 204 { 205 Star* pStar = m_starsList->pNext; 206 YD_DELETE(m_starsList); 207 m_starsList = pStar; 208 } 209 else 210 { 211 Star* pStar = m_starsList; 212 Yuint t = index - 1; 213 while (t && pStar) 214 { 215 t--; 216 pStar = pStar->pNext; 217 } 218 assert(pStar); 219 220 Star* pDel = pStar->pNext; 221 pStar->pNext = pDel->pNext; 222 YD_DELETE(pDel); 223 } 224 225 m_starsCount--; 226 227 Yuint id = 0; 228 Star* pStar = m_starsList; 229 while (pStar) 230 { 231 pStar->id = id; 232 id++; 233 pStar = pStar->pNext; 234 } 235 236 // 重算能量 237 CalculateAmountEnergy(); 238 239 return true; 240 } 241 242 // 添加星體 243 void CNBodyKernel::AddStar( 244 const Vector3& vPosition, 245 const Vector3& vVelocity, 246 const Yreal fWeight, 247 Ycolor color) 248 { 249 Star* pNewStar = YD_NEW(Star); 250 pNewStar->vPosition = vPosition; 251 pNewStar->vVelocity = vVelocity; 252 pNewStar->fWeight = fWeight; 253 pNewStar->fRadiusScale = yf_pow(fWeight, 1.0f/3.0f); 254 pNewStar->CalculateKineticEnergyByVelocity(); 255 pNewStar->color = color; 256 pNewStar->id = m_starsCount; 257 for (Yuint i = 0; i < YD_ORBIT_VERTICES_COUNT; i++) 258 { 259 pNewStar->verticesOrbit[i] = vPosition; 260 } 261 262 if (!m_starsList) 263 { 264 m_starsList = pNewStar; 265 } 266 else 267 { 268 Star* pStar = m_starsList; 269 while (pStar->pNext) 270 { 271 pStar = pStar->pNext; 272 } 273 pStar->pNext = pNewStar; 274 } 275 276 m_starsCount++; 277 278 // 重算能量 279 CalculateAmountEnergy(); 280 } 281 282 // 設置星體的重量 283 void CNBodyKernel::SetStarWeight(Yuint index, Yreal weight) 284 { 285 if (index >= m_starsCount) 286 { 287 return; 288 } 289 290 Star* pStar = m_starsList; 291 while (index && pStar) 292 { 293 index--; 294 pStar = pStar->pNext; 295 } 296 297 pStar->fWeight = weight; 298 pStar->fRadiusScale = yf_pow(weight, 1.0f/3.0f); 299 pStar->CalculateKineticEnergyByVelocity(); 300 301 // 重算能量 302 CalculateAmountEnergy(); 303 } 304 305 // 設置星體的軌跡是否可見 306 void CNBodyKernel::SetStarOrbitVisible(Yuint index, bool visible) 307 { 308 if (index >= m_starsCount) 309 { 310 return; 311 } 312 313 Star* pStar = m_starsList; 314 while (index && pStar) 315 { 316 index--; 317 pStar = pStar->pNext; 318 } 319 320 pStar->bOrbitVisible = visible; 321 } 322 323 // 設置星體的顏色 324 void CNBodyKernel::SetStarColor(Yuint index, Ycolor color) 325 { 326 if (index >= m_starsCount) 327 { 328 return; 329 } 330 331 Star* pStar = m_starsList; 332 while (index && pStar) 333 { 334 index--; 335 pStar = pStar->pNext; 336 } 337 338 pStar->color = color; 339 } 340 341 // 計算總動能 342 void CNBodyKernel::CalculateAmountOfKineticEnergy() 343 { 344 // 動能 345 m_fAmountOfKineticEnergy = 0.0f; 346 Star* pStar = m_starsList; 347 while (pStar) 348 { 349 //pStar->CalculateKineticEnergyByVelocity(); 350 m_fAmountOfKineticEnergy += pStar->fKineticEnergy; 351 pStar = pStar->pNext; 352 } 353 } 354 355 // 計算總勢能 356 void CNBodyKernel::CalculateAmountOfPotentialEnergy() 357 { 358 m_fAmountOfPotentialEnergy = 0.0f; 359 360 Star* pCurrent = m_starsList; 361 Star* pNext; 362 while (pCurrent) 363 { 364 pNext = pCurrent->pNext; 365 while (pNext) 366 { 367 Vector3 vAB = pCurrent->vPosition - pNext->vPosition; 368 float disAB = D3DXVec3Length(&vAB); 369 float fPotentialEnergyAB = -m_fGravitationCoefficient*pCurrent->fWeight*pNext->fWeight/disAB; 370 m_fAmountOfPotentialEnergy += fPotentialEnergyAB; 371 pNext = pNext->pNext; 372 } 373 pCurrent = pCurrent->pNext; 374 } 375 } 376 377 const Vector3* CNBodyKernel::GetStarPosition(Yuint index) const 378 { 379 if (index >= m_starsCount) 380 { 381 return 0; 382 } 383 384 const Star* pStar = m_starsList; 385 while (index && pStar) 386 { 387 index--; 388 pStar = pStar->pNext; 389 } 390 391 assert(pStar); 392 return &pStar->vPosition; 393 } 394 395 Yreal CNBodyKernel::GetStarWeight(Yuint index) const 396 { 397 if (index >= m_starsCount) 398 { 399 return 0.0f; 400 } 401 402 const Star* pStar = m_starsList; 403 while (index && pStar) 404 { 405 index--; 406 pStar = pStar->pNext; 407 } 408 409 assert(pStar); 410 return pStar->fWeight; 411 } 412 413 bool CNBodyKernel::GetStarOrbitVisible(Yuint index) const 414 { 415 if (index >= m_starsCount) 416 { 417 return false; 418 } 419 420 const Star* pStar = m_starsList; 421 while (index && pStar) 422 { 423 index--; 424 pStar = pStar->pNext; 425 } 426 427 assert(pStar); 428 return pStar->bOrbitVisible != YD_FALSE; 429 } 430 431 Ycolor CNBodyKernel::GetStarColor(Yuint index) const 432 { 433 if (index >= m_starsCount) 434 { 435 return 0; 436 } 437 438 const Star* pStar = m_starsList; 439 while (index && pStar) 440 { 441 index--; 442 pStar = pStar->pNext; 443 } 444 445 assert(pStar); 446 return pStar->color; 447 } 448 449 Yuint CNBodyKernel::GetStarID(Yuint index) const 450 { 451 if (index >= m_starsCount) 452 { 453 return 0; 454 } 455 456 const Star* pStar = m_starsList; 457 while (index && pStar) 458 { 459 index--; 460 pStar = pStar->pNext; 461 } 462 463 assert(pStar); 464 return pStar->id; 465 } 466 467 void CNBodyKernel::GetSpaceCenter(Vector3& vCenter) const 468 { 469 vCenter = Vector3(0.0f, 0.0f, 0.0f); 470 if (!m_starsCount) 471 { 472 return; 473 } 474 475 Star* pStar = m_starsList; 476 while (pStar) 477 { 478 vCenter += pStar->vPosition; 479 pStar = pStar->pNext; 480 } 481 482 vCenter /= (Yreal)m_starsCount; 483 } 484 485 void CNBodyKernel::GetWeightCenter(Vector3& vCenter) const 486 { 487 vCenter = Vector3(0.0f, 0.0f, 0.0f); 488 if (!m_starsCount) 489 { 490 return; 491 } 492 493 Yreal weights = 0.0f; 494 Star* pStar = m_starsList; 495 while (pStar) 496 { 497 vCenter += pStar->vPosition*pStar->fWeight; 498 weights += pStar->fWeight; 499 pStar = pStar->pNext; 500 } 501 502 vCenter /= weights; 503 }
程序啟動后,會出現4個球體在運動。不知道大家有沒有注意到:Win7的開機動畫就是四個球體的運動,所以我這程序的默認也是生成四個球體。
通過UI控件,可以添加刪除星體。沒有上限,當然星體越多程序越慢。
可以顯示每一個星體的運動軌跡
用戶可以實時修改任意一個星體的質量
鼠標右鍵用於控制視角
鍵盤U用於開關UI用戶界面.
通過UI用戶界面可以設置三個球體的質量,設置萬有引力系數,設置天體運行速度,設置球體的顯示大小.
鍵盤0~9用於開關第N個球體運動軌跡的顯示
鍵盤G,用於開關三維網格的顯示
鍵盤C,用於開關坐標軸的顯示
鍵盤P,用於暫停
鍵盤R,用於重置.
F11 全屏切換