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

shi*_*shi 7 processing physics

今天早上我想写一个无用的程序,我最终得到了这个极其简单的天文模拟器.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);
}
Run Code Online (Sandbox Code Playgroud)

更新:经过一番努力后,我将浮点变量更改为double.但是我的行星还在弹跳出屏幕.我认为除了double/float问题,实际上解决方案存在一些问题.我没有定义任何时间步骤,这也导致不准确的行为,特别是当速度很高时.

更新2:设置时间步骤有很大帮助.一些没有时间步骤疯狂的设置现在工作正常.但是只要两个行星的中心有可能非常接近,那么系统就有可能再次变得狂野.要解决这个问题,需要一个更好的集成商.

更新3:在回应@ kevin-workman时,我在这里移植了他漂亮的代码来替换原来的项目代码.添加原始帖子中的相同第三个行星,并更新相应的牛顿数学.与他的测试相反,它看起来即使mv.add(p.speed.mult(p.mass));被注释掉,第三个星球仍然疯狂(现在因为我已经将演示代码更改为最小版本,所以不再有这样的行).我认为由此引入的错误mult()有所贡献,但具体而言,不稳定的积分器也起着重要作用.

Kev*_*man 10

这个问题与floatvs double准确性无关.Float具有足够的准确性,事实上float,默认情况下,处理用于所有内容,因此double您尝试使用的任何值都会丢失.

你的所有问题都是由这条线引起的:

for (Planet p: planets) {
    mv.add(p.speed.mult(p.mass));
}
Run Code Online (Sandbox Code Playgroud)

特别是这一点:

p.speed.mult(p.mass)
Run Code Online (Sandbox Code Playgroud)

这条线将每个行星的速度乘以其质量.

你可能会认为原来p.speed.mult(p.mass)p.speed PVector保持不变,但事实并非如此.PVector不是不可变的,因此调用该mult()函数会更改基础PVector实例.

你的前两个行星有一个mass1,所以此行并没有真正影响他们.但是,你的第三行星有一个mass10,这意味着你乘以其速度10每一个帧.

您可以通过简单地将原始行星的质量改变为10甚至2或通过将第三个行星的质量改变为来测试这一点1.

因此,要解决您的主要问题,只需删除此行,或更改它,以便它不会修改p.speed PVector.

更多信息可以在Processing reference中找到PVector#mult():

将矢量乘以标量.使用float的方法版本直接作用于调用它的向量(如上面的第一个例子).同时接收PVector和float作为参数的版本是静态方法,并且每个版本都返回一个新的PVector,它是乘法运算的结果.

基本上,您可以将该行更改为:

PVector.mult(p.speed, p.mass)
Run Code Online (Sandbox Code Playgroud)

这将p.speed保持不变并返回带有乘法值的副本.

退后一步,你会遇到另一个问题,因为你的行星可以任意地相互靠近.换句话说,它们的距离可以接近(或等于)零.这在现实生活中不会发生,如果确实如此,你可以打赌事情会变得疯狂.如果事情变得太近,那么你将会有疯狂的"重力助力".您可以考虑限制它们的距离.

如果您还有其他问题,如果您使用此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);
}
Run Code Online (Sandbox Code Playgroud)