【问题标题】:Vector direction for gravity in a circular orbit圆形轨道中重力的矢量方向
【发布时间】:2014-09-07 17:44:14
【问题描述】:

我目前正在使用 C# 进行一个项目,我在其中玩弄行星引力,我知道这是一个要充分利用它的核心主题,但我喜欢挑战。我一直在阅读牛顿定律和开普勒定律,但我无法弄清楚的一件事是如何获得正确的引力方向。

在我的示例中,我只有 2 个身体。一颗卫星和一颗行星。这是为了简化它,所以我可以掌握它 - 但我的计划是让多个对象动态地相互影响,并希望最终得到一个有点现实的多体系统。

当你有一个轨道时,卫星就有一个引力,这当然是在行星的方向上,但那个方向不是一个常数。为了更好地解释我的问题,我将尝试使用一个示例:

假设我们有一颗卫星以 50 m/s 的速度移动,并以 10 m/s/s 的速度向地球加速,半径为 100 m。 (所有理论数字)如果我们说帧速率为 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;
}

如果我的措辞很糟糕,请让我重新措辞 - 英语不是我的母语,如果您不知道正确的技术术语,特定主题可能很难措辞。 :)

我有一种预感,这在开普勒第二定律中有所涵盖,但如果是这样,那么我不确定如何使用它,因为我没有完全理解他的定律。

感谢您的宝贵时间 - 这意味着很多!

(如果有人在我的函数中发现多个错误,请指出!)

【问题讨论】:

标签: c# vector physics trigonometry


【解决方案1】:

我目前正在使用 C# 进行一个项目,我在其中玩弄行星引力

这是一种同时学习模拟技术、编程和物理的有趣方式。

我无法弄清楚的一件事是如何获得正确的重力方向。

我假设您不是在尝试模拟相对论引力。地球不在围绕太阳的轨道上,地球在围绕八分钟前太阳所在的轨道。纠正引力不是瞬时的这一事实可能很困难。 (更新:根据评论,这是不正确的。我知道什么;第二年牛顿动力学后我停止学习物理,对张量微积分只有最模糊的了解。)

在这个早期阶段,您最好假设引力是瞬时的,并且行星是所有质量都在中心的点。引力矢量是从一点到另一点的直线。

假设我们有一颗以 50 m/s 的速度移动的卫星......如果我们说帧速率是每秒一帧,那么一秒后对象将向右移动 50 个单位,向下移动 10 个单位。

让我们更清楚地说明这一点。力等于质量乘以加速度。你计算出身体之间的力。你知道它们的质量,所以你现在知道每个物体的加速度。每个物体都有一个位置和一个速度。加速度改变速度。速度改变位置。因此,如果粒子开始时向左的速度为 50 m/s,向下的速度为 0 m/s,然后施加一个力使其向下加速 10 m/s/s,那么我们可以计算出变化为速度,然后改变位置。正如您所注意到的,在那一秒结束时,位置和速度与它们现有的大小相比都会发生巨大的变化。

当卫星在一帧内移动多个单元并移动大约 50% 的半径时,在这一帧中,重力方向发生了很大变化,但施加的力只是“向下”。这会产生很大的误差,尤其是当对象移动半径的很大百分比时。

正确。问题在于帧速率非常太低,无法正确模拟您所描述的交互。您需要运行模拟,以便在对象快速改变方向时查看十分之一、百分之一或千分之几秒。时间步长的大小通常称为模拟的“delta t”,而你的太大了。

对于行星体,您现在所做的就像试图通过每隔几个月模拟地球的位置来模拟地球,同时假设它沿直线运动。您需要每隔几分钟而不是每隔几个月实际模拟一次它的位置。

在我们的示例中,我们可能需要我们的重力方向基于我们当前位置和这一帧结束时的位置之间的平均值。

您可以这样做,但简单地减少计算的“delta t”会更容易。那么帧开始和结束的方向之间的差异要小得多。

一旦你解决了这个问题,你就可以使用更多的技术。例如,您可以检测帧之间的位置变化太大,然后返回并以更小的时间步重做计算。如果位置几乎没有变化,则增加时间步长。

一旦您对进行了排序,您可以在物理模拟中使用许多更高级的技术,但我首先要让基本的时间步进真正扎实。更高级的技术本质上是对您“对时间步长的变化进行更智能的插值”的想法的变体——您在这里走在正确的轨道上,但您应该在跑步之前先走。

