Bumpy triangle meshes

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

Re: Bumpy triangle meshes

Post by Erwin Coumans »

sparkprime wrote: Does its normal remain constant during its lifetime?
Yes.
What about its other attributes?
Local contact points (in body space) are constant, but due to body motion the world-space contact points and penetration depth is incrementally updated. m_lifeTime is incremented, starting from zero.
How do I register the callback?
You need to set it through a global function, there is no 'proper' API. See Bullet/Demos/ConcaveDemo or Bullet/Demos/MultiMaterialDemo for examples.

Code: Select all

extern ContactAddedCallback		gContactAddedCallback;
gContactAddedCallback = customCallback;
Is the only way to keep tabs on contacts during their full life time to iterate through all the contacts every frame?
It all depends, there are many different methods, but why would you need to iterate over all contacts?
About the winding, as far as I understand it, bullet doesn't care about the winding, and the triangles are essentially two-sided. I wonder, if it were possible to filter out contacts on triangles to make them 'one way', whether this would solve some problems with objects getting jammed in walls, etc.
Correct, triangles are two-sided when using btConvexConcaveCollisionAlgorithm. It is possible to add/replace the collision algorithms and/or collision shapes that perform single-sided collision detection.

Hope this helps,
Erwin
pico
Posts: 229
Joined: Sun Sep 30, 2007 7:58 am

Re: Bumpy triangle meshes

Post by pico »

Hi Alex,

i tried your latest callback. The box is still going through walls unfortunately.
AlexSilverman
Posts: 141
Joined: Mon Jul 02, 2007 5:12 pm

Re: Bumpy triangle meshes

Post by AlexSilverman »

Hi pico,

I don't know how complicated your program is, but would it be possible to put together a small application that shows what you're seeing?

Thanks.

- Alex
pico
Posts: 229
Joined: Sun Sep 30, 2007 7:58 am

Re: Bumpy triangle meshes

Post by pico »

Hi Alex,

the application is a complete game. However, it should be very easily reproduceable. Use a custom bumpy mesh with some floors and walls and a box moving along. The result are not always wrong. Even on the same mesh position you can sometimes go through walls and sometimes don't. This looks to me like it could be related to the caching of contacts which may fail when the contact are changed in your callback?!
sparkprime
Posts: 508
Joined: Fri May 30, 2008 2:51 am
Location: Ossining, New York
Contact:

Re: Bumpy triangle meshes

Post by sparkprime »

Thanks for the detailed reply. I'm going to cook up a small reproduction case... In the mean time:
Erwin Coumans wrote: Local contact points (in body space) are constant, but due to body motion the world-space contact points and penetration depth is incrementally updated. m_lifeTime is incremented, starting from zero.
Does the impulse also change? Is there a single impulse per internal simulation step or is it more complicated than that (with several solver iterations having their own impulses within a single simulation step)?

And what is the relationship with manifolds?
Is the only way to keep tabs on contacts during their full life time to iterate through all the contacts every frame?
It all depends, there are many different methods, but why would you need to iterate over all contacts?
I'm not sure... if I have some kind of 'sparks' animation or smoke/dust etc i'd want to move it around with the object, is that a good excuse? If I wanted to destroy an object after too much force was put on it, I'd have to keep examining the contact would I not?
Correct, triangles are two-sided when using btConvexConcaveCollisionAlgorithm. It is possible to add/replace the collision algorithms and/or collision shapes that perform single-sided collision detection.
Ah how can I for instance make a gimpact mesh one-sided? Can it be done per-collision-shape? I have problems with cars getting jammed into the curb, and sometimes things tunneling through the car's body and rattling around on the inside. However I'd want the static triangle meshes to be single sided I think at least for some instances.

Changing the algorithm would mean changing it for everything but maybe we could add a flag that allowed it to be chosen per shape?
sparkprime
Posts: 508
Joined: Fri May 30, 2008 2:51 am
Location: Ossining, New York
Contact:

Re: Bumpy triangle meshes

Post by sparkprime »

Here is the reproduction case. I made it last night but got knocked offline so I had to wait until now to post it. It is based on the hello world example. There is a ground (trimesh) and a box (0.15 halfwidth) resting on the ground. The box is shoved along the ground and flies up into the air when it crosses a triangle boundary. There are no graphics but the (x,y) coords of the box are printed to stdout. The y output should be 0.15 constantly but there are several severe knocks in the air that are caused by trimesh edges.

