3
votes

I'm having a problem with the box2d as3 b2ContactListener class. I have a class named ContactListener that extends b2ContactListener and overrides the PostSolve method. PostSolve takes 2 parameters, contact which holds the info about the 2 objects that have contact, and impulse which holds info about the contact. I'm using the impulse parameter to decide how hard 2 objects hit and then I apply damage accordingly.

Here is the problem: If I make anything that is circular, and let it slowly roll on the static body I have as the ground, then drop a fairly large object anywhere on the ground, the circle while in motion will get contacts will a repeating impulse that is way to large for just rolling. It causes the rolling circular objects to break when they shouldn't.

Its almost as if its shaking the static body and causing massive damage to objects hundreds of meters away, but it only affects circles.

Can anyone shed some light on the situation? Is this a known issue? Workarounds?

I am using Box2DAs3 version 2.1a.

Update: Once the body enters this weird state of applying to much damage, any circle that touches it gets tons of large impulses. Once something non circular come in contact with the body, it no longer has the problem. Also this problem is not only on static objects but dynamic and kinematic as well.

Update: I have narrowed the problem down further. The contact listener freaks out and apply's mass impulses when the flat edge of a large object hits my ground object. Any object, not just circles, that is awake and touches the ground will get tons of PostSolve method calls. I tried dropping a box with a size of 11 pixels by 11 pixels onto the ground while a circle was rolling on the ground. The bug did not happen. However if the box is 12 by 12 the bug does occur. Also if i rotate a box with a size of 12 by 12 to 0.1 degrees, the bug does not occur. There needs to be a large enough contact area for it to repro. Also density of the box does not effect anything. Also if the box is a rectangle with a width of 10 and height of 100 and the bug will repro. It's almost like just the size of the object is causing the bug, possibly not the area of contact size.

Update: Here is a link to a Box2D forum post I made that has an example swf with source.

Link

1
I know nothing about box2d, but this actually makes a certain amount of physical sense, IF it affects stationary circles as well as rolling ones.Beta
Sleeping circles, polygon that are sleeping and awake are all not affected by it.Jordan
Fascinating! This sounds like a bug in box2d, and I don't think there will be a clean workaround.Beta
can you post a link to what you are describing?Zevan
@Zevan I added a link to an exampleJordan

1 Answers

0
votes

WOW. So I've finally found out what the problem is.

After hours upon hours of trying to figure out if there is a workaround by doing something fancy in the ContactListener, I decided to look and see where the PostSolve method was being called from. It comes from a class named b2Island and it comes from the Report function in that class. At first glance I spotted the problem easily. Her is the function:

private static var s_impulse:b2ContactImpulse = new b2ContactImpulse();
public function Report(constraints:Vector.<b2ContactConstraint>) : void
{
    if (m_listener == null)
    {
        return;
    }

    for (var i:int = 0; i < m_contactCount; ++i)
    {
        var c:b2Contact = m_contacts[i];
        var cc:b2ContactConstraint = constraints[ i ];

        for (var j:int = 0; j < cc.pointCount; ++j)
        {
            s_impulse.normalImpulses[j] = cc.points[j].normalImpulse;
            s_impulse.tangentImpulses[j] = cc.points[j].tangentImpulse;
        }
        m_listener.PostSolve(c, s_impulse);
    }
}

So yeah obviously the s_impulse var is static so it will be the same for every instance of the b2Island class ( if there is more then one ) and also it's not getting reset at any point. All the references to the s_impulse var can be seen above, so nothing else ever happens to it. But here's the point, a circle only has one contact with a polygon meaning it will only set the impulse for one contact when being reported. The other contact, if not being reset will have the last impulse of the last object to be reported.

Basically the impulses seen on the circle is actually left over impulse from whatever just got reported. To fix it do this:

private static var s_impulse:b2ContactImpulse = new b2ContactImpulse();
public function Report(constraints:Vector.<b2ContactConstraint>) : void
{
    if (m_listener == null)
    {
        return;
    }

    for (var i:int = 0; i < m_contactCount; ++i)
    {
        s_impulse = new b2ContactImpulse();

        var c:b2Contact = m_contacts[i];
        var cc:b2ContactConstraint = constraints[ i ];

        for (var j:int = 0; j < cc.pointCount; ++j)
        {
            s_impulse.normalImpulses[j] = cc.points[j].normalImpulse;
            s_impulse.tangentImpulses[j] = cc.points[j].tangentImpulse;
        }
        m_listener.PostSolve(c, s_impulse);
    }
}

It's as simple as that.