2
votes

I'm having a lot of trouble detecting collisions in a zero-G space game. Hopefully this image will help me explain:

http://i.stack.imgur.com/f7AHO.png

The white rectangle is a static body with a b2PolygonShape fixture attached, as such:

    // Create the line physics body definition
b2BodyDef wallBodyDef;
wallBodyDef.position.Set(0.0f, 0.0f);

// Create the line physics body in the physics world
wallBodyDef.type = b2_staticBody; // Set as a static body
m_Body = world->CreateBody(&wallBodyDef);

    // Create the vertex array which will be used to make the physics shape
b2Vec2 vertices[4];
vertices[0].Set(m_Point1.x, m_Point1.y); // Point 1
vertices[1].Set(m_Point1.x + (sin(angle - 90*(float)DEG_TO_RAD)*m_Thickness), m_Point1.y - (cos(angle - 90*(float)DEG_TO_RAD)*m_Thickness)); // Point 2
vertices[2].Set(m_Point2.x + (sin(angle - 90*(float)DEG_TO_RAD)*m_Thickness), m_Point2.y - (cos(angle - 90*(float)DEG_TO_RAD)*m_Thickness)); // Point 3
vertices[3].Set(m_Point2.x, m_Point2.y); // Point 3
int32 count = 4; // Vertex count

b2PolygonShape wallShape; // Create the line physics shape
wallShape.Set(vertices, count); // Set the physics shape using the vertex array above

// Define the dynamic body fixture
b2FixtureDef fixtureDef;
fixtureDef.shape = &wallShape; // Set the line shape
fixtureDef.density = 0.0f; // Set the density
fixtureDef.friction = 0.0f; // Set the friction
fixtureDef.restitution = 0.5f; // Set the restitution

// Add the shape to the body
m_Fixture = m_Body->CreateFixture(&fixtureDef);
m_Fixture->SetUserData("Wall");[/code]

You'll have to trust me that that makes the shape in the image. The physics simulation works perfectly, the player (small triangle) collides with the body with pixel perfect precision. However, I come to a problem when I try to determine when a collision takes place so I can remove health and what-not. The code I am using for this is as follows:

/*------ Check for collisions ------*/
        if (m_Physics->GetWorld()->GetContactCount() > 0)
        {
            if (m_Physics->GetWorld()->GetContactList()->GetFixtureA()->GetUserData() == "Player" &&
                m_Physics->GetWorld()->GetContactList()->GetFixtureB()->GetUserData() == "Wall")
            {
                m_Player->CollideWall();
            }
        }

I'm aware there are probably better ways to do collisions, but I'm just a beginner and haven't found anywhere that explains how to do listeners and callbacks well enough for me to understand. The problem I have is that GetContactCount() shows a contact whenever the player body enters the purple box above. Obviously there is a rectangular bounding box being created that encompasses the white rectangle.

I've tried making the fixture an EdgeShape, and the same thing happens. Does anyone have any idea what is going on here? I'd really like to get collision nailed so I can move on to other things. Thank you very much for any help.

4

4 Answers

2
votes

The bounding box is an AABB (axis aligned bounding box) which means it will always be aligned with the the Cartesian axes. AABBs are normally used for broadphase collision detection because it's a relatively simple (and inexpensive) computation.

You need to make sure that you're testing against the OBB (oriented bounding box) for the objects if you want more accurate (but not pixel perfect, as Micah pointed out) results.

Also, I agree with Micah's answer that you will most likely need a more general system for handling collisions. Even if you only ever have just walls and the player, there's no guarantee that which object will be A and which will be B. And as you add other object types, this will quickly unravel.

2
votes

Creating the contact listener isn't terribly difficult, from the docs (added to attempt to handle your situation):

class MyContactListener:public b2ContactListener
{
private:
    PlayerClass *m_Player;

public:
    MyContactListener(PlayerClass *player) : m_Player(player)
    { }

    void BeginContact(b2Contact* contact)
    { /* handle begin event */ }

    void EndContact(b2Contact* contact)
    {
        if (contact->GetFixtureA()->GetUserData() == m_Player
         || contact->GetFixtureB()->GetUserData() == m_Player)
        {
            m_Player->CollideWall();
        }
    }

    /* we're not interested in these for the time being */

    void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
    { /* handle pre-solve event */ }

    void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse)
    { /* handle post-solve event */ }
};

This requires you to assign m_Player to the player's fixture's user data field. Then you can use the contact listener like so:

m_Physics->GetWorld()->SetContactListener(new MyContactListener(m_Player));
0
votes

How do you know GetFixtureA is the player and B is the wall? Could it be reversed? Could there be an FixtureC? I would think you would need a more generic solution.

I've used a similar graphics framework (Qt) and it had something so you could grab any two objects and call something like 'hasCollided' which would return a bool. You could get away with not using a callback and just call it in the drawScene() or check it periodically.

0
votes

In Box2D the existence of a contact just means that the AABBs of two fixtures overlaps. It does not necessarily mean that the shapes of the fixtures themselves are touching.

You can use the IsTouching() function of a contact to check if the shapes are actually touching, but the preferred way to deal with collisions is to use the callback feature to have the engine tell you whenever two fixtures start/stop touching. Using callbacks is much more efficient and easier to manage in the long run, though it may be a little more effort to set up initially and there are a few things to be careful about - see here for an example: http://www.iforce2d.net/b2dtut/collision-callbacks