If NORMAL_HACK is defined, code that attempts to adjust the normal is turned on. This vastly improves the behaviour of the box (although it still isn't perfect). I tried a number of things but I didn't have net access at the time so this is all home-made and not quite the same as the example given earlier.

The code is below, and also a graph of the (x,y) coords. The green line is with NORMAL_HACK, the red line is without. You can see that the green line is almost flat as it should be but the red line is knocked into the air several times as the box goes in the direction of +x. The white line is 0 so it gets knocked up by its own height -- quite noticable.

Image

Code: Select all

#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <vector>

#include <btBulletDynamicsCommon.h>

// Used to initialise the trimesh
typedef btAlignedObjectArray<btVector3> Vertexes;
struct Face {
        Face (int v1_, int v2_, int v3_, unsigned int flag_)
              : v1(v1_), v2(v2_), v3(v3_), flag(flag_) { }
        int v1, v2, v3;
        unsigned long flag; // unused in this repro case but don't want to remove it
};
typedef std::vector<Face> Faces;


//#define NORMAL_HACK
#ifdef NORMAL_HACK
std::ostream &operator<<(std::ostream &o, btVector3 &v) {
        o << "("<<v.x()<<", "<<v.y()<<", "<<v.z()<<")";
        return o;
}

btVector3 get_face_normal (const btStridingMeshInterface *mesh, int face) {
        PHY_ScalarType vertexes_type, indexes_type;
        const unsigned char *vertexes;
        int num_vertexes;
        int vertexes_stride;
        const unsigned char *indexes;
        int num_faces;
        int face_stride;
        mesh->getLockedReadOnlyVertexIndexBase(&vertexes, num_vertexes, vertexes_type, vertexes_stride,
                                               &indexes, face_stride, num_faces, indexes_type);
        assert(vertexes_type == PHY_FLOAT);
        assert(indexes_type == PHY_INTEGER);
        const int *indexes2 = reinterpret_cast<const int *>(indexes + face_stride*face);
        int i1=indexes2[0], i2=indexes2[1], i3=indexes2[2];
        btVector3 v1 = *reinterpret_cast<const btVector3 *>(vertexes + vertexes_stride * i1);
        btVector3 v2 = *reinterpret_cast<const btVector3 *>(vertexes + vertexes_stride * i2);
        btVector3 v3 = *reinterpret_cast<const btVector3 *>(vertexes + vertexes_stride * i3);
        btVector3 r;
        r = (v2-v1).cross(v3-v1);
        r.normalize();
        return r;
}

// Attempts to fix it
extern ContactAddedCallback      gContactAddedCallback;
void customCallbackObj(btManifoldPoint& cp, const btCollisionObject* colObj, int partId, int index)
{
        (void) partId;
        const btCollisionShape *shape = colObj->getCollisionShape();
        if (shape->getShapeType() != TRIANGLE_SHAPE_PROXYTYPE) return;
        const btCollisionShape *parent = colObj->getRootCollisionShape();
        if (parent == NULL) return;
        if (parent->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) return;
        const btTriangleMeshShape *parent2 = static_cast<const btTriangleMeshShape*>(parent);
        const btStridingMeshInterface *mesh = parent2->getMeshInterface();
        btVector3 face_normal = get_face_normal(mesh,index);
        float dot = face_normal.dot(cp.m_normalWorldOnB);
        cp.m_normalWorldOnB = dot > 0 ? face_normal : -face_normal;
}
bool customCallback(btManifoldPoint& cp, const btCollisionObject* colObj0,int partId0,int index0,const btCollisionObject* colObj1,int partId1,int index1) 
{
        customCallbackObj(cp, colObj0, partId0, index0);
        customCallbackObj(cp, colObj1, partId1, index1);
        return true;
}
#endif

int main (void)
{
        #ifdef NORMAL_HACK
        gContactAddedCallback = customCallback;
        #endif

        /* STANDARD STUFF */

        btVector3 worldAabbMin(-10000,-10000,-10000);
        btVector3 worldAabbMax(10000,10000,10000);
        int maxProxies = 1024;
        btAxisSweep3* broadphase = new btAxisSweep3(worldAabbMin,worldAabbMax,maxProxies);

        btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
        btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

        btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

        btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);

        dynamicsWorld->setGravity(btVector3(0,-10,0));



        /* MAKE ME A TRIANGLE MESH WORTHY OF MORDOR */

        Vertexes *vertexes = new Vertexes();
        int sz = 11;
        vertexes->reserve(sz*sz);
        for (int z=-5 ; z<=5 ; z++) {
                for (int x=-5 ; x<=5 ; x++) {
                        // sz * sz grid of 10m squares
                        vertexes->push_back(btVector3(10*x, 0, 10*z));
                }
        }

        Faces *faces = new Faces();
        int num_faces = (sz-1) * (sz-1) * 2; // the 2 turns quads into triangles
        faces->reserve(num_faces);
        for (int z=0 ; z<sz-1 ; z++) {
                for (int x=0 ; x<sz-1 ; x++) {
                        int top_left = z*sz + x;       // (x,   z)
                        int top_rght = z*sz + x+1;     // (x+1, z)
                        int bot_left = (z+1)*sz + x;   // (x,   z+1)
                        int bot_rght = (z+1)*sz + x+1; // (x+1, z+1)
                        /*
                                +---------+
                                |\        |
                                |  \      |
                                |    \    |  Clockwise!
                                |      \  |
                                |        \|
                                +---------+
                        */
                        faces->push_back(Face(top_left,top_rght,bot_rght,0x0));
                        faces->push_back(Face(top_left,bot_rght,bot_left,0x0));
                }
        }

        btTriangleIndexVertexArray *v = new btTriangleIndexVertexArray(
                faces->size(), &((*faces)[0].v1), sizeof(Face),
                vertexes->size(), &((*vertexes)[0][0]), sizeof(btVector3));



        /* STANDARD STUFF */

        btCollisionShape* groundShape = new btBvhTriangleMeshShape(v,true,true);
        groundShape->setMargin(0);

        btCollisionShape* fallShape = new btBoxShape(btVector3(0.15,0.15,0.15));


        // put the ground at -0.5 so that the box centre will be at height 0 
        btDefaultMotionState* groundMotionState = new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(0,0,0)));
        btRigidBody::btRigidBodyConstructionInfo
                groundRigidBodyCI(0,groundMotionState,groundShape,btVector3(0,0,0));
        btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI);
        dynamicsWorld->addRigidBody(groundRigidBody);
        groundRigidBody->setFriction(0);


        btDefaultMotionState* fallMotionState =
                new btDefaultMotionState(btTransform(btQuaternion(0,0,0,1),btVector3(3,0.15,3)));
        btScalar mass = 20;
        btVector3 fallInertia(0,0,0);
        fallShape->calculateLocalInertia(mass,fallInertia);
        btRigidBody::btRigidBodyConstructionInfo fallRigidBodyCI(mass,fallMotionState,fallShape,fallInertia);
        btRigidBody* fallRigidBody = new btRigidBody(fallRigidBodyCI);
        fallRigidBody->setFriction(0);
        dynamicsWorld->addRigidBody(fallRigidBody);
        #ifdef NORMAL_HACK
        fallRigidBody->setCollisionFlags(fallRigidBody->getCollisionFlags()  | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK);
        #endif





        for (int i=0 ; i<500 ; i++) {
                dynamicsWorld->stepSimulation(1/60.f,10);

                if (i==60) {
                        /* GIVE IT A SHOVE */
                        fallRigidBody->applyCentralImpulse(btVector3(100,0,60));
                }

                btTransform trans;
                fallRigidBody->getMotionState()->getWorldTransform(trans);

                printf("%0.4f %0.4f\n", trans.getOrigin().getX(), trans.getOrigin().getY());
        }

        dynamicsWorld->removeRigidBody(fallRigidBody);
        delete fallRigidBody->getMotionState();
        delete fallRigidBody;

        dynamicsWorld->removeRigidBody(groundRigidBody);
        delete groundRigidBody->getMotionState();
        delete groundRigidBody;


        delete fallShape;

        delete groundShape;

        delete vertexes;
        delete faces;

        delete dynamicsWorld;
        delete solver;
        delete collisionConfiguration;
        delete dispatcher;
        delete broadphase;

        return 0;
}

