Format of Collision Normals

console
Posts: 18
Joined: Thu Sep 04, 2008 10:25 am

Format of Collision Normals

Post by console »

I would like to integrate the SOLID collision detection library (http://www.dtecta.com/) into Bullet. I've made progress towards the integration and I can now detect collisions along with the contact points on the two colliding objects. However, the normals in Bullet and SOLID appear to be of differing formats. In one particular collision, for instance, I get (-7.8945e-5, -1.0, -3.0847318e-7) with Bullet but (3.0517578e-5, 0.51364136, 3.0517578e-5) with SOLID. This results in an inappropriate collision response from Bullet.

Could anyone tell me the format of Bullet's collision normals, so that I can fully integrate the two? It'd also be great if you could let me know how I can convert from SOLID's format to Bullet's.

Thanks.
User avatar
Erwin Coumans
Site Admin
Posts: 4221
Joined: Sun Jun 26, 2005 6:43 pm
Location: California, USA

Re: Format of Collision Normals

Post by Erwin Coumans »

Interesting, is that a research project?

Bullet started off similar to SOLID but added several improvements, so I'm interested in any benefits of SOLID.
Bullet/Extras/ExtraSolid35 contains wrappers that allow to use the inner parts of SOLID, EPA and Johnson, together with Bullet. However, SOLID's GJK and EPA implementation used to be broken, as can be verified by the Bullet/Demos/EPAPenDepthDemo. This was reported back in 2006 by Pierre Terdiman (OPCODE), and fixed for Bullet.

Bullet normals point from object B towards object A, and might be not normalized. SOLID normals point from A to B, so a 'normalize' and multiplying by -1 might help.
Thanks,
Erwin
console
Posts: 18
Joined: Thu Sep 04, 2008 10:25 am

Re: Format of Collision Normals

Post by console »

Thanks for the information.

I followed your advice for converting the normal. It didn't resolve the problem but instead resulted in strange behaviour. The objects travel through one another, as before, but the response is delayed so that objects pause appropriately once they collide, before travelling through one another. It's like the collision works correctly for a while before being dismissed by SOLID. ExtraSolid35 didn't really clarify any of the issues.

By the way, this is a research project. I just want to compare a number of collision detection systems. I'm not expecting to get better results from SOLID but I need to be able to compare Bullet with something, and being most familiar with SOLID, I chose that.
User avatar
Erwin Coumans
Site Admin
Posts: 4221
Joined: Sun Jun 26, 2005 6:43 pm
Location: California, USA

Re: Format of Collision Normals

Post by Erwin Coumans »

How are you integrating SOLID exactly? Do you use SOLID for broadphase and narrowphase, using 'dtTest'?
How do you store btPersistentManifolds, and do you update/refresh contact manifolds?
Bullet works best with persistent contact manifolds, to gather multiple contact points. SOLID 3.5.x doesn't have this. One point is not enough for stable stacking. But this is not likely the problem.

Does SOLID report penetrations?
Have you tried the 'hybrid' version, with a positive collision margin?

Thanks,
Erwin
console
Posts: 18
Joined: Thu Sep 04, 2008 10:25 am

Re: Format of Collision Normals

Post by console »

I'm using the broadphase and narrowphase with DT_Test and the DT_DEPTH_RESPONSE class. All of my objects have a positive margin.

I'm storing and refreshing btPersistentManifolds. On each cycle, I execute the code below before calling DT_Test. (My class inherits from btDiscreteDynamicsWorld.)

Code: Select all

for (int i = 0; i < m_dispatcher1->getNumManifolds(); i++) {
  const btRigidBody* body0 = reinterpret_cast<const btRigidBody*>(m_dispatcher1->getManifoldByIndexInternal(i)->getBody0());
  const btRigidBody* body1 = reinterpret_cast<const btRigidBody*>(m_dispatcher1->getManifoldByIndexInternal(i)->getBody1());
  m_dispatcher1->getManifoldByIndexInternal(i)->refreshContactPoints(body0->getWorldTransform(), body1->getWorldTransform());
}
Then, in the callback function where SOLID reports the collision, I execute the following:

Code: Select all

btPersistentManifold* manifold = pointer->m_dispatcher1->getNewManifold(reinterpret_cast<void*>(pointer->getCollisionObjectArray()[i]), reinterpret_cast<void*>(pointer->getCollisionObjectArray()[j]));
const btVector3 pointA(collData->point1[0], collData->point1[1], collData->point1[2]);
btVector3 pointB(collData->point2[0], collData->point2[1], collData->point2[2]);
pointB -= pointer->getCollisionObjectArray()[j]->getWorldTransform().getOrigin();
const btVector3 normal(collData->normal[0], collData->normal[1], collData->normal[2]);
manifold->addManifoldPoint(btManifoldPoint(pointA, pointB, normal, 0.0));
i and j are references to the colliding objects, as reported by SOLID.

I don't know of a way for SOLID to report penetrations. It reports collisions successfully, including those where the objects have inter-penetrated, although it does not explicitly mark these as such. It is possible to calculate the penetration depth using DT_GetPenDepth.

By the hybrid version, I assume you mean the ExtraSolid35 demo. Unfortunately, the functionality I need doesn't seem to be present in this example, so I have to use the main library.

Thanks.
User avatar
Erwin Coumans
Site Admin
Posts: 4221
Joined: Sun Jun 26, 2005 6:43 pm
Location: California, USA

Re: Format of Collision Normals

Post by Erwin Coumans »

Where/when do you discard contact manifolds? It looks like you are creating a new btPersistentManifold each collision callback?

Bullet collision detection only creates a contact manifold once objects start overlapping, and only discards them when broadphase AABB overlap stops. This way, points are cached: added and removed/updated during the 'refreshContactPoints' call. If you don't do this, you end up with only one point: it should still work but resting contact will be very instable.

Code: Select all

manifold->addManifoldPoint(btManifoldPoint(pointA, pointB, normal, 0.0));
Hardcoded distance of 0.0 won't work: you need to provide the actual distance between the closest points on both objects, projected onto the separating normal: positive distance = separation, negative distance is penetration.

During a SOLID callback you need to figure out if a pair of contacts has positive distance, or zero. If zero, it might be a penetration, so use DT_GetPenDepth to find the actual penetrating distance. Are you using SOLID-3.5.6 from http://www.dtecta.com?
It has DT_GetPenDepth in the C-API, that should report penetrations indeed. That information is necessary to calculate the penetration information (= collision normal, point on A, point on B, distance).

See Bullet\btGjkPairDetectioYou might want to add something similar to SOLID to calculate normal/penetraton depth. Also, you can report contacts with positive distance, Bullet constraint solver and persistent manifolds can deal with positive distances.
See for example http://code.google.com/p/bullet/source/ ... or.cpp#275

Code: Select all

//somehow distinguish between separating (positive distance) contacts and penetrating contacts (negative distance)
//given a penetration:
btVector3 tmpNormalInB = tmpPointOnB-tmpPointOnA;
btScalar lenSqr = tmpNormalInB.length2();
if (lenSqr > (SIMD_EPSILON*SIMD_EPSILON))
{
	tmpNormalInB /= btSqrt(lenSqr);
	btScalar distance2 = -(tmpPointOnA-tmpPointOnB).length();
}
Hope this helps,
Erwin
console
Posts: 18
Joined: Thu Sep 04, 2008 10:25 am

Re: Format of Collision Normals

Post by console »

I'm using SOLID 3.5.6 from http://www.dtecta.com/

I've modified my code based on your suggestions, although I'm still unsure if I'm updating the Bullet data structures correctly.

The code I've got is:

Code: Select all

void SOLIDDynamicsWorld::PerformCollisionDetection() {
    for (int i = 0; i < m_dispatcher1->getNumManifolds(); i++) {
        const btRigidBody* body0 = reinterpret_cast<const btRigidBody*>(m_dispatcher1->getManifoldByIndexInternal(i)->getBody0());
        const btRigidBody* body1 = reinterpret_cast<const btRigidBody*>(m_dispatcher1->getManifoldByIndexInternal(i)->getBody1());
        m_dispatcher1->getManifoldByIndexInternal(i)->refreshContactPoints(body0->getWorldTransform(), body1->getWorldTransform());
    }

    const btCollisionObjectArray& objects = getCollisionObjectArray();
    for (unsigned int i = 1; i < _objects.size(); ++i) {
        const btTransform&  transform = objects[i]->getWorldTransform();
        const btVector3&    origin    = transform.getOrigin();
        const btQuaternion& rotation  = transform.getRotation();

        const DT_Vector3    position    = {origin[0], origin[1], origin[2]};
        const DT_Quaternion orientation = {rotation[0], rotation[1], rotation[2], rotation[3]};
        DT_SetPosition(_objects[i][0].get(), position);
        DT_SetOrientation(_objects[i][0].get(), orientation);
    }
    DT_Test(_scene.get(), _responseTable.get());

    for (unsigned int i = 0; i < static_cast<unsigned int>(m_dispatcher1->getNumManifolds()); ++i) {
        btPersistentManifold* currentManifold = m_dispatcher1->getManifoldByIndexInternal(i);

        const btRigidBody* body0 = reinterpret_cast<const btRigidBody*>(currentManifold->getBody0());
        const btRigidBody* body1 = reinterpret_cast<const btRigidBody*>(currentManifold->getBody1());

        const std::tr1::tuple<unsigned int, unsigned int> collision(reinterpret_cast<unsigned int>(body0->getUserPointer()), reinterpret_cast<unsigned int>(body1->getUserPointer()));

        if (_collisions.count(collision) == 0) {
            m_dispatcher1->releaseManifold(currentManifold);
            --i;
        }
    }

    _collisions.clear();
}
and

Code: Select all

DT_Bool SOLIDDynamicsWorld::ProcessCollision(void* clientData, void* clientObject0, void* clientObject1, const DT_CollData* collData) {
    SOLIDDynamicsWorld* pointer = reinterpret_cast<SOLIDDynamicsWorld*>(clientData);

    unsigned int i = GetIndex(clientObject0);
    unsigned int j = GetIndex(clientObject1);

    if (i != j) {
        if (i > j) {
            std::swap(i, j);
        }

        const std::tr1::tuple<unsigned int, unsigned int> collision(i, j);
        pointer->_collisions.insert(collision);

        btPersistentManifold* manifold = NULL;
        for (unsigned int k = 0; k < static_cast<unsigned int>(pointer->m_dispatcher1->getNumManifolds()); ++k) {
            btPersistentManifold* currentManifold = pointer->m_dispatcher1->getManifoldByIndexInternal(k);
            if ((currentManifold->getBody0() == reinterpret_cast<void*>(pointer->getCollisionObjectArray()[i])) && (currentManifold->getBody1() == reinterpret_cast<void*>(pointer->getCollisionObjectArray()[j]))) {
                manifold = currentManifold;
                break;
            }
        }
        if (manifold == NULL) {
            manifold = pointer->m_dispatcher1->getNewManifold(reinterpret_cast<void*>(pointer->getCollisionObjectArray()[i]), reinterpret_cast<void*>(pointer->getCollisionObjectArray()[j]));
        }

        const btVector3 pointA(collData->point1[0], collData->point1[1], collData->point1[2]);
        btVector3 pointB(collData->point2[0], collData->point2[1], collData->point2[2]);
        pointB -= pointer->getCollisionObjectArray()[j]->getWorldTransform().getOrigin();
        btVector3 normal(collData->normal[0], collData->normal[1], collData->normal[2]);
        normal.normalize();
        const btVector3 oppositeNormal = -normal;

        const ScalarType distanceSquared = (pointB - pointA).length2();
        if (distanceSquared > (SIMD_EPSILON * SIMD_EPSILON)) {
            const ScalarType distance = -(pointA - pointB).length();
            manifold->addManifoldPoint(btManifoldPoint(pointA, pointB, oppositeNormal, distance));
        }
    }
    return DT_CONTINUE;
}
When a collision occurs now, the objects pause before resuming their path (through the other object) after a few frames. It seems to be working better, but something is still wrong. Can you see what I'm doing wrong?

Thanks.