schteppe / cannon.js

A lightweight 3D physics engine written in JavaScript.
http://schteppe.github.com/cannon.js
MIT License
4.72k stars 711 forks source link

Actions in case of penetration #37

Closed huulbaek closed 12 years ago

huulbaek commented 12 years ago

I am wondering what actions could be taken in case of penetration?

I have a container where all the objects are inside but there will be many penetrations, because the objects move quickly around.

Since it's not so important exactly where the objects were when penetrating, I thought I could do something like this:

if (object.position.z < -1.8) { // object is outside container
    object.preStep = function(){
        this.velocity.set(0,0,0);
        this.position.set(0,0,0);
        this.preStep = null;
    }
}

But that fails in one of two ways.

1) cannon.js will hit this:

if (closestFaceA<0){
                console.log("--- did not find any closest face... ---");
                return;
            }

and for some reason only the x and y coordinates are applied.

2) cannon.js will throw an Uncaught TypeError: Cannot read property 'length' of undefined in

var numVertices = polyB.length;

I am guessing it's because the engine does some penetration handling before I can apply my fix? Is there any way to apply the changes to the object after penetration? Or should the object be removed from the world and added again?

(love the engine by the way, as you know!) :)

schteppe commented 12 years ago

Okay, the first thing that I would like to say is that this is not a very good solution to the penetration problem. Making the engine better or just increase its precision would be the best thing to do. Maybe use smaller masses or less gravity force?

I am a bit surprised that the engine gets stuck in the contact generator like that. Your solution should be possible to implement if you really want to. Perhaps you could reconstruct this problem using the demo renderer and I'll have a look?

huulbaek commented 12 years ago

Yeah, I know, it's general a terrible solution - but for my purpose it would be fine, if I could succeed. The problem can be reproduced with the following code - some cubes will eventually disappear outside the view and container. However, no errors from the engine - these mostly only appear if I make the preStep callback.

var demo = new CANNON.Demo();
var nx=4, ny=4;
demo.addScene(function(app){
    createContainer(app,nx,ny,4);
});

var groundBody;
demo.start();

