pmndrs / cannon-es

💣 A lightweight 3D physics engine written in JavaScript.
https://pmndrs.github.io/cannon-es/
MIT License
1.79k stars 133 forks source link

Collisions between multiple convex polyhedron #73

Closed rrelmy closed 3 years ago

rrelmy commented 3 years ago

We tried to get collisions between different convex polyhedrons based on simplified models loaded as GLB.

The collisions are very unreliable but happen sometimes, most of the time objects just fall through each other. Against planes and boxes the models behave as expected but not on each other.

I am not sure if the problem is with the models or the implementation. Is there some documentation about requirements to make such things possible?

Demo (red objects are fixed): https://eww7v.csb.app/

marcofugaro commented 3 years ago

@rrelmy you have to make the spoon body thicker.

Physics engines work on something called Discrete collision detection. Here's Box2D creator explaining it if you want to learn more:https://youtu.be/7_nKOET6zwI?t=211

rrelmy commented 3 years ago

@marcofugaro Thank you very much for the hint.

Rafapp commented 1 year 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.