cannon.js icon indicating copy to clipboard operation
cannon.js copied to clipboard

ConvexPolyhedron and Box collision detection issue

Open peterfaj opened this issue 10 years ago • 10 comments

First of all, thanks for this project. I didn't think a physics engine could be this fun. I'm just starting out with Cannon.js, but I'm having an issue with collision detection.

I have two objects: a Box and a ConvexPolyhedron. Sometimes when they collide, the collision is not immediately detected and the box enters the polyhedron. Then, if the box is pushed further, they are suddenly violently separated.

If I use gravity, the polyhedron stands nicely on the ground, but if it is disturbed, it sometimes falls partially into the ground and is then violently thrown skyward.

Is this a known issue, or is there something wrong with my ConvexPolyhedron? The model I'm using is convex. It is a small model that I made for testing purposes. I have uploaded it here: https://www.dropbox.com/s/aih8dxy6cxi6jan/simple.js?dl=0

There is no problem with collision detection when I'm using two Boxes, however.

Thanks in advance.

peterfaj avatar Nov 19 '14 11:11 peterfaj

Strange! Do you have a working example scene?

schteppe avatar Nov 19 '14 12:11 schteppe

Thank you for replying.

Here's the example: https://www.dropbox.com/s/dwow5mvno6bw9ul/example.html?dl=0

peterfaj avatar Nov 19 '14 16:11 peterfaj

I have the exact same problem (using 0.6.2). Spheres always collide with a ConvexPolyhedron, but boxes will not collide at certain locations. Basically, I have created a plane with a ConvexPolyhedron, added some gravity and placed boxes and spheres on top. Some of the boxes fall through, all of the spheres remain in place.

thohag avatar Mar 30 '15 00:03 thohag

Strange.. I started off by re-making the scene in the Cannon.js debug renderer. The code follows.

<!DOCTYPE html>
<html>
  <head>
    <title>cannon.js - convex demo</title>
    <meta charset="utf-8">
    <link rel="stylesheet" href="css/style.css" type="text/css"/>
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  </head>
  <body>
    <script src="../build/cannon.js"></script>
    <script src="../build/cannon.demo.js"></script>
    <script src="../libs/dat.gui.js"></script>
    <script src="../libs/Three.js"></script>
    <script src="../libs/TrackballControls.js"></script>
    <script src="../libs/Detector.js"></script>
    <script src="../libs/Stats.js"></script>
    <script src="../libs/smoothie.js"></script>
    <script>

      /**
       * Experiment for testing ConvexPolyhedrons.
       */
      var demo = new CANNON.Demo();

      demo.addScene("test",function(){
          var world = setupWorld(demo);

          world.gravity.set(0,0,0);

          // Blue Box
          var shape = new CANNON.Box(new CANNON.Vec3(0.5, 0.25, 1));
          var body = new CANNON.Body( {
              position:      new CANNON.Vec3(0, -0.1, 5),
              velocity:      new CANNON.Vec3(0, 0, -4),
              linearDamping: 0.5,
              angularDamping: 0.5,
              mass:          100
          } );
          body.addShape(shape);
          world.addBody(body);
          demo.addVisual(body);


          body = new CANNON.Body( {
              mass:          10,
              linearDamping: 0.5
          } );
          body.angularDamping = 0.5;
          var vertices = [];

          // Gray ConvexPolyhedron
          body.position.set(0, 0, -2);
          body.quaternion.y = 10;
          body.quaternion.z = 5;
          body.quaternion.normalize();

          var verts = [-1.281104,-0.003405,0.892073,1.839549,-0.003405,0.892073,-1.281104,0.641190,0.892073,1.839549,0.641190,0.892073,-1.281104,0.641190,-0.694338,1.839549,0.641190,-0.694338,-1.281104,-0.003405,-0.694338,1.839549,-0.003405,-0.694338,0.279223,1.241851,0.892073,0.279223,1.241851,-0.694338,-0.500941,1.241851,0.892073,-0.500941,1.241851,-0.694338];

          for (var i=0; i<verts.length / 3; i++) {
              vertices.push(new CANNON.Vec3(verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2]));
          }

          var faces = [
            [
              0,
              1,
              2
            ],
            [
              2,
              1,
              10
            ],
            [
              10,
              1,
              8
            ],
            [
              1,
              3,
              8
            ],
            [
              2,
              10,
              4
            ],
            [
              4,
              10,
              11
            ],
            [
              7,
              6,
              5
            ],
            [
              5,
              6,
              9
            ],
            [
              6,
              4,
              9
            ],
            [
              4,
              11,
              9
            ],
            [
              6,
              7,
              0
            ],
            [
              0,
              7,
              1
            ],
            [
              1,
              7,
              3
            ],
            [
              3,
              7,
              5
            ],
            [
              6,
              0,
              4
            ],
            [
              4,
              0,
              2
            ],
            [
              9,
              8,
              5
            ],
            [
              5,
              8,
              3
            ],
            [
              11,
              10,
              9
            ],
            [
              9,
              10,
              8
            ]
          ];
          var part = new CANNON.ConvexPolyhedron(vertices, faces);
          body.addShape(part);
          world.addBody(body);
          demo.addVisual(body);
        });

      function setupWorld(demo){
        var world = demo.getWorld();
        world.broadphase = new CANNON.NaiveBroadphase();
        world.solver.iterations = 10;
        return world;
      };

      demo.start();

    </script>
  </body>