【讨论】:

  • 感谢您的回答。正如您可能已经看到的那样,增量时间已经是我计算的一部分 - “UniverseController.DeltaTime;”,它是完成最后一帧所花费的时间,乘以 TimeScale。我知道这是一个问题。但我希望能够在不损失太多精度的情况下以更高的速度移动我的卫星/部分。不得不说,我不确定这是否是唯一的问题,也可能是计算和实现不好,但这似乎是错误的来源之一,我可以纠正。其他问题也可以解决
  • @CDK:计算似乎是正确的,假设模拟被缩放以使引力常数为 1。加速度是 m1 x m2 / r^2,所以质量为 m1 的粒子在 delta t 的时间跨度内的 delta v 是 dt * m1 x m2 / r^2 / m1,m1 的取消,你得到了一个正确的delta v 的计算。只要让 delta t 小得多,你的模拟就会变得更加准确。
  • @CDK 您可以使用 RK4 获得相当好的近似值,但 Euler 对于任何合理的时间步长都不可靠(只是不够准确)。如果您可以提出一阶 ODE 的状态向量并将其扔到 RK4 上,那么这个问题的解决方案非常优雅,但我不确定您的数学背景有多好。
  • Re 地球不在绕太阳运行的轨道上,地球在绕太阳运行八分钟前的轨道上运行。 这是非常错误的,首先由拉普拉斯发现1805 年。110 年后,爱因斯坦知道了这一点;他的广义相对论没有这样说。一个完整的广义相对论公式将使它成为 PDE 而不是 ODE 问题。一阶参数化的后牛顿近似保持在 ODE 的范围内,并且在太阳系中工作得非常好;请参见Orbital Ephemerides of the Sun, Moon, and Planets 第 3 页的公式 8-1。
  • 仅供参考,rk4 在很长一段时间内仍然不稳定,因为它不会为引力系统节省能量。您将需要一个symplectic integrator。一阶方法仍然需要一半的积分器是隐式的,但它会永远保持稳定,不像标准的 euler 或 rk4。
【解决方案2】:

我将从一个几乎与您一直在使用的 Euler-Cromer 积​​分一样简单但明显更准确的技术开始。这就是越级技术。这个想法很简单:位置和速度彼此保持半个时间步长。

初始状态在时间 t0 具有位置和速度。要获得半步偏移,第一步需要一个特殊情况,其中速度使用间隔开始时的加速度提前半个时间步,然后位置提前一整步。在第一次特殊情况之后,代码就像您的 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 没有多大意义,甚至可能没有跨越。)

正确区分属于谁的内容(集成者与问题空间)可以优化问题域(添加相关性等),并可以轻松切换集成技术,以便您可以评估一种技术与另一种技术。

【讨论】:

  • 这可能正是我需要的,我会写下来。但目前它对我来说太先进了,我无法理解。顺便说一句,我在计算实际运动时已经只使用双打了,但是用什么更好呢?我会尝试阅读你的东西几次,但现在,我提供的答案还可以,它消除了大时间步长的任何可能性,因此有点准确。但我会朝着你的想法努力,看看它是否能奏效。我的计划是添加火箭和更多功能,直到这个项目让我厌烦为止。谢谢你的时间! :)
  • 从你目前使用的 Euler-Cromer 技术转换为越级是相当微不足道的,而提高准确性的回报是相当巨大的。您只需进行少量更改即可在当前架构中进行此切换。您在此处显示的代码使用浮点数而不是双精度数。在对轨道力学进行建模时,会相当快地漂浮在陨石坑中。从浮点数转换为双精度数会产生巨大的回报,而不会对性能造成太大影响。 (续)
  • 关于更高的精度:许多机器本身并不支持它。 C/C++ 中的long double 可能只有 80 位宽(例如,Intel 盒子)。对太阳系进行数千年或更长时间的建模需要至少四倍精度(128 位浮点),以避免即使是最好的积分器也完全丧失精度。这意味着在本机不支持四精度的计算机上进行软件仿真,这反过来又意味着极大的性能损失。在您的应用程序中没有理由去那里。双精度对你来说应该很好用。
  • 真的不知道如何实现上述内容。我可以在某个地方与您联系,对这个概念有任何疑问吗?无论如何,如果不是:我上面显示的代码使用双打..“double V = (massOther) / Math.Pow(R,2) * Time.DeltaTime;”等等...为什么第一帧使用半加速? “orbiting_body.vel += orbiting_body.acc*delta_t/2.0;”我得到的和你所呈现的主要区别是什么?我看不出有什么大的不同……抱歉。我会尝试在需要的地方添加小数..还是应该使用其他数据类型?到目前为止谢谢..
【解决方案3】:

所以我找到了一个解决方案,它可能不是最聪明的,但它有效,并且在阅读了 Eric 的答案和阅读了 marcus 的评论之后,我想到了它,你可以说它是两者的结合:

这是新代码:

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% 时,它会将计算分成多个部分,使其更加精确。这当然会在高速工作时降低帧率,但对于像这样的项目。

仍然非常欢迎不同的解决方案。我可能会调整触发量,但最重要的是它可以工作,然后我可以担心让它更流畅!

感谢所有看过的人,以及所有帮助自己找到结论的人! :) 人们能提供这样的帮助真是太棒了!

【讨论】:

  • 请不要将更新作为新答案发布;只发布答案作为答案。编辑您的原始帖子以添加新信息。
  • 实际上,作为答案的更新应该作为答案发布,而不是作为对原始帖子的编辑。 CDK 做得对。
猜你喜欢
  • 1970-01-01
  • 2016-05-28
  • 2022-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多