cannon.js
cannon.js copied to clipboard
ConvexPolyhedron and Box collision detection issue
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.
Strange! Do you have a working example scene?
Thank you for replying.
Here's the example: https://www.dropbox.com/s/dwow5mvno6bw9ul/example.html?dl=0
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.
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>
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??
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.
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
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.
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)?
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.