libgdx:物体碰撞时的速度损失

libgdx: speed loss on object collision

我正在尝试构建一个示例,其中包含一些在它们之间移动和碰撞的圆圈。由于所有圆圈都具有相同的属性 restitution = 1friction = 0,我希望它们应该永远以相同的速度反弹。但是,相反,一些物体变慢了:

世界创建:

World.setVelocityThreshold(0);
world = new World(new Vector2(0, 0), true);

边界:

EdgeShape edge = new EdgeShape();
FixtureDef wallFixtureDef = new FixtureDef();
wallFixtureDef.shape = edge;
wallFixtureDef.density = 0;
wallFixtureDef.friction = 0;
wallFixtureDef.restitution = 1;

BodyDef wallBodyDef = new BodyDef();
wallBodyDef.type = BodyDef.BodyType.StaticBody;

Body wallsBody = world.createBody(wallBodyDef);

// Bottom
edge.set(1, 1, WIDTH - 1, 1);
wallsBody.createFixture(wallFixtureDef);

// Top
edge.set(1, HEIGTH - 1, WIDTH - 1, HEIGTH - 1);
wallsBody.createFixture(wallFixtureDef);

// Left
edge.set(1, 1, 1, HEIGTH - 1);
wallsBody.createFixture(wallFixtureDef);

// Right
edge.set(WIDTH - 1, 1, WIDTH - 1, HEIGTH - 1);
wallsBody.createFixture(wallFixtureDef);

圈子:

CircleShape circleShape = new CircleShape();
circleShape.setRadius(24);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = circleShape;
fixtureDef.density = 1;
fixtureDef.friction = 0;
fixtureDef.restitution = 1;

for (int i = 1; i <= 10; i++) {
    BodyDef bodyDef = new BodyDef();
    bodyDef.type = BodyDef.BodyType.DynamicBody;
    bodyDef.position.set(i * 70, HEIGTH / 2);

    Body ballBody = world.createBody(bodyDef);
    ballBody.createFixture(fixtureDef);
    ballBody.setBullet(true);
    ballBody.setLinearVelocity(300, 120);
}

我也尝试过不使用:World.setVelocityThreshold(0); 和不使用 ballBody.setBullet(true);,但效果相同。

那么,我该如何实现呢?如果 restitution = 1friction = 0,是什么导致了这种行为?

更新:

终于按预期运行了。主要有两个问题:浮动精度和初速度太大(box2d有最大速度限制)。

我已经复制了这个例子,但单位要小得多:

radius = 10 
density = 0.1f
velocity = (10, 10)

这并不能防止圆圈位置的精度误差,最终将无法完全对齐。但它可以防止系统失去速度。

使用这些值,一旦圆圈未对齐,取决于它们碰撞的角度,如果一个减速另一个加速。即维持系统的动量

在原来的例子中,问题是我使用了大单位。 Circles 的初始速度被限制为 box2d 允许的最大值。在这种情况下,当圆圈在没有对齐的情况下发生碰撞时,一个可能会减速但另一个不能加速(因为它已经处于最大速度),因此系统会失去速度。系统的动力没有保持。

第一个修正是使用更小的单位,这样精度误差就更低,并且数值不超过box2d的限制。

第二个,仅对这个具体示例有效,将在进行物理模拟后更正圆圈的位置。

world.step(STEP_TIME, VELOCITY_ITERATIONS, POSITION_ITERATIONS);

float y = bodies[0].getPosition().y;
for (Body body : bodies) {
    body.setTransform(body.getPosition().x, y, body.getAngle());
}

body 上的 greater-than-zero 线性阻尼设置旨在减慢 body。

鉴于您显示的来源和 box2d sources show that by default the linear damping is 0,线性阻尼设置似乎不是罪魁祸首。我会 re-check 应用程序源和 libgdx 构建的源,以确认确实没有将动态物体上的线性阻尼设置为 non-zero 值。

另一方面,如果线性阻尼在某处默认设置为非零,我希望所有的圆圈都会减速(同样,至少在理论上)并且您的图片似乎只显示一个圆圈有明显的速度变化:速度损失。

所以我相信一些不那么有意(但更深奥)的东西在起作用。

至少您所看到的某些内容将来自 floating point arithmetic 的使用。 floating-point 算术的不准确性因圆圈在墙上和彼此之间的弹跳而变得更加复杂。在这种情况下,位于不同 xy 位置的圆圈有时会出现略有不同的碰撞响应错误,这会破坏它们开始时的有序设置。

有关浮点运算使用中可能出现的问题的详细解释,请参阅:What Every Computer Scientist Should Know About Floating-Point Arithmetic

至于您可以采取什么措施来防止这种情况发生,据我所知,没有任何简单的解决方案。减轻问题会更容易。一些可能的缓解措施可能是:

  • 用不同的大小、距离、密度和速度重新运行模拟,看看哪种组合可以最适合您优化模拟。如果速度损失比模拟引起的一般混乱更令人担忧,那么增加 masses/densities 将是我首先要尝试的。
  • 减少步骤增量时间并更频繁地调用步骤。
  • 减少模拟中的圆圈数。
  • 在所有 Box2D 源代码中将 float 的使用替换为 double,并在修改后的 Box2D 之上重建 Java 实现。

用久经考验的定点整数实现替换 float 的使用应该可以消除浮点不准确,但代价是引入不同类型的不准确,并且比使用 double 替换 float 需要更多的工作。您可以尝试解决问题的另一种方法是覆盖接触侦听器回调中的碰撞响应,这样您就可以解决不准确问题并将其重新纳入计算。如果您不想模拟其他交互,我认为在您的联系人侦听器代码中强制使用已知路径比尝试更正错误更容易。

如果您期望 100% 牛顿摇篮般的结果,那可能更难实现,部分原因是我提到的浮点运算原因。请注意,类似 Box2D 的物理模拟通常不提供完全牛顿的 cradle-like 结果(例如,请参阅 this video on YouTube). There are some discussions that I've found on the web about this such as Newton's cradle on the Bullet Physics forum which may offer more insight or ideas. Incidentally, I have a Newton's cradle demo in the test bed of the physics engine I've been working on,它提供了大量的运行时间 tweak-ability 并且还演示了 non-Newton 的摇篮般的结果。

抱歉,如果这看起来像是个坏消息,但我确实希望它能有所帮助。