Search code examples
javalibgdxbox2dgame-physics

libgdx: speed loss on object collision


I'm trying to build a sample that consists in some circles moving and colliding among themselves. As all the circles have the same attributes and restitution = 1 and friction = 0, I expect that they should bounce eternally always at the same speed. But, instead, some objects slow down:

enter image description here

World creation:

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

Boundaries:

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);

Circles:

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);
}

I've also tried without: World.setVelocityThreshold(0); and without ballBody.setBullet(true); but it does the same.

So, how could I achieve it? If restitution = 1 and friction = 0, what's causing this behavior?

UPDATE:

Finally it works as expected. There are two main problems: float precision and too large initial velocity (box2d has a maximum speed limit).

I have reproduced the example but with much smaller units:

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

This does not prevent the precision error in the positions of the circles, which will end up not being perfectly aligned. But it prevents the system from losing speed.

With these values, once the circles are not aligned, depending on the angle at which they collide, if one slows down the other one accelerates. That is, the momentum of the system is maintained.

In the original example, the problem is that I use large units. Circles' initial velocity is limited to the maximum that box2d allows. In this situation, when the circles collide without being aligned, one may slow down but the other one can not accelerate (because it's already at max speed) and, therefore, the system loses speed. The momentum of the system is not maintained.

The first correction is to use smaller units, so that the precision error is lower and values does not exceed the limits of box2d.

enter image description here

The second, only valid for this concrete example, would be to correct the position of the circles once the simulation of physics has been made.

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());
}

enter image description here


Solution

  • A greater-than-zero linear damping setting on a body is meant for slowing down that body.

    Given the sources you've shown and that box2d sources show that by default the linear damping is 0, it does not appear that the linear damping setting is the culprit. I would re-check the application sources and the sources that libgdx is built from, to confirm that indeed nothing is setting the linear damping on dynamic bodies to non-zero values.

    On the other hand, if linear damping was set to non zero by a default somewhere, I'd expect all the circles to slow down (equally, at least in theory) and your picture appears to show only one circle having a noticeable speed change: a speed loss.

    So I believe something less intentional (and more esoteric) is at work.

    At least some of what you are seeing will come about from the use of floating point arithmetic. The inaccuracy of floating-point arithmetic gets compounded by the bouncing that the circles are doing against the wall and each other. In this situation, the circles — at different x and y locations — will sometimes get slightly different collision response errors that destabilizes the orderly setup they start with.

    For an elaborate explanation of problems that can crop up in the use of floating point arithmetic, see: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

    As to what you can do to prevent this, there aren't any easy solutions that I'm aware of. It'd be easier to mitigate the problem. Some possible mitigations could be:

    • Rerunning the simulation with different sizes, distances, densities, and velocities to see which combination can optimize the simulation best for you. If speed loss is more the concern than the general disarray that the simulation devolves to, increasing masses/densities would be the first thing I'd try of these.
    • Decreasing step delta times and calling step more often.
    • Decreasing the number of circles in your simulation.
    • Replacing the use of float with double in all the Box2D source code and rebuilding the Java implementation on top of your modified Box2D.

    Replacing the use of float with a tried and proven fixed point integer implementation should eliminate the floating point inaccuracies but at the expense of introducing different kinds of inaccuracies and taking significant more work than replacing the use of floats with doubles would. Another tack you could try that should fix the problem would be to override the collision responses in contact listener callbacks such that you account for the inaccuracy and factor this back into the computations. If there are no other interactions that you want to simulate, I think it'd be easier to force the known paths in your contact listener code rather than try to correct the errors.

    If you're expecting 100% Newton's cradle like results, that can be harder to achieve in part for the floating point arithmetic reason I've mentioned. Note that Box2D-like physics simulations often don't provide completely Newton's cradle-like results (for example see 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 which provides a lot of runtime tweak-ability and also demonstrates non-Newton's cradle like results.

    Sorry if this seems like bad news but I do hope it can help anyway.