Search code examples
javascript3dcannon.js

Cannon.js: Finding the side of a dice is using quaternions


Here is a fiddle with some of the code I'm working with.

I'm rolling the dice, not randomly yet, but that'll be added later. But right now I'm having a hard time to find out which side the dice has landed.

On line 352 in the JS, inside the tick function is where I'm trying to find out which side the dice landed on. I've tried several ways to look at the quaternion, but it never gives me the results I'm expecting. And I'm sure its because I don't know jack about quaternions. So any help would be much appreciated.

function tick() {
    world.step(timeStep);

    drawScene();

    if(cubeBody.velocity.norm() < 0.001) {
        var direction = cubeBody.quaternion.toAxisAngle()[0];

        console.log(direction);

        if (direction.x < 0.1 && direction.x > -0.1 &&
            direction.y < 0.1 && direction.y > -0.1) {
            console.log("side 1 or 6");
        } else if (direction.y > 0.1 &&
            direction.z < 0.1 && direction.z > -0.1) {
            console.log("side 2");
        } else if (direction.y < -0.1 &&
            direction.z < 0.1 && direction.z > -0.1) {
            console.log("side 5");
        } else if (direction.x > 0.1 &&
            direction.z < 0.1 && direction.z > -0.1) {
            console.log("side 3");
        } else if (direction.x > 0.1 &&
            direction.z < 0.1 && direction.z > -0.1) {
            console.log("side 4");
        } else {
            console.log("we got trouble");
        }
    } else {
        requestAnimationFrame(tick);
    }

}

This code will probably end up using a switch or object key later, but for right now to help me "visualize" it, I have it as a horrible if statement. Sorry about that.


Solution

  • You can use the six local axes of the cube (+x, -x, +y, -y, +z, -z) and transform them to world space using the body.quaternion, and check which of these vectors is "pointing up the most".

    However, you'll get better performance if you transform the world up vector to local body space, and check which direction this vector is pointing locally.

    Before your game loop:

    var localUp = new CANNON.Vec3();
    var inverseBodyOrientation = new CANNON.Quaternion();
    var limit = Math.sin(Math.PI/4);
    

    In your game loop:

    // Transform the world up vector to local body space
    localUp.set(0,1,0);
    body.quaternion.inverse(inverseBodyOrientation);
    inverseBodyOrientation.vmult(localUp, localUp);
    
    // Check which side is up
    if(localUp.x > limit){
        // Positive x is up
    } else if(localUp.x < -limit){
        // Negative x is up
    } else if(localUp.y > limit){
        // Positive y is up
    } else if(localUp.y < -limit){
        // Negative y is up
    } else if(localUp.z > limit){
        // Positive z is up
    } else if(localUp.z < -limit){
        // Negative z is up
    } else {
        // The box is not resting flat on the ground plane
    }
    

    The limit is chosen so that the local up vector must be within the top, bottom, right, left, front and back slices of a sphere with radius 1. See the gray areas in the picture below.

    The "up" regions to compare against.