今天早上我感觉自己写了一个无用的程序,我最终得到了这个extremely minimal astronomic simulator in processing。 MCVE版本的代码附在帖子的末尾。
然后我添加了一些行星,坐下来准备好欣赏一些绕着彼此绕行的行星。然而事实证明是一个问题。
问题是,两个原本相互靠得太近的静态行星往往会相互冲击到超高速,它们都会永远消失在屏幕之外。这显然违背了动力原则。行星不这样做。我开始怀疑我的牛顿法实施有什么问题。但有些时候,只要它们保持“安全”的距离,行星就可以正常工作。
如果你想调查这个问题,我已经为你仔细考虑了两个设置。第一个,增加了两个行星,并且非常稳定。 (您可以通过注释掉其中包含'Three'的每一行来完成此操作。)您可以通过输入默认代码来测试第二行。这是行星疯狂的地方。
这个问题似乎是一个谜,起源于我从未探索过的一些计算机科学领域(尽管我是一个菜鸟)。为了我的头发,如果有人能解释,我会非常感激。
代码开始:
PVector planetOneLocation = new PVector(300, 200);
PVector planetOneSpeed = new PVector(0, -.1);
float planetOneMass = 1;
PVector planetTwoLocation = new PVector(100, 200);
PVector planetTwoSpeed = new PVector(0, .1);
float planetTwoMass = 1;
PVector planetThreeLocation = new PVector(200, 200);
PVector planetThreeSpeed = new PVector(0, 0);
float planetThreeMass = 10;
float g = 5;
void setup() {
size(500, 500);
}
void draw() {
updatePlanetOne();
updatePlanetTwo();
updatePlanetThree();
planetOneLocation.add(planetOneSpeed);
planetTwoLocation.add(planetTwoSpeed);
planetThreeLocation.add(planetThreeSpeed);
background(0);
ellipse(planetOneLocation.x, planetOneLocation.y, 10*planetOneMass, 10*planetOneMass);
ellipse(planetTwoLocation.x, planetTwoLocation.y, 10*planetTwoMass, 10*planetTwoMass);
ellipse(planetThreeLocation.x, planetThreeLocation.y, 10*planetThreeMass, 10*planetThreeMass);
}
void updatePlanetOne() {
PVector accDir = PVector.sub(planetTwoLocation, planetOneLocation);
float a = g * planetTwoMass / (accDir.mag() * accDir.mag());
accDir.normalize();
PVector acceleration = accDir.mult(a);
planetOneSpeed.add(acceleration);
accDir = PVector.sub(planetThreeLocation, planetOneLocation);
a = g * planetThreeMass / (accDir.mag() * accDir.mag());
accDir.normalize();
acceleration = accDir.mult(a);
planetOneSpeed.add(acceleration);
}
void updatePlanetTwo() {
PVector accDir = PVector.sub(planetOneLocation, planetTwoLocation);
float a = g * planetOneMass / (accDir.mag() * accDir.mag());
accDir.normalize();
PVector acceleration = accDir.mult(a);
planetTwoSpeed.add(acceleration);
accDir = PVector.sub(planetThreeLocation, planetTwoLocation);
a = g * planetThreeMass / (accDir.mag() * accDir.mag());
accDir.normalize();
acceleration = accDir.mult(a);
planetTwoSpeed.add(acceleration);
}
void updatePlanetThree() {
PVector accDir = PVector.sub(planetOneLocation, planetThreeLocation);
float a = g * planetOneMass / (accDir.mag() * accDir.mag());
accDir.normalize();
PVector acceleration = accDir.mult(a);
planetThreeSpeed.add(acceleration);
accDir = PVector.sub(planetTwoLocation, planetThreeLocation);
a = g * planetTwoMass / (accDir.mag() * accDir.mag());
accDir.normalize();
acceleration = accDir.mult(a);
planetThreeSpeed.add(acceleration);
}
更新:经过一番努力后,我将浮动变量更改为double。但是我的行星还在弹跳出屏幕。我认为除了double / float问题,实际上解决方案存在一些问题。我没有定义任何时间步骤,这也导致不准确的行为,特别是当速度很高时。
更新2 :设置时间步骤有很大帮助。一些没有时间步骤疯狂的设置现在工作正常。但是只要两个行星的中心有可能非常接近,那么系统就有可能再次变得疯狂。要解决这个问题,需要一个更好的集成商。
更新3 :在回复@ kevin-workman时,我在这里移植了他漂亮的代码来替换原来的项目代码。添加原始帖子中的相同第三个行星,并更新相应的牛顿数学。与他的测试相反,它看起来即使mv.add(p.speed.mult(p.mass));
被注释掉,第三个星球仍然发疯(现在因为我已经将演示代码更改为最小版本,所以不再有这样的行)。我认为mult()
引入的错误确实有所贡献,但具体而言,不稳定的积分器也起着重要作用。
答案 0 :(得分:10)
此问题与float
vs double
准确性无关。 Float
具有足够的准确性,事实上,默认情况下,处理使用float
作为所有内容,因此您尝试使用的任何double
值都会丢失。
你所有的问题都是由这一行引起的:
for (Planet p: planets) {
mv.add(p.speed.mult(p.mass));
}
特别是这一点:
p.speed.mult(p.mass)
此行将每个行星的速度乘以其质量。
您可能认为p.speed.mult(p.mass)
会使原始p.speed
PVector
保持不变,但事实并非如此。 PVector
不是不可变的,因此调用mult()
函数会更改基础PVector
实例。
你的前两颗行星的mass
1
,所以这条线并没有真正影响它们。但是你的第三颗行星有mass
10
,这意味着你每一帧的速度乘以10
。
您只需将原始行星的质量更改为10
或2
,或将第三个行星的质量更改为1
即可对此进行测试。
因此,要解决您的主要问题,只需删除此行,或更改它以便它不会修改p.speed
PVector
。
更多信息可在the Processing reference for PVector#mult()
中找到:
将矢量乘以标量。使用a的方法的版本 float直接作用于调用它的向量(如 上面的第一个例子)。同时接收PVector和a的版本 float as arguments是静态方法,每个返回一个新的PVector 这是乘法运算的结果。
基本上,您可以将该行更改为:
PVector.mult(p.speed, p.mass)
这将使p.speed
保持不变,并返回带有相乘值的副本。
退一步,你将会遇到另一个问题,因为你的行星可以任意地相互靠近。换句话说,它们的距离可以接近(或等于)零。这在现实生活中并没有发生,如果确实如此,你可以打赌事情会变得疯狂。因此,你将会有疯狂的“重力助攻”#34;如果事情变得太近了你可以考虑限制它们的距离。
如果您还有其他问题,如果您使用此MCVE而不是发布整个项目,则会更容易为您提供帮助:
PVector planetOneLocation = new PVector(300, 200);
PVector planetOneSpeed = new PVector(0, -.1);
float planetOneMass = 1;
PVector planetTwoLocation = new PVector(100, 200);
PVector planetTwoSpeed = new PVector(0, .1);
float planetTwoMass = 10;
float g = 5;
void setup() {
size(500, 500);
}
void draw() {
updatePlanetOne();
updatePlanetTwo();
planetOneLocation.add(planetOneSpeed);
planetTwoLocation.add(planetTwoSpeed);
background(0);
ellipse(planetOneLocation.x, planetOneLocation.y, 10*planetOneMass, 10*planetOneMass);
ellipse(planetTwoLocation.x, planetTwoLocation.y, 10*planetTwoMass, 10*planetTwoMass);
}
void updatePlanetOne() {
PVector accDir = PVector.sub(planetTwoLocation, planetOneLocation);
float a = g * planetOneMass / (accDir.mag() * accDir.mag());
accDir.normalize();
PVector acceleration = accDir.mult(a);
planetOneSpeed.add(acceleration);
}
void updatePlanetTwo() {
PVector accDir = PVector.sub(planetOneLocation, planetTwoLocation);
float a = g * planetTwoMass / (accDir.mag() * accDir.mag());
accDir.normalize();
PVector acceleration = accDir.mult(a);
planetTwoSpeed.add(acceleration);
}
答案 1 :(得分:0)
问题出在整合部分。 使用的是欧拉积分器,它不是很准确。 Verlet集成通常用于物理模拟。
引用Ilmari Karonen's Answer,verlet可以实现为:
acceleration = force(time, position) / mass;
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
newAcceleration = force(time, position) / mass;
velocity += timestep * (acceleration + newAcceleration) / 2;