我的行星发疯了 - 与Java有关的问题

时间:2016-05-13 08:18:06

标签: processing physics

今天早上我感觉自己写了一个无用的程序,我最终得到了这个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()引入的错误确实有所贡献,但具体而言,不稳定的积分器也起着重要作用。

2 个答案:

答案 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

您只需将原始行星的质量更改为102,或将第三个行星的质量更改为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;
相关问题