Convex Polyhedron normals going inwards and bad collision with box nor plane

Hi,
the past days i’ve been working on getting the convex polyhedron collider working. Now everything is set in shape, position and rotation, verified with the cannon debugger.

The cube does collide, but enters the body, sticks in it, and slowly gets pushed out. I am also getting errors such as “.faceNormals[120] = Vec3(0.8228529875619893,-0.0739619522514679,-0.5634204384644383) looks like it points into the shape? The vertices follow. Make sure they are ordered CCW around the normal, using the right hand rule.”

From all the sources i was able to find I assembled the code below:

let meshgeometry = new ConvexGeometry( vertex_positions);
material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
mesh = new THREE.Mesh( meshgeometry, material );

 let geometry = new THREE.BufferGeometry();
 geometry.setAttribute('position', mesh.geometry.getAttribute('position'));
 geometry.setAttribute('normal', mesh.geometry.getAttribute('normal'));
 geometry = BufferGeometryUtils.mergeVertices(geometry, 1e-1);

 for(var i = 0; i < position.length; i += 3) {
     points.push(new CANNON.Vec3(position[i] - com.x, position[i + 1] - com.y, position[i + 2] - com.z));
 }

 for(var i = 0; i < geomFaces.length; i += 3)
     faces.push([geomFaces[i], geomFaces[i+1], geomFaces[i+2]]);

let shape = new CANNON.ConvexPolyhedron({vertices: points, faces:faces})
const physicsBody = new CANNON.Body({ mass: 2, shape: shape ,
                                                                      collisionFilterGroup: 2,
                                                                      collisionFilterMask: 1 })

return physicsBody 

The cube to collide is as follows

const cubeBody = new CANNON.Body({ mass: mass,
                                        shape: cubeShape,
                                        collisionFilterGroup: 1,
                                        collisionFilterMask: 1|2})

Is it something with coplanar faces like described here?
https://github.com/schteppe/cannon.js/issues/164
But merging vertices should fix this, or how else to do it? Also this issue is from 2014…

Hope this can be helped!

A convexPolyhedron is best for convex hulls. A convex hull has no corner pointing inwards. It is the smallest polygon that encloses all the points in the set.
If you want inwards facing faces, use a trimesh.
The torusKnot in this demo uses a trimesh : Physics with Cannon - Three.js Tutorials
Unfortunately though, cannon trimeshes can only collide with spheres and planes. Thats why it falls and lands on the floor surface which is a plane

Here is another trimesh example : useTrimesh & Contact Materials - React Three Fiber Tutorials

Hi Sean,

Thanks for the swift reply. Unfortunately I specifically chose the convex polyhedron hull for its ability to collide with other convex polyhedrons. My mesh was originally concave as a gltf import, transformed via vertex_positions to a convexGeometry from threejs.
I do not intend to have any inwards facing faces. I solved this by splitting my mesh into several convex shapes.

Cool, in that case if you ever want better performance, experiment with a compound shape of spheres. It is much faster that using convexpolyhedrons. You position spheres of different sizes in different places to roughly estimate the shape. It’s not perfect, but much faster.
The working demo in this page is using a compound shape of spheres : Trimeshes, ConvexPolyhedrons and Compound Shapes - Three.js Tutorials

1 Like

Thanks, the performance gain seems tremendous. Sphere colliders are noted, but for now convex hulls are the go to.
I’ve actually been to your page several times for reference. Truly great work!
Though for convexPolyhedrons you appear to be using cannonUtils, which i couldnt figure out how to install.

That cannonUtils is something I wrote that I reference straight from my web server.
https://sbcode.net/extra_html/utils/cannonUtils.js
It is the JavaScript version of the TypeScript version that I use in my TypeScript course.

I also have a GitHub for it : GitHub - Sean-Bradley/CannonUtils
there is a CDN link also : https://cdn.jsdelivr.net/gh/Sean-Bradley/CannonUtils@main/cannonUtils.js

Anyway, the functions themselves are so tiny, you can just copy/paste them into your own project

I checked out your implementation. It seems to do the same thing i do to obtain the convexPolyhedron, whereas you merge vertices yourself.
I think the issue might come from the ConvexGeometry function. There should not be any normals facing inwards given a geometry made with this, no?

My function doesn’t promise to fix inwards facing faces. Sorry if you misunderstood.

No worries, i am thinking that THREE.ConvexGeometry should take care of that. I’ve used your function now and the collision seems to be working now. I will check further in detail later.
The cause for the issue was that my code did not include defining normals ahead in time.

However your code gives me even more inward facing faces warnings/errors. Any idea what could cause this, or could i safely ignore these?

I don’t have your problems, Sorry.

Great, thanks.

