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

ConvexPolyhedron collision issues #459

Open AshDevFr opened 3 years ago

AshDevFr commented 3 years ago

Hi, I have been trying to use CANNON.ConvexPolyhedron to handle collision between different type of objects.

When they collide with the plane or other type of shapes there is no issue but when two ConvexPolyhedron collide I get an error:

cannon.js:5085 Uncaught TypeError: Cannot read property 'x' of undefined
    at Vec3.copy (cannon.js:5085)
    at ConvexPolyhedron.clipFaceAgainstHull (cannon.js:8344)
    at ConvexPolyhedron.clipAgainstHull (cannon.js:8041)
    at Narrowphase.<computed>.Narrowphase.convexConvex (cannon.js:12266)
    at Narrowphase.getContacts (cannon.js:11256)
    at World.internalStep (cannon.js:13359)
    at World.step (cannon.js:13197)
    at animate (index.js:29)

This is how I create my shape:

const geo1 = new THREE.IcosahedronGeometry(2, 0);
const geo1Mesh = new THREE.Mesh(geo1, objectMaterial);
geo1Mesh.position.x = -15;
geo1Mesh.position.z = 0;
geo1Mesh.position.y = 3;
scene.add(geo1Mesh);

const geo1Body = new CANNON.Body({ mass: 10, material: objectBodyMaterial });
geo1Body.position.copy(geo1Mesh.position);
geo1Body.addShape(getPolyhedronShape(geo1Mesh));
world.addBody(geo1Body);

function getPolyhedronShape(mesh) {
  const position = mesh.geometry.attributes.position.array;
  const points = [];
  for (let i = 0; i < position.length; i += 3) {
    points.push(new CANNON.Vec3(position[i], position[i + 1], position[i + 2]));
  }
  const faces = [];
  for (let i = 0; i < position.length / 3; i += 3) {
    faces.push([i, i + 1, i + 2]);
  }

  return new CANNON.ConvexPolyhedron(points, faces);
}

I am using Three.js version 0.126.1 and Cannon.js version 0.6.2

I created a sandbox if anybody wants to take a look: https://codesandbox.io/s/hardcore-glitter-wxscw?file=/src/index.js

Am I missing something ?

Thank you for your time.

Dannie226 commented 3 years ago

I have found that if you use an indexed geometry, and then loop through points and faces, convex on convex works pretty well most of the time. If you need to merge vertices, I would recommend BufferGeometryUtils or if you are using an import statement this will work. code for creating convex from indexed geometry:

function createFromIndexed(mesh){
  let geometry = mesh.geometry;
  geometry.deleteAttribute('normal');  
  //if not planning on putting textures on the mesh, you can delete the uv mapping for better vertice merging  
  //geometry.deleteAttribute('uv');  
  geometry = THREE.BufferGeometryUtils.mergeVertices(geometry); 
  //if using import statement  
  //geometry = BufferGeometryUtils.mergeVertices(geometry);  
  mesh.geometry = geometry;
  let position = geometry.attributes.position.array;  
  let geomFaces = geometry.index.array;   
  const points = [];  
  const faces = [];  
  for(var i = 0;i<position.length;i+=3){  
    points.push(new CANNON.Vec3(position[i],position[i+1],position[i+2]);  
  }  
  for(var i = 0;i<geomFaces.length;i+=3){  
    faces.push([geomFaces[i],geomFaces[i+1],geomFaces[i+2]);  
  }  
  return new CANNON.ConvexPolyhedron(points,faces);  
}
Day-OS commented 2 years ago

Has this been solved yet? I'm getting the same problem.

Dannie226 commented 2 years ago

Because of the way that cannon.js is programmed, if the convex triangles are not all connected, then convex-convex collision will not work. Which is why you can use either buffer geometry utils to merge the vertices, or make your own geometry that is indexed. unfortunately, you have to do one of the two for convex-convex collision, the answer stated above, does the one where you merge the vertices with buffer geometry utils.

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.