</html>

schteppe avatar Mar 30 '15 06:03 schteppe

Hi. Apart from misunderstanding something, I encounter the same problem. ConvexPolyedra (simple tetrahedra with mass 0 in my example) have strange behaviors. Other physical objects go through these tetrahedra or... sometimes not, depending upon the location of the contact point?? Don't really understand "bullet 2" of Stefan's conclusion in https://github.com/schteppe/cannon.js/issues/109 ("The winding of the faces needs to be counter clockwise around the normal"?????). In my example, behaviors of tetrahedra change when changing the way face indices are organized. So, I probably missed something... Any idea??

Bab64 avatar Aug 10 '16 08:08 Bab64

The issue seems to be caused by coplanar faces in the model. Even though the object itself is convex and that's good, additionally no pair of faces must be coplanar i.e. be contained in the same 3D plane.

See here: https://github.com/pmndrs/cannon-es/blob/master/src/shapes/ConvexPolyhedron.ts##L16-L17

Realizing and changing this fixed my collision issues that looked very similar to yours.

YouRik avatar Apr 27 '21 10:04 YouRik

Thanks @YouRik. It's been a while and I don't remember how I generated the example model. But if I assume that all faces are triangles, then yes, it does indeed have coplanar faces. If I modify the side that gets hit, by moving one of its vertices out of the shared plane, the problem disappears. Here's the example modified in this way: https://www.dropbox.com/s/vmy3l3rib5tdrnl/example_fixed.html?dl=0

peterfaj avatar May 03 '21 10:05 peterfaj

Just ran into this issue where box colliders acted strangely with convex polyhedrons, turns out I was using a Static Cannon Body with mass =1 . I set the mass = 0, and the problem got fixed.

Rafapp avatar Jul 13 '23 14:07 Rafapp

Thanks Rafapp for the tip! I've used this extension (fork?) 1 year ago: https://pmndrs.github.io/cannon-es/. Maybe, the problem is solved in this extension (untested)?

Bab64 avatar Jul 31 '23 15:07 Bab64

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:

  • Run blender directly in order to see console output ( https://blender.stackexchange.com/questions/6173/where-does-console-output-go )
  • Generate 1 box in blender per collider and do not apply transforms
  • Name your collection in blender "Colliders"

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.

Rafapp avatar Aug 07 '23 18:08 Rafapp