In the past I’ve experienced two situations when Cannon complains about inward pointing normals:

  • there are really faces with wrong winding order
  • all faces are correct, but the origin of the convex polyhedron (0,0,0) is outside the polyhedron itself.

The second situation is caused by the way Cannon checks orientation of faces (by calculating the angle between the normal vector and the vector from (0,0,0) to a vertex of the face).

Maybe there are other problematic situations, but at least make sure that polyhedron’s (0,0,0) is inside the convex shape.

1 Like

Hi Pavel,

  • Reversing the order swaps the faces for which these errors occur, so bad faces are now good and good faces became bad. This considerably increases the number of errors.

  • Centering the vertices by subtracting the mean reduced the number of errors by half. :slightly_smiling_face: Thanks

Given that the normals are given by me, and Cannon “checks” the normals, does that mean that it may just be a false alarm and things will keep going as they should, or will cannon take action to ignore those normals? (Basically, have you had these errors but the collision still worked flawlessly?)

If there are wrong faces (i.e. with wrong winding order of vertices!), you should reverse the order only on these faces, not all faces. If you do it on all faces, then wrong faces will become correct, but correct faces will become wrong.

If all faces have the correct winding order, and you still get the error with the normal, then look for (0,0,0).

If Cannon is not happy with a normal, it may lead to wrong calculation of collision (either false positive, or false negative), depending on which side of the body collides.

Edit: I do not remember whether Cannon cares about externally provided normals, or it always relies on its own calculation of normals.

Damn ok, I will fix it then. Is there a programmatic way to figure out which of the faces are wrong by accessing a list or so? It’s several hundred faces on several meshes, so doing this manually will take quite a while.

Calculate the normal from the first three vertices. Then find the dot product of this normal with the vector from (0,0,0) to one of these three vertices. The sign of the product gives the orientation. If it is negative, reverse the order of vertices, if positive - keep them. This works only if (0,0,0) is inside the shape.

Note: depending of the case, you may get symmetrical situation – negatives are good, positives are wrong.

Many years ago I did have a function that I could use to highlight faces that cannon considered inwards facing.

I don’t use it any more since I avoid convexPolyhedrons in preference to compound shapes.

The function was part of my custom debug renderer but I removed it.

I don’t know if the function will work for you. It was written for Three r129 and before.

Use it as a guide if you still want to pursue convexPolyhedrons

Comes with no warranties or guarantees.

//highlight inwards facing faces.
geometry.faces.forEach(f => {
    const n = f.normal
    n.negate();
    f.normal = n
    const v1 = geometry.vertices[f.a]
    if (n.dot(v1) > 0) {
        const v2 = geometry.vertices[f.b]
        const v3 = geometry.vertices[f.c]

        const p = new THREE.Vector3();
        p.x = (v1.x + v2.x + v3.x) / 3;
        p.y = (v1.y + v2.y + v3.y) / 3;
        p.z = (v1.z + v2.z + v3.z) / 3;

        const g = new THREE.Geometry();
        g.vertices.push(v1, v2, v3)
        g.faces.push(new THREE.Face3(0, 1, 2));
        g.computeFaceNormals();
        const m = new THREE.Mesh(g, new THREE.MeshBasicMaterial({ color: 0xff0000 }));
        mesh.add(m)
    }
});

Like I already said, I don’t have your problems.

My simple solution now is that i’ve removed the error message (in favor of a small notice) in the cannon function that checks the normals and replaced that with 3 lines that swap the faces vertices. Then I call computeNormals a second time in the constructor, and on the second pass indeed no more vertices need to be swapped.

It’s a bit odd that this wouldn’t be in the base library (why error without trying the first obvious fix), but anyway, the collisions now work perfectly as intended!

Most likely the reason for this is that fixing issues with user data is not a goal of the library. It only warns if there is something suspicious.

For example, it does not fix wrong normals, because it takes a lot of computations to be really sure that a normal is wrong, because it may appear wrong:

  • if it is really wrong (then only its face must be fixed)
  • if it is correct, but (0,0,0) is outside the shape (then all vertices must be fixed)
  • if it is correct, but the shape is not convex (then only some vertices must be fixed)
  • if is is correct, but there are coplanar faces (then only some vertices must be fixed)

The computation point is fair. Would make it opt-in by a warning asking the user to turn it on for debugging.
Though the first fix, of swapping the faces, is pretty much a trivial amount of computation, as it already did the normal check and just swaps some indices. The second pass isn’t necessary, unless you wan’t to double check that swapping worked, which you can do once for an object and then turn it off.

Almost the same thing goes for centering the vertices by the mean, which runs at O(n), one pass to accumulate the mean, another to subtract the mean from each vertex.

If you really want to cut down on computation you can serialize the object post-check and init it from json with no extra computation at all.