我目前正在C#中开展一个项目,在那里我玩行星万有引力,我知道这是一个很难掌握它的主题,但我喜欢挑战。我一直在阅读牛顿定律和Keplers定律,但有一点我无法弄清楚如何得到正确的引力方向。
在我的例子中,我只有2个身体。卫星和行星。这是为了简化它,所以我可以把握它 - 但我的计划是让多个物体相互动态相互作用,并希望最终得到一个有点逼真的多体系统。
当你有一个轨道时,那么卫星就有一个引力,那就是在行星的方向,但那个方向并不是一个常数。为了更好地解释我的问题,我将尝试使用一个例子:
假设我们有一颗卫星以50米/秒的速度移动,并以10米/秒/秒的速度朝着地球加速,半径为100米。 (所有理论数字)如果我们然后说帧率为1,那么在一秒之后,对象将向前50个单位,向下10个单位。
当卫星在一个框架内移动多个单位并且半径约为50%时,重力方向在此框架内已经移动了很多,但施加的力只是“向下”。这会产生很大的误差,特别是如果物体移动半径的很大一部分。
在我们的例子中,我们可能需要我们的重力方向基于我们当前位置与该帧结束时的位置之间的平均值。
如何计算这个呢?
我对三角学有基础知识,但主要关注三角形。假设我很愚蠢,因为与你们中的任何人相比,我可能都是。
(我提出了一个问题,但最后删除了它,因为它产生了一些敌意,基本上没有那么好的措辞,并且一般都是一般的 - 这不是一个特定的问题。我希望这更好。如果不是,请告知我,我在这里学习:))
仅供参考,这是我现在正在运动的功能:
foreach (ExtTerBody OtherObject in UniverseController.CurrentUniverse.ExterTerBodies.Where(x => x != this))
{
double massOther = OtherObject.Mass;
double R = Vector2Math.Distance(Position, OtherObject.Position);
double V = (massOther) / Math.Pow(R,2) * UniverseController.DeltaTime;
Vector2 NonNormTwo = (OtherObject.Position - Position).Normalized() * V;
Vector2 NonNormDir = Velocity + NonNormTwo;
Velocity = NonNormDir;
Position += Velocity * Time.DeltaTime;
}
如果我对自己表达不好,请让我重新措辞部分 - 英语不是我的母语,当你不知道正确的技术术语时,特定的主题可能很难说。 :)
我有一种预感,这是keplers第二定律所涵盖的,但如果是,那么我不确定如何使用它,因为我不能充分理解他的定律。
感谢您的时间 - 这意味着很多!
(如果有人看到我的功能出现多重错误,那么请指出它们!)
答案 0 :(得分:14)
我目前正在C#中开展一个项目,在那里我玩行星万有引力
这是一种同时学习模拟技术,编程和物理的有趣方式。
我无法弄清楚的一件事是如何获得正确的引力方向。
我认为你不是在试图模拟相对论的万有引力。地球不在太阳轨道上运行,地球在太阳在八分钟前的轨道上运行。纠正引力不是瞬时的事实可能是困难的。 (更新:根据评论,这是不正确的。我知道什么;我在第二年牛顿动力学后停止了物理学,并且对张量微积分只有最模糊的理解。)
你在这个早期阶段做得最好,假设引力是瞬时的,行星是指其质量都在中心的点。引力矢量是从一点到另一点的直线。
让我们说卫星以50米/秒的速度移动......如果我们说帧速率是每秒一帧,那么一秒后物体将是50个单位,10单位下来。
让我们说清楚一点。力等于质量乘以加速度。你可以计算出身体之间的力量。你知道他们的群众,所以你现在知道每个身体的加速度。每个身体都有一个位置和一个速度。加速度会改变速度。速度改变了位置。因此,如果粒子开始向左移动速度为50米/秒,向下移动速度为0米/秒,然后你施加一个使其加速10米/秒/秒的力,那么我们可以计算出更改为速度,然后改变位置。正如您所注意到的那样,在那一秒结束时,位置和速度将与现有幅度相比发生巨大变化。
当卫星移动一个框架中的多个单位并且大约半径的50%时,重力方向在此框架内已经移动了很多,但施加的力仅向下移动了#34;这会产生很大的误差,特别是如果物体移动半径的很大一部分。
正确。问题是,帧率极大太低,无法正确建模您所描述的交互。如果对象快速改变方向,您需要运行模拟,以便查看十分之一,百分之一秒或几秒钟。时间步长通常称为" delta t"模拟,你的太大了。
对于行星体,你现在所做的就像试图通过每隔几个月模拟它的位置来模拟地球,并假设它同时在一条直线上移动。你需要每隔几分钟,而不是每隔几个月实际模拟一下它的位置。
在我们的例子中,我们可能需要根据当前位置与此帧结束位置之间的平均值来确定我们的重力方向。
你可以做到这一点,但简单地减少" delta t"用于计算。然后,框架开始和结束方向之间的差异要小得多。
一旦你解决了这个问题,你就可以使用更多技巧。例如,当帧之间的位置变化太大时,您可以检测,然后返回并以较小的时间步重做计算。如果位置几乎没有变化,那么增加时间步长。
一旦你 排序了,你就可以在物理模拟中使用许多更先进的技术,但我首先要让基本的时间步进真正稳固。更高级的技术本质上是对你的想法的变化"在时间步长上做更聪明的插值" - 你在这里走在正确的轨道上,但你应该在跑步前走路。
答案 1 :(得分:3)
我开始使用的技术几乎与您使用的Euler-Cromer集成一样简单,但显着更准确。这是蛙跳技术。这个想法很简单:位置和速度保持在彼此的半个步骤。
初始状态在时间t 0 时具有位置和速度。要获得半步偏移量,您需要在第一步中使用特殊情况,其中使用间隔开始时的加速度将速度提前半步,然后将位置提前一整步。在第一次特殊情况之后,代码就像你的Euler-Cromer集成商一样。
在伪代码中,算法看起来像
void calculate_accel (orbiting_body_collection, central_body) {
foreach (orbiting_body : orbiting_body_collection) {
delta_pos = central_body.pos - orbiting_body.pos;
orbiting_body.acc =
(central_body.mu / pow(delta_pos.magnitude(),3)) * delta_pos;
}
}
void leapfrog_step (orbiting_body_collection, central_body, delta_t) {
static bool initialized = false;
calculate_accel (orbiting_body_collection, central_body);
if (! initialized) {
initialized = true;
foreach orbiting_body {
orbiting_body.vel += orbiting_body.acc*delta_t/2.0;
orbiting_body.pos += orbiting_body.vel*delta_t;
}
}
else {
foreach orbiting_body {
orbiting_body.vel += orbiting_body.acc*delta_t;
orbiting_body.pos += orbiting_body.vel*delta_t;
}
}
}
请注意,我已将加速度添加为每个轨道体的场。这是保持算法与您的算法类似的临时步骤。另请注意,我将加速度计算移到了它自己的独立函数中。这不是暂时的一步。这是推进更先进的集成技术的第一步。
下一个重要步骤是撤消临时添加加速度。加速度恰当地属于积分器,而不是身体。另一方面,加速度的计算属于问题空间,而不是积分器。您可能希望在行星引力相互作用中添加相对论修正或太阳辐射压力或行星。积分器应该不知道计算加速度的内容。函数calculate_accels
是由积分器调用的黑盒子。
不同的积分器在需要计算加速度时有非常不同的概念。有些存储了近期加速度的历史记录,有些需要额外的工作空间来计算某种平均加速度。有些人对速度做同样的事情(保持历史,有一些速度工作空间)。一些更高级的集成技术在内部使用许多技术,从一个技术切换到另一个技术,以在精度和CPU使用之间提供最佳平衡。如果要模拟太阳系,则需要一个非常精确的积分器。 (而且你需要移动很远,远离浮子。即使是双打也不足以进行高精度的太阳能系统集成。对于浮子,没有太多的点超过RK4,甚至可能没有超越。)
正确地分离属于谁(积分器与问题空间)的内容可以优化问题域(添加相关性等),并且可以轻松切换集成技术,以便您可以评估一种技术与另一种技术。
答案 2 :(得分:0)
所以我找到了一个解决方案,它可能不是最聪明的,但它确实有效,在阅读了Eric的答案并阅读了马库斯的评论之后,我想起了它,你可以说它是两者的组合:
这是新代码:
foreach (ExtTerBody OtherObject in UniverseController.CurrentUniverse.ExterTerBodies.Where(x => x != this))
{
double massOther = OtherObject.Mass;
double R = Vector2Math.Distance(Position, OtherObject.Position);
double V = (massOther) / Math.Pow(R,2) * Time.DeltaTime;
float VRmod = (float)Math.Round(V/(R*0.001), 0, MidpointRounding.AwayFromZero);
if(V > R*0.01f)
{
for (int x = 0; x < VRmod; x++)
{
EulerMovement(OtherObject, Time.DeltaTime / VRmod);
}
}
else
EulerMovement(OtherObject, Time.DeltaTime);
}
public void EulerMovement(ExtTerBody OtherObject, float deltaTime)
{
double massOther = OtherObject.Mass;
double R = Vector2Math.Distance(Position, OtherObject.Position);
double V = (massOther) / Math.Pow(R, 2) * deltaTime;
Vector2 NonNormTwo = (OtherObject.Position - Position).Normalized() * V;
Vector2 NonNormDir = Velocity + NonNormTwo;
Velocity = NonNormDir;
//Debug.WriteLine("Velocity=" + Velocity);
Position += Velocity * deltaTime;
}
解释一下:
我得出的结论是,如果问题是卫星在一帧中有太大的速度,那么为什么不把它分成多帧呢?所以这就是“它”现在的作用。
当卫星的速度超过当前半径的1%时,它将计算分成多个咬合,使其更加精确。当高速运行时,这将降低帧速率,但是它可以用于像这样的项目。
不同的解决方案仍然非常受欢迎。我可能会调整触发器数量,但最重要的是它可以工作,然后我可以担心它会更顺畅!
感谢所有看过的人,帮助过的每个人都能自己找到结论! :)人们可以帮助这样做真棒!