schteppe / cannon.js

A lightweight 3D physics engine written in JavaScript.
http://schteppe.github.com/cannon.js
MIT License
4.63k stars 700 forks source link

Threejs Mesh to Cannonjs ConvexPolyhedron #109

Open bordeck opened 10 years ago

bordeck commented 10 years ago

Hello!

Thank you for this awesome library!!!

I'm working on a mesh fracturation on threejs meshs and i want to include your library into my project and especially to the fractured meshs. My current problem is that i don't find the solution to convert a convex mesh from threejs to the cannonjs. I'm a little bit lost with ConvexPolyhedron parameters. I have already convert the vertices and i can see the fractured mesh moving on the ground but i need to use a collisionFilterMask (to prevent collision) on them because the cannon bodies haven't faces. As i'm not skilled in 3d, I have tried to implement the solution you have wrote for the bunny but i have lots of warnings like "looks like it points into the shape? The vertices follow. Make sure they are ordered CCW around the normal, using the right hand rule." and i don't know how to fix this.

I hope it's the right place to ask you a solution and thanks in advance for your answer.

lazd commented 10 years ago

Same here, I tried doing the conversion from the bunny example, but I get all the same errors as @bordeck:

Vertex 553: (-4591.5,-4447.42,-113.096) cannon.js:2365
Face normal 69 (0.25750913918348467,0.08884519204742058,-0.9621827139826602) looks like it points into the shape? The vertices follow. Make sure they are ordered CCW around the normal, using the right hand rule. 

I'm trying to create a ConvexPolyhedron to represent this model: https://github.com/satellite-game/Satellite-Mobile/blob/master/client/game/models/phobos_hifi.json

bryce-evans commented 9 years ago

Any update on this? It's been 19 months and this still seems to be an issue. I'm able to copy over Threejs geometry to form a convex hull but the hull does not act properly with collisions and I get the same errors as above. I added a wireframe rendering of the hull and I am confident that the vertices and faces in the hull are correct (also working with a simple cube just for debugging). Is this a bug or is more info (such as normals etc) needed for the constructor? There's minimal documentation on how to manage this.

schteppe commented 9 years ago

Don't think there's any updates to the convex class that would help. However, I could give a few pointers.

  1. The input mesh to the ConvexPolyhedron needs to be convex.
  2. The winding of the faces needs to be counter clockwise around the normal. The resulting normal needs to point away from the center of the convex.
  3. The number of faces should be few to get performance. In box2d you can add max 8 vertices in a convex shape. In 3D there would be more, but if you compare to the Box, you have 6 faces (8 verts).
  4. Consider using other primitives instead, like a compound of spheres. This is the most performant choice, and you will probably end up in doing this in the end. If you are using Three.js, consider using the three.js debug renderer.
  5. If your mesh is a mesh with triangles, you might want to use the Trimesh class instead. However, only planes, spheres and rays can collide with it for now.
  6. There's a bug in convex/convex collisions that might appear for some meshes. Here be dragons.
Rafapp commented 11 months ago

Greetings everyone.

Convex polyhedron never behaved correctly for me with more complex shapes. To fix this, I created a Python script with blender that parses the boxes in a collection, which you should name "Colliders," then it will print out json format text in the console, and using that json file I parse it and generate the proper box colliders in my game.

Here is the blender python script:

import bpy
import math

collection = bpy.data.collections.get("Colliders")

# Get all objects in the scene
all_objects = collection.objects

# Count meshes for formatting
meshCount = 0
for obj in all_objects:
    if obj.type == 'MESH':
        meshCount += 1

# Print mesh information using ID counter
objectID = 0

print("{")

for obj in all_objects:
    if obj.type == 'MESH':
        print("    \"box_" + str(objectID) + "\":{")

        # Position
        position = obj.location
        print("        \"position\":{")
        print("            \"x\":" + str(position.x) + ",")
        print("            \"y\":" + str(position.y) + ",")
        print("            \"z\":" + str(position.z))
        print("         },")

        # Scale
        scale = obj.scale
        print("        \"scale\":{")
        print("            \"x\":" + str(scale.x) + ",")
        print("            \"y\":" + str(scale.y) + ",")
        print("            \"z\":" + str(scale.z))
        print("         },")

        # Rotation (Euler)
        rotation = obj.rotation_euler
        print("        \"rotation\":{")
        print("            \"x\":" + str(math.degrees(rotation.x)) + ",")
        print("            \"y\":" + str(math.degrees(rotation.y)) + ",")
        print("            \"z\":" + str(math.degrees(rotation.z)))
        print("         }")
        if(objectID != meshCount - 1):
            print("    },\n")
        else:
            print("    }")

        objectID += 1

print("}")

That will generate the json text. Copy that from your console, paste it in a .json file, and parse it like so in a js file with cannon:

/*
 * Creating the level collision geometry
 */
async function GenerateLevelColliders(){

    // RigidBody
    levelRigidbody = new CANNON.Body({
        type: CANNON.Body.STATIC
    });

    // Parse the JSON file
    await fetch("levelColliders/level_" + currentLevel + ".json")
    .then(response => response.json())
    .then(data => {
        let boxID = 0;

        for (let boxKey in data) {
            if(data.hasOwnProperty(boxKey)){
                const box = data[boxKey];
                const position = box.position;
                const scale = box.scale;
                const rotation = box.rotation;

                let boxShape = new CANNON.Box(new CANNON.Vec3(scale.x, scale.y, scale.z));

                // Set the last box as the "checkpoint"
                if(boxID == Object.keys(data).length - 1){
                    // Create a specific body for final shape
                    levelEndRigidbody = new CANNON.Body({
                        type: CANNON.Body.STATIC
                    });

                    // Add its box
                    levelEndRigidbody.addShape(
                        boxShape,
                        new CANNON.Vec3(position.x , position.z, -position.y), // ~ Y is up in blender
                        EulerToQuaternion(rotation.x + 90, rotation.z, -rotation.y)
                    );

                } else {
                    // Add each other box given position, scale and rotation
                    levelRigidbody.addShape(
                        boxShape,
                        new CANNON.Vec3(position.x , position.z, -position.y), // ~ Y is up in blender
                        EulerToQuaternion(rotation.x + 90, rotation.z, -rotation.y)
                    );
                }
                boxID += 1;
            }
        }   
    })
    .catch(error => {
        console.error('Error parsing JSON file:', error);
    });

    // Add the bodies
    PhysicsWorld.addBody(levelRigidbody);
    PhysicsWorld.addBody(levelEndRigidbody);

    console.log("Finished loading: levelColliders/level_" + currentLevel + ".json ☑");
}

Keep in mind you must:

Hope this is helpful for game devs building larger levels with a bunch of colliders. The only downside to this is your colliders will use only boxes as primitives, but I'm sure this pipeline can be applied to spheres, and even tris.