// vim: shiftwidth=8:tabstop=8:expandtab

However when I tried to reproduce this in my game I ran into a separate set of problems. While the normal hack does fix this particular problem, it causes boxes to get jammed when they are fired at trimesh edges. Gimpact triangle meshes usually just fall straight through the floor. I tried a number of things including trying to preserve the direction (up/down) of the normal and its magnitude but I couldn't find anything that was stable enough to be a real fix for the original problem.

In conclusion, "correcting" the normals does work, but the side-effects make it unsuitable as a general solution.
User avatar
Erwin Coumans
Site Admin
Posts: 4221
Joined: Sun Jun 26, 2005 6:43 pm
Location: California, USA
Contact:

Re: Bumpy triangle meshes

Post by Erwin Coumans »

The small bump in the green line (using the normal correction) could be because the penetration depth is not corrected. For Bullet 2.74 we try to provide a sample that shows how to 'correct' the normal as well as the penetration depth. It doesn't serve a purpose to call it a hack ;-)
While the normal correction does fix this particular problem, it causes boxes to get jammed when they are fired at trimesh edges. Gimpact triangle meshes usually just fall straight through the floor.
Is this issue related to Gimpact, or does it also happen for static btBvhTriangleMeshShape? Moving triangle meshes with Gimpact are not recommended, please use btCompoundShape with convex hulls. Can you provide a reproduction case that shows the failing gimpact collision?
Thanks,
Erwin
sparkprime
Posts: 508
Joined: Fri May 30, 2008 2:51 am
Location: Ossining, New York
Contact:

