Search code examples
javaopengllwjglbulletphysicsjbullet

LWJGL Mesh to JBullet collider


I'm working on creating a voxel engine in LWJGL 3, I have all the basics down (chunks, mesh rendering, etc).

Now I'm working on adding physics using JBullet. This is my first time using JBullet directly, but I've used Bullet before in other 3D engines.

From here I gathered that all I needed to do to create a collision object the same shape as my mesh was the plug the vertices and indices into a TriangleIndexVertexArray and use that for a BvhTriangleMeshShape.

Here is my code:

    float[] coords = mesh.getVertices();
    int[] indices = mesh.getIndices();

    if (indices.length > 0) {
        IndexedMesh indexedMesh = new IndexedMesh();
        indexedMesh.numTriangles = indices.length / 3;
        indexedMesh.triangleIndexBase = ByteBuffer.allocateDirect(indices.length*Float.BYTES).order(ByteOrder.nativeOrder());
        indexedMesh.triangleIndexBase.asIntBuffer().put(indices);
        indexedMesh.triangleIndexStride = 3 * Float.BYTES;
        indexedMesh.numVertices = coords.length / 3;
        indexedMesh.vertexBase = ByteBuffer.allocateDirect(coords.length*Float.BYTES).order(ByteOrder.nativeOrder());
        indexedMesh.vertexBase.asFloatBuffer().put(coords);
        indexedMesh.vertexStride = 3 * Float.BYTES;

        TriangleIndexVertexArray vertArray = new TriangleIndexVertexArray();
        vertArray.addIndexedMesh(indexedMesh);

        boolean useQuantizedAabbCompression = false;
        BvhTriangleMeshShape meshShape = new BvhTriangleMeshShape(vertArray, useQuantizedAabbCompression);

        CollisionShape collisionShape = meshShape;

        CollisionObject colObject = new CollisionObject();
        colObject.setCollisionShape(collisionShape);
        colObject.setWorldTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(position.x, position.y, position.z), 1f)));
        dynamicsWorld.addCollisionObject(colObject);

    } else {
        System.err.println("Failed to extract geometry from model. ");
    }

I know that the vertices and indices are valid as I'm getting them here after drawing my mesh.

This seems to somewhat work, but when I try to drop a cube rigidbody onto the terrain, it seems to collide way above the terrain! (I know that the cube is setup correctly because if I remove the mesh collider it hits the base ground plane at y=0).

enter image description here

I thought maybe it was a scaling issue (although I don't see how that could be), so I tried changing:

colObject.setWorldTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(position.x, position.y, position.z), 1f))); to:

colObject.setWorldTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(position.x, position.y, position.z), 0.5f)));

But after changing the scale from 1 it acted like the mesh collider didn't exist.

It's hard to find any resources or code for JBullet surrounding mesh collision, and I've been working on this for almost 2 days, so I'm hoping maybe some of you people who have done it before can help me out :)

Update 1:

I created an implementation of the IDebugDrawer so I could draw the debug infomation in the scene.

To test it I ran it with just a basic ground plane and a falling cube. I noticed that when the cube is falling the aabb matches the cube size, but when it hits the floor the aabb becomes significantly larger then it was.

enter image description here

I'm going to assue that this is normal Bullet behavior due to collition bouncing, and look at that later as it doesn't effect my current problem.

I re-enabled the generation of the colliders from the chunk meshs, and saw this:

enter image description here

It appears that the aabb visualization of the chunk is a lot higher then the actual chunk (I know my y positioning of the overall collision object is correct).

enter image description here

I'm going to try to figure out if I can draw the actual collision mesh or not.

Update 2:

As far as I can see looking at the source, the meshof the colliders should be drawing in debug, so I'm not sure why it isn't.

I tried changing the Box rigidbody to a sphere, and it actually rolled across the top of the visualized aabb for the terrain collider. It just rolled flat though and didn't go hit or down where there where hills or dips in the terrain where, so it was obviously just rolling across the flat top of the aabb.


Solution

  • So after adding in the Debug Drawer, I was confused as to why the aabb was x2 larger then it should have been.

    After spending hours trying little adjustments, I noticed something odd - there was a 0.25 gap between the collider and the edge of the chunk. I proceeded to zoom out and surprisingly noticed this:

    enter image description here

    There is an extera row and column of colliders? No that doesn't make sense, there should be 5x5 colliders to match the 5x5 chunks.

    Then I counted blocks and realized that the colliders where spanning 64 blocks (my chunks are 32x32!).

    I quickly realized that this was a scaling issue, and after adding

    BvhTriangleMeshShape meshShape = new BvhTriangleMeshShape(vertArray, useQuantizedAabbCompression);
    meshShape.setLocalScaling(new Vector3f(0.5f, 0.5f, 0.5f));
    

    To scale the colliders down by half, everything fit and worked! My "sphere" rolled and came to a stop where there was a hill in the terrain like it should.

    enter image description here

    My full code for coverting an LWJGL mesh to a JBullet mesh collder is:

    public void addMesh(org.joml.Vector3f position, Mesh mesh){
        float[] coords = mesh.getVertices();
        int[] indices = mesh.getIndices();
    
        if (indices.length > 0) {
    
            IndexedMesh indexedMesh = new IndexedMesh();
            indexedMesh.numTriangles = indices.length / 3;
            indexedMesh.triangleIndexBase = ByteBuffer.allocateDirect(indices.length*Integer.BYTES).order(ByteOrder.nativeOrder());
            indexedMesh.triangleIndexBase.rewind();
            indexedMesh.triangleIndexBase.asIntBuffer().put(indices);
            indexedMesh.triangleIndexStride = 3 * Integer.BYTES;
            indexedMesh.numVertices = coords.length / 3;
            indexedMesh.vertexBase = ByteBuffer.allocateDirect(coords.length*Float.BYTES).order(ByteOrder.nativeOrder());
            indexedMesh.vertexBase.rewind();
            indexedMesh.vertexBase.asFloatBuffer().put(coords);
            indexedMesh.vertexStride = 3 * Float.BYTES;
    
            TriangleIndexVertexArray vertArray = new TriangleIndexVertexArray();
            vertArray.addIndexedMesh(indexedMesh);
    
            boolean useQuantizedAabbCompression = false;
            BvhTriangleMeshShape meshShape = new BvhTriangleMeshShape(vertArray, useQuantizedAabbCompression);
            meshShape.setLocalScaling(new Vector3f(0.5f, 0.5f, 0.5f));
    
            CollisionShape collisionShape = meshShape;
    
            CollisionObject colObject = new CollisionObject();
            colObject.setCollisionShape(collisionShape);
            colObject.setWorldTransform(new Transform(new Matrix4f(new Quat4f(0, 0, 0, 1), new Vector3f(position.x, position.y, position.z), 1f)));
            dynamicsWorld.addCollisionObject(colObject);
    
        } else {
            System.err.println("Failed to extract geometry from model. ");
        }
    
    }
    

    Update 1:

    Even though the scaling was the fix for said prolem, it caused me to look deeper and realize that I mistakenly was using to block size (0.5f) for the mesh scaling factor in my mesh view matrix. Changing the scale to 1 like it should be fixed it.