Rotation after a Rotation not acting how I Expect

119 Views Asked by At

I have a rubiks-cube-like puzzle I am trying to model in webgl using Three.js:

enter image description here

Each of the two-color centers can rotate. When they rotate, they bring all of the pieces around them along on the rotation. For example, if we rotate the orange-blue center, this is what happens:

rotating around Orange-Blue Center

When you complete a rotation, everything lines up:

enter image description here

However, now when I try to rotate the Orange-White center, I get strange behavior for the two pieces it inherited from the Orange-Blue center after the first rotation:

enter image description here

I expect them to rotate like the other pieces, but instead they rotate differently.

I am using Three.js's Object3D.rotateOnAxis() function to do my rotations:

function rotate ( center, distance ) {
    var axis = center.axis;
    center.model.rotateOnAxis ( axis, distance );
    for ( var i in center.stickers ) {
        center.stickers[i].rotateOnAxis ( axis, distance  );
    }

    for ( var i in center.children ) {
        center.children[i].model.rotateOnAxis ( axis, distance );

        //Note: the stickers are just the colored faces
        for ( var s in center.children[i].stickers ) {
            center.children[i].stickers[s].rotateOnAxis ( axis, distance );
        }
    }
}

Here is what my key-press code looks like:

function showCube() {
    //set stuff up, then...

    count = 0;
    parent.onkeypress = function(event) { 
        if ( count < 6 ) {
            rotate( blocks.centers [ "BO" ],  Math.PI / 6 );

            count++;

            if ( count == 6 ) {
                moveFace ( blocks, "O1", "OY", "BW" );
                moveFace ( blocks, "O2", "OW", "BY" );

                moveFace ( blocks, "B1", "BW", "OY" );
                moveFace ( blocks, "B2", "BY", "OW" );

                moveCorner ( blocks, "BOW", "OW", "OY" );
                moveCorner ( blocks, "BOW", "BW", "BY" );

                moveCorner ( blocks, "BOY", "OY", "OW" );
                moveCorner ( blocks, "BOY", "BY", "BW" );
            }

        } else { 
            rotate( blocks.centers [ "OW" ],  Math.PI / 6 );
        }
    }
}

function moveFace ( blocks, child, oldParent, newParent ) {
    var index = blocks.centers [ oldParent ].children.indexOf ( blocks.faces [ child ] );
    blocks.centers [ oldParent ].children.splice ( index, 1 );
    blocks.centers [ newParent ].children.push ( blocks.faces [ child ] );
}

function moveCorner ( blocks, child, oldParent, newParent ) {
    var index = blocks.centers [ oldParent ].children.indexOf ( blocks.corners [ child ] );
    blocks.centers [ oldParent ].children.splice ( index, 1 );
    blocks.centers [ newParent ].children.push ( blocks.corners [ child ] );
}

Interestingly, to get the Blue-Orange-Yellow corner to rotate correctly, I need to do the rotation around the difference between the BO vector and the OW vector.

That is to say:

  • Blue-Orange axis is the normalized vector of: (0, 1, 1)
  • Orange-White axis is the normalized vector of: (1, 0, 1)
  • After the first rotation is complete, if I try to rotate the BOY corner around the normal of (1, 0, 1), I get the behavior shown in the pictures.
  • However, if I try to rotate it around (0, 1, 1) - (1, 0, 1), i.e. (-1, 1, 0), I get the desired behavior.

I don't understand what's happening. Can you help me update my rotate function so I get the behavior I want, without having to keep a long list of past rotations that a piece has experienced?

I suspect that after I do my first rotation, I need to tell the piece to "zero" its rotation state without causing any motion, but I'm not quite sure how to do that, or if it's the right approach.

You can play with the thing here: http://joshuad.net/clover-cube/so-question/

Thanks!

1

There are 1 best solutions below

0
Grumblesaurus On BEST ANSWER

I fixed it by changing my rotate method to this:

function rotate ( center, distance ) {
    var axis = center.axis;
    center.model.rotateOnAxis ( axis, distance );
    applyStatesToMatrixDirectly ( center.model );

    for ( var stickerIndex in center.stickers ) {
        center.stickers[stickerIndex].rotateOnAxis ( axis, distance  );
        applyStatesToMatrixDirectly ( center.stickers[stickerIndex] );
    }

    for ( var childIndex in center.children ) {
        center.children[childIndex].model.rotateOnAxis ( axis, distance );
        applyStatesToMatrixDirectly ( center.children[childIndex].model );

        for ( var childStickerIndex in center.children[childIndex].stickers ) {
            center.children[childIndex].stickers[childStickerIndex].rotateOnAxis ( axis, distance );
            applyStatesToMatrixDirectly ( center.children[childIndex].stickers[childStickerIndex] );
        }
    }
}

function applyStatesToMatrixDirectly ( model ) {
    model.updateMatrix();
    model.geometry.applyMatrix( model.matrix );
    model.position.set( 0, 0, 0 );
    model.rotation.set( 0, 0, 0 );
    model.scale.set( 1, 1, 1 )
    model.updateMatrix();
}

The idea is that the function applyStatesToMatrixDirectly() applies the rotation directly to the model matrix and then reset all of the rotation data (as well as everything else). This allows the model to "forget" that it has been rotated, allowing the new rotateOnAxis to work.