Re: Bumpy triangle meshes

Post by sparkprime »

Erwin Coumans wrote: Is this issue related to Gimpact, or does it also happen for static btBvhTriangleMeshShape?
The floor was a static btBvhTriangleMeshShape, problems occured when firing a box shape at the floor and dropping a gimpact triangle mesh shape on the floor. I've been using gimpact triangle mesh shapes for my vehicle bodies,
Moving triangle meshes with Gimpact are not recommended, please use btCompoundShape with convex hulls.
I thought gimpact was designed for this... I can try Bullet's convex decomposition algorithm, so far I tried the gimpact decomposition shape and the result was extremely unstable. Gimpact triangle meshes have worked really well though until I tried playing with the contact normals.

Since boxes have problems with this normal correction, I doubt hulls will be any better, though.
Can you provide a reproduction case that shows the failing gimpact collision?
Thanks,
Erwin
It doesn't work for any shape that I tried, I suspect it has nothing to do with gimpact. Let's try fixing the primitive shapes and then see if the gimpact shapes start working. I can try extending the above program to fire the box at the floor and compare the behaviour with and without the normal "correction". I can also simplify it, having compared it to the other code in this thread, it's much easier to get the vertexes from the triangle subpart rather than the original mesh buffer.

I'd be happy with the current solution as long as we can get rid of these other issues it introduces.

However I'm really busy with work and phd thesis at the moment so I have to wait until I get a free evening.
Bbilz
Posts: 26
Joined: Wed Feb 27, 2008 9:55 am

Re: Bumpy triangle meshes

Post by Bbilz »

I noticed that in Alex's code above the triangle normals are never transformed into world space. I added the following to both of the normal fixing sections and it fixed loads of cases where my objects would fall through rotated trimesh objects - maybe this is the problem people are seeing with things falling through walls/floor depending on the order of the cross product..

Code: Select all

    if (colObj0->getCollisionShape()->getShapeType() == TRIANGLE_SHAPE_PROXYTYPE)
    {
        btTransform orient = colObj0->getWorldTransform();
        orient.setOrigin( btVector3(0.0f,0.0f,0.0f ) );

        btTriangleShape * s0 = ((btTriangleShape*)colObj0->getCollisionShape());
        cp.m_normalWorldOnB = (s0->m_vertices1[1] - s0->m_vertices1[0]).cross(s0->m_vertices1[2] - s0->m_vertices1[0]);
        cp.m_normalWorldOnB = orient * cp.m_normalWorldOnB;
        cp.m_normalWorldOnB.normalize();
    }
sparkprime
Posts: 508
Joined: Fri May 30, 2008 2:51 am
Location: Ossining, New York
Contact:

Re: Bumpy triangle meshes

Post by sparkprime »

Well spotted, the transform to world space does seem to work a lot better. My gimpact problems are gone now. I tried a variety of shapes and the only one that gets jammed in the floor is the box. I'm using motion clamping for the box and if I disable the motion clamping, the jamming goes away (they tunnel straight through the floor of course). Reducing the sphere-swept radius reduces the chance of a box being jammed but does not completely eliminate it.

Motion clamping never worked properly for boxes anyway.

I haven't tried correcting the penetration, I'm not sure how to do this. What is the correct value?
sparkprime
Posts: 508
Joined: Fri May 30, 2008 2:51 am
Location: Ossining, New York
Contact:

Re: Bumpy triangle meshes

Post by sparkprime »

This is what i'm currently using:

Code: Select all

void contact_added_callback_obj (btManifoldPoint& cp,
                                 const btCollisionObject* colObj,
                                 int partId, int index)
{
        (void) partId;
        (void) index;
        const btCollisionShape *shape = colObj->getCollisionShape();

        if (shape->getShapeType() != TRIANGLE_SHAPE_PROXYTYPE) return;
        const btTriangleShape *tshape =
               static_cast<const btTriangleShape*>(colObj->getCollisionShape());


        const btCollisionShape *parent = colObj->getRootCollisionShape();
        if (parent == NULL) return;
        if (parent->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) return;

        btTransform orient = colObj->getWorldTransform();
        orient.setOrigin( btVector3(0.0f,0.0f,0.0f ) );

        btVector3 v1 = tshape->m_vertices1[0];
        btVector3 v2 = tshape->m_vertices1[1];
        btVector3 v3 = tshape->m_vertices1[2];

        btVector3 normal = (v2-v1).cross(v3-v1);

        normal = orient * normal;
        normal.normalize();

        btScalar dot = normal.dot(cp.m_normalWorldOnB);
        btScalar magnitude = cp.m_normalWorldOnB.length();
        normal *= dot > 0 ? magnitude : -magnitude;

        cp.m_normalWorldOnB = normal;
}

bool contact_added_callback (btManifoldPoint& cp,
                             const btCollisionObject* colObj0,
                             int partId0, int index0,
                             const btCollisionObject* colObj1,
                             int partId1, int index1)
{
        contact_added_callback_obj(cp, colObj0, partId0, index0);
        contact_added_callback_obj(cp, colObj1, partId1, index1);
        //std::cout << to_ogre(cp.m_normalWorldOnB) << std::endl;
        return true;
}
pico
Posts: 229
Joined: Sun Sep 30, 2007 7:58 am

Re: Bumpy triangle meshes

Post by pico »

Hi,

i tested the above code also and it works very nice. Wrong triangle edge hits seem to be gone with it.

Thanks for posting.
AlexSilverman
Posts: 141
Joined: Mon Jul 02, 2007 5:12 pm

Re: Bumpy triangle meshes

Post by AlexSilverman »

Ah. Just to be sure, the normal needs to be oriented because the verts of the triangle are in local space, so they don't change as the orinetation of the mesh does, correct? Without this step, the normal would always be returned as though the orientation of the mesh were 0 degrees on all 3 axes? This was never a problem I ran into because our static geometry was always given to us exactly where it needed to be so we never changed the tranform for it, but this makes a lot of sense (assuming I'm correct in what I said above).

- Alex
pico
Posts: 229
Joined: Sun Sep 30, 2007 7:58 am

Re: Bumpy triangle meshes

Post by pico »

Hi Alex & Sparkprime,

i tested the last callback in our game and found a big problem.

When you collide with an outer edge of a mesh the edge will have the normal of the face and so you will have a wrong collision. Just imagine a triangle and you hit it from the side. You will intersect as the edge won't hold you back anymore.
User avatar
Erwin Coumans
Site Admin
Posts: 4221
Joined: Sun Jun 26, 2005 6:43 pm
Location: California, USA
Contact:

Re: Bumpy triangle meshes

Post by Erwin Coumans »

pico wrote: When you collide with an outer edge of a mesh the edge will have the normal of the face and so you will have a wrong collision. Just imagine a triangle and you hit it from the side. You will intersect as the edge won't hold you back anymore.
You can modify the original mesh, so that additional triangles are added to pad the outer triangles. Or modify the callback logic, to exclude those triangles from changing the contact normal (for example based on triangle index/part index).

Hope this helps,
Erwin
Post Reply