function createContainer(app,nx,ny,nz){
    // Create world
    var world = new CANNON.World();
    app.setWorld(world);
    world.gravity.set(0,0,-41);
    world.broadphase = new CANNON.NaiveBroadphase();
    world.solver.iterations = 10;
    world.solver.setSpookParams(4000,1);

    // Materials
    var cube = new CANNON.Material('cube');
    var ground = new CANNON.Material('ground');
    var side = new CANNON.Material('side');
    var top = new CANNON.Material('top');

    var cube_ground = new CANNON.ContactMaterial(cube,
    ground,
    0.6,
    0.99
    );
    world.addContactMaterial(cube_ground);

    var cube_top = new CANNON.ContactMaterial(cube,
    top,
    0.7,
    1
    );
    world.addContactMaterial(cube_top);

    var cube_side = new CANNON.ContactMaterial(cube,
    side,
    0.1,
    1
    );
    world.addContactMaterial(cube_side);

    var cube_cube = new CANNON.ContactMaterial(cube,
    cube,
    0.7,
    0.95
    );
    world.addContactMaterial(cube_cube);

    // ground plane
    var groundShape = new CANNON.Plane(new CANNON.Vec3(0,0,1));
    groundBody = new CANNON.RigidBody(0,groundShape,ground);
    groundBody.motionstate = CANNON.Body.KINEMATIC;
    groundBody.position.set(0,0,-1);
    world.add(groundBody);
    app.addVisual(groundBody);

    // Top plane
    var topShape = new CANNON.Plane(new CANNON.Vec3(0,0,-1));
    var topBody = new CANNON.RigidBody(0,topShape,top);
    topBody.position.set(0,0,2);
    world.add(topBody);

    // plane -x
    var planeShapeXmin = new CANNON.Plane(new CANNON.Vec3(0,1,0));
    var planeXmin = new CANNON.RigidBody(0, planeShapeXmin,side);
    planeXmin.position.set(0,-5,0);
    world.add(planeXmin);

    // Plane +x
    var planeShapeXmax = new CANNON.Plane(new CANNON.Vec3(0,-1,0));
    var planeXmax = new CANNON.RigidBody(0, planeShapeXmax,side);
    planeXmax.position.set(0,5,0);
    world.add(planeXmax);

    // Plane -y
    var planeShapeYmin = new CANNON.Plane(new CANNON.Vec3(1,0,0));
    var planeYmin = new CANNON.RigidBody(0, planeShapeYmin,side);
    planeYmin.position.set(-5,0,0);
    world.add(planeYmin);

    // Plane +y
    var planeShapeYmax = new CANNON.Plane(new CANNON.Vec3(-1,0,0));
    var planeYmax = new CANNON.RigidBody(0, planeShapeYmax, side);
    planeYmax.position.set(5,0,0);
    world.add(planeYmax);

    // Cubes on plane
    var phys_bodies = [];
    var physicsCube;
    var cubeShape = new CANNON.Box(new CANNON.Vec3(0.15,0.15,0.15));
    for (var i=0; i<24; i++) {
        physicsCube = new CANNON.RigidBody(2, cubeShape, cube);
        physicsCube.linearDamping = 0;
        physicsCube.angularDamping = 0;

        var PosX = Math.random() * 2.01 - 1;
        var PosY = Math.random() * 2.01 - 1;
        physicsCube.position.set(PosX,PosY,2);

        var QuaX = Math.random() * 2.01 - 1;
        var QuaY = Math.random() * 2.01 - 1;
        var QuaZ = Math.random() * 2.01 - 1;

        var q = new CANNON.Quaternion();

        q.setFromAxisAngle(new CANNON.Vec3(QuaX,QuaY,QuaZ),Math.PI*0.25);
        physicsCube.quaternion.set(q.x,q.y,q.z,q.w);

        var VelX = Math.random()*20 - 10;
        var VelY = Math.random()*20 - 10;
        var VelZ = Math.random()*20 - 10;
        physicsCube.velocity.set(VelX,VelY,VelZ);

        phys_bodies.push(physicsCube);
        world.add(physicsCube);
        app.addVisual(physicsCube);

    }
}
timerId = setInterval(function(){
    if(groundBody.position.z == -1) {
        groundBody.position.set(0,0,-0.5);
    }
    else
        groundBody.position.set(0,0,-1);
},200);
schteppe commented 12 years ago

Maybe the errors appeared because you used the preStep callback in the wrong way - I'm not sure. This is what I added to the for loop above, and it worked without errors.

physicsCube.preStep = function(){
  if (this.position.z < -0.8) { // object is outside container
    this.velocity.set(0,0,0);
    this.position.set(0,0,0);
  }
};

Each cube is indeed being transported to the origin and its linear center-of-mass velocity is set to zero whenever it passes z=-0.8. Had to change -1.8 to -0.8, otherwise nothing happened. Not sure if this is what you wanted though.

Another thing that I came to think about when experimenting with your code was normalization of quaternions. You may want to do full quaternion normalization each step if you have very drastic movements like your example. Otherwise you may get too drastic rotations and your quaternions will blow up (usually the 3D objects will expand to infinite size in the Demo framework when this happens). Not sure, but this can be the reason why the errors appeared. The quaternion normalization is currently done internally in the World class.

if(stepnumber % 3 === 0)
  quat.normalizeFast();

Changing the integer to 1 will make the engine normalize each step instead of the default of every third step. Changing .normalizeFast() to .normalize() will make the normalization slower but of higher quality. If you feel confident about the maximum size of the velocities in your simulation you can use even fewer normalizations (change 3 to any larger integer) to instead increase performance.

huulbaek commented 12 years ago

It seems that just by changing the normalization steps from 3 to 1 fixed all my problems - at least, from the few minutes of testing I have performed just now.

So, I don't need the prestep!

(and no, I am not at all confident about the max velocities).

Awesome! ;)

schteppe commented 12 years ago

Good! I'll try making it simpler to change the normalization frequency in future updates :)