0
votes

I am using Libgdx's box2d implementation.

I am encountering a null pointer error after I delete a body from the world. I've tracked the problem down to my contact listener, where I am testing the fixture's filter against the other fixture to see if I should process the collision. The problem is that one of the fixtures is null. Initially you would think, okay so only process the collision for the non-null fixture, but if I don't know what the fixture was that ended contact, it becomes impossible to know what is and is not contacting the non-null fixture.

Before I get into the code, I promise that making the actual world.destroyBody(body) call outside of the physics step by adding the body to a queue to be processed later. Additionally the body gets queued in the update loop, which happens before the world.step()

(Note: Entities are basically wrappers for bodies)

Here is the main update loop

@Override
public void render() {
    processDestroyQueue();

    sm.update();   //Calls entity update functions, where the entity is added to the q
    camera.update();


    //Step
    world.step(STEP, 6, 2); //Step == 1/60f

    //... removed rendering stuff
}

Here is the add to queue function and processDestroyQueue()

public void destroy(Entity ent){
    destroyQueue.add(ent);
}

private void processDestroyQueue(){
    Entity ent;
    while((ent = destroyQueue.poll()) != null){
        world.destroyBody(ent.body());
        ent.dispose();
        entities.remove(ent);
    }
}

Finally here is the contact handler

public class ContactHandler implements ContactListener {

@Override
public void beginContact(Contact contact) {
    Fixture fixA = contact.getFixtureA();
    Fixture fixB = contact.getFixtureB();

    parseContact(true, fixA, fixB, contact);
}

@Override
public void endContact(Contact contact) {
    Fixture fixA = contact.getFixtureA();
    Fixture fixB = contact.getFixtureB();

    parseContact(false, fixA, fixB, contact);
}

@Override
public void preSolve(Contact contact, Manifold oldManifold) {
    // TODO Auto-generated method stub

}

@Override
public void postSolve(Contact contact, ContactImpulse impulse) {
    // TODO Auto-generated method stub

}

public void parseContact(boolean begin, Fixture fixA, Fixture fixB, Contact contact){
    System.out.println("FixtureA: " + fixA);
    System.out.println("FixtureB: " + fixB);
    if( (fixA.getFilterData().categoryBits & fixB.getFilterData().maskBits) != 0 ){
        sendContact(begin, fixA, fixB, contact);
        sendContact(begin, fixB, fixA, contact);
    }else if( (fixB.getFilterData().categoryBits & fixA.getFilterData().maskBits) != 0 ){
        sendContact(begin, fixA, fixB, contact);
        sendContact(begin, fixB, fixA, contact);
    }
}

public void sendContact(boolean begin, Fixture fixA, Fixture fixB, Contact contact){
    Object dataA = fixA.getUserData();
    Object dataB = fixB.getUserData();

    if(dataA instanceof FixtureData && dataB instanceof FixtureData){
        FixtureData fixtureDataB = (FixtureData)dataB;
        FixtureData fixtureDataA = (FixtureData)dataA;
        fixtureDataA.contact(fixB, contact, begin);
        fixtureDataB.contact(fixA, contact, begin);
    }
}

}

Lastly the error:

Exception in thread "LWJGL Application" java.lang.NullPointerException
at com.gearworks.game.ContactHandler.parseContact(ContactHandler.java:42)
at com.gearworks.game.ContactHandler.endContact(ContactHandler.java:24)
at com.badlogic.gdx.physics.box2d.World.endContact(World.java:903)
at com.badlogic.gdx.physics.box2d.World.jniDestroyBody(Native Method)
at com.badlogic.gdx.physics.box2d.World.destroyBody(World.java:322)
at com.gearworks.Client.processDestroyQueue(Client.java:189)
at com.gearworks.Client.render(Client.java:118)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:206)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)

From what it looks like to me Box2D simply erases the fixture and then dispatches its end contact event. I need That fixture to be in the end contact even so that I know which fixture has completed its contact. Is my implementation wrong? Or is this just how box2D works and I need to figure out some other method?

1
So at the beginning of parseContact, the println says that the fixture is null? Fixtures are supposed to still be valid at that point, otherwise there is no point to having an EndContact listener. btw I think you have a typo in the last line of the sendContact function. - iforce2d
Oh you're right I do, the second one should be fixA. But yeah, fixture A is null after it is delete. But that was my thought exactly. - RaptorIV
If the EndContact function gives you a null fixture, it would be a bug in the library. But I think many other people would be complaining if that was the case. Are you sure the fixture is null, or is getFilterData returning null... - iforce2d
The printlines print out: FixtureA: null FixtureB: com.badlogic.gdx.physics.box2d.Fixture@76d5989c - RaptorIV
Also I sincerely doubt its a bug with the engine. Is there any way I could accidentally trigger this behavior? - RaptorIV

1 Answers

1
votes

It was indeed a bug in libgdx:

https://github.com/libgdx/libgdx/issues/1381

https://github.com/libgdx/libgdx/pull/1837

I think you just need a newer version of the source.