Mesh to Mesh minimum distance

Post Reply
BlackMirror
Posts: 2
Joined: Tue Feb 25, 2020 11:27 am

Mesh to Mesh minimum distance

Post by BlackMirror » Tue Feb 25, 2020 11:40 am

Hello Bullet people out there,

so I'm quite new to Bullet but really like what you created here.
I tried to import two .stl files and get the minimum distance between these two meshes.

The problem is, the position of the mesh. So while the shape has its absolut positions, once I apply the shape to a rigid body I have to give it a motionState and therefore a transform with an origin. As a result, the two bodies are on the same position and not as they have been placed in the absolut stl coordinates (or am I wrong?) . Is there maybe another solution to get the closesed distance of two meshes?

Also I did not implement the debug drawer yet, maybe someone can point me in the right direction where to find an easy to copy example? (Wiki is not working anymore :()

My code:

Code: Select all

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include "btBulletDynamicsCommon.h"

struct MySTLTriangle
{
  float normal[3];
  float vertex0[3];
  float vertex1[3];
  float vertex2[3];
};

struct MyContactResultCallback : public btCollisionWorld::ContactResultCallback
{
  bool m_connected;
  btScalar m_margin;
  MyContactResultCallback() : m_connected(false), m_margin(0.05)
  {
  }
  virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1)
  {
      std::cout << "Distance: " << cp.getDistance() << std::endl;
      if (cp.getDistance() <= m_margin)
          m_connected = true;
      return 1.f;
  }
};

int main() {
    std::cout << "Hello, World!" << std::endl;

    ///collision configuration contains default setup for memory, collision setup. Advanced users can create their own configuration.
    btDefaultCollisionConfiguration *collisionConfiguration = new btDefaultCollisionConfiguration();

    ///use the default collision dispatcher. For parallel processing you can use a diffent dispatcher (see Extras/BulletMultiThreaded)
    btCollisionDispatcher *dispatcher = new btCollisionDispatcher(collisionConfiguration);

    ///btDbvtBroadphase is a good general purpose broadphase. You can also try out btAxis3Sweep.
    btBroadphaseInterface *overlappingPairCache = new btDbvtBroadphase();

    ///the default constraint solver. For parallel processing you can use a different solver (see Extras/BulletMultiThreaded)
    btSequentialImpulseConstraintSolver *solver = new btSequentialImpulseConstraintSolver;

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

    ///-----initialization_end-----

    //keep track of the shapes, we release memory at exit.
    //make sure to re-use collision shapes among rigid bodies whenever possible!
    btAlignedObjectArray<btCollisionShape *> collisionShapes;

    ///create a few basic rigid bodies
    btCollisionObject motor;
    btTriangleMesh tetraMesh;
    //read all vertices of the triangles from the stl file
    std::ifstream stlInput ("motor.stl");
    std::string line;
    while(std::getline(stlInput, line)){
        std::istringstream iss(line);
        float vertex1, vertex2, vertex3;
        std::string tag;
        if (!(iss >> tag >> vertex1 >> vertex2 >> vertex3) || tag != "vertex") {continue;}
        auto vec1 = btVector3(vertex1, vertex2, vertex3);

        std::getline(stlInput, line);
        iss.str(line);
        if (!(iss >> tag >> vertex1 >> vertex2 >> vertex3) || tag != "vertex") {continue;}
        auto vec2 = btVector3(vertex1, vertex2, vertex3);

        std::getline(stlInput, line);
        iss.str(line);
        if (!(iss >> tag >> vertex1 >> vertex2 >> vertex3) || tag != "vertex") {continue;}
        auto vec3 = btVector3(vertex1, vertex2, vertex3);

        tetraMesh.addTriangle(vec1, vec2, vec3);
    }

    btConvexTriangleMeshShape *tetraShape = new btConvexTriangleMeshShape(&tetraMesh, false);

    collisionShapes.push_back(tetraShape);
    motor.setCollisionShape(tetraShape);

    btVector3 localInertia(0, 0, 0);
    btTransform trans;
    trans.setIdentity();

    motor.setWorldTransform(trans);

    btDefaultMotionState *motionState = new btDefaultMotionState(trans);
    btRigidBody *body = new btRigidBody(1, motionState, tetraShape, localInertia);

    dynamicsWorld->addRigidBody(body);

    /// --------------------------
    btCollisionObject screw;
    btTriangleMesh tetraScrew;
    //read all vertices of the triangles from the stl file
    std::ifstream stlInputScrew ("screw1.stl");
    std::string lineScrew;
    while(std::getline(stlInputScrew, lineScrew)){
        std::istringstream iss(lineScrew);
        float vertex1, vertex2, vertex3;
        std::string tag;
        if (!(iss >> tag >> vertex1 >> vertex2 >> vertex3) || tag != "vertex") {continue;}
        auto vec1 = btVector3(vertex1, vertex2, vertex3);

        std::getline(stlInput, lineScrew);
        iss.str(lineScrew);
        if (!(iss >> tag >> vertex1 >> vertex2 >> vertex3) || tag != "vertex") {continue;}
        auto vec2 = btVector3(vertex1, vertex2, vertex3);

        std::getline(stlInput, lineScrew);
        iss.str(lineScrew);
        if (!(iss >> tag >> vertex1 >> vertex2 >> vertex3) || tag != "vertex") {continue;}
        auto vec3 = btVector3(vertex1, vertex2, vertex3);

        tetraScrew.addTriangle(vec1, vec2, vec3);
    }

    btConvexTriangleMeshShape *tetraShapeScrew = new btConvexTriangleMeshShape(&tetraScrew, false);

    collisionShapes.push_back(tetraShapeScrew);
    screw.setCollisionShape(tetraShapeScrew);

    btVector3 localInertiaScrew(0, 0, 0);
    btTransform transScrew;
    transScrew.setIdentity();

    screw.setWorldTransform(transScrew);

    btDefaultMotionState *motionStateScrew = new btDefaultMotionState(transScrew);
    btRigidBody *bodyScrew = new btRigidBody(1, motionStateScrew, tetraShapeScrew, localInertiaScrew);

    dynamicsWorld->addRigidBody(bodyScrew);


    MyContactResultCallback result;
    dynamicsWorld->contactPairTest(&motor, &screw, result);
    std::string connected = "False";
    if(result.m_connected) connected = "True";
    std::cout << "Connected? " << connected << std::endl;
    return 0;

}

User avatar
drleviathan
Posts: 597
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Mesh to Mesh minimum distance

Post by drleviathan » Tue Feb 25, 2020 4:31 pm

A couple things:

(1) Using a MotionState here is not required for your case. The MotionState is a "bridge" object which provides a simple API for communicating changes in the body's transform to the rendered "GameObject" (in the case of dynamic objects) an visa-versa (in the case of kinematic objects). In other words, in your case you can just slam the body transforms to where you want them. No need to rely on the MotionState...

except when I look at your code I see you happen to be using the one MotionState feature which does help you: when you give a MotionState to the body before you add it to the world... then on add the world will call MotionState::setWorldTransform() once (as if the object were kinematic) and in the case of DefaultMotionState this will place the body where you want it...

except it appears you supply the identity transform for both trans and transScrew. What you want to do is compute the true world-frame transforms of those two objects.

(2) I think maybe the btDiscreteDynamicsWorld::contactPairTest() doesn't work between two btConvexTriangleMeshShape. I'm not 100% certain about this so you should try it and see but my understanding is that is why btConvexTriangleMeshShape objects cannot be set as dynamic (e.g. they must be static, but they can sometimes work (with some failure modes) as kinematic): the mesh-vs-mesh CollisionAlgorithm is not yet implemented in Bullet. In order to achieve what you want you may need to make at least one of those shapes a convex shape, or a btCompoundShape with convex parts.

User avatar
drleviathan
Posts: 597
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Mesh to Mesh minimum distance

Post by drleviathan » Tue Feb 25, 2020 4:40 pm

Sorry I forgot to mention:

(3) Supposedly the btGImpactShape stuff does supply a mesh-vs-mesh CollisionAlgorithm, but I have not used those shapes yet, so I can't testify with authority. You could try using those shapes instead.

BlackMirror
Posts: 2
Joined: Tue Feb 25, 2020 11:27 am

Re: Mesh to Mesh minimum distance

Post by BlackMirror » Tue Feb 25, 2020 5:13 pm

So I get a collision using the btConvexTriangleMeshShapes:

with:

Code: Select all

trans.setIdentity(); 
trans.setOrigin(btVector3(0, 0, 0));

transScrew.setIdentiy();
transScrew.setOrigin(btVector3(0, 0, 0));

Distance: -0.0650233
Connected? True
once I slightly differ the origin to

Code: Select all

transScrew.setOrigin(btVector3(0, 0.1, 0));

Distance: 0.0349767
Connected? True
so I was guessing the collision works fine, just that both of my collision shapes are must likly on the same place. So if I get you right I have to compute the origin of the mesh by myself (out of the stl file) and place my collision object there? I thougth maybe by just adding the vertices in its absolut coordinates the collisionObject will be already in its place.

User avatar
drleviathan
Posts: 597
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: Mesh to Mesh minimum distance

Post by drleviathan » Tue Feb 25, 2020 9:00 pm

If all of your mesh data for each object's shape is in the world-frame, and you set each object's transform to be identity, then I would expect them to collide correctly. I don't see a problem with that.

I only now realize: you're using btConvexTriangleMeshShape which really just a btConvexHull around a bunch of triangles and is not the btBvhTriangleMeshShape which can be concave and suffers the "no mutual CollisionAlgorithm" problem. As such, I would expect your contactPairTest() to Just Work... assuming the two actually do overlap in the broadphase. That is, if the broadphase proxies don't actually overlap then I'd expect the contact test to early-exit with no results quite quickly.

If that isn't your problem then I'm out of ideas.

Post Reply