Using three.js and tween.js I'm trying to create a rubick's cube. It is composed of 27 small cubes who are grouped together to be rotated by +Math.PI/2
or -Math.PI/2
. The tween library is used to get a smooth rotation.
My issue is that if a face is rotated multiple times (more than 10) it gets noticeably misalligned and I can't understand how to fix it (link to visual image of the problem).
This is the function that rotates the objects in the example below:
function rotate( ii ) {
// Creates a dummy object to group multiple objects together and
// rotate (only one object in this example).
var rotationGroup = new THREE.Object3D();
scene.add(rotationGroup);
THREE.SceneUtils.attach( box[ ii ], scene, rotationGroup );
rotationGroup.updateMatrixWorld();
// Checks that no other animations are active. If so stops the
// function (this is rude, but it's not the focus of the example).
if( TWEEN.getAll().length>0 )
return;
// Creates the tween object for animation.
var tween;
var duration = 300;
var radiants = Math.PI/2;
tween = new TWEEN.Tween( rotationGroup.rotation ).to( { z: radiants }, duration );
tween.easing( TWEEN.Easing.Quadratic.InOut );
tween.onComplete(
function() {
THREE.SceneUtils.detach( box[ ii ], rotationGroup, scene );
box[ii].updateMatrixWorld();
scene.remove(rotationGroup);
}
);
// Starts the animation.
tween.start();
}
I can't figure out how to create a running snippet on stackoverflow so for now I'll paste some code replicating the problem (not the rubick's one which is longer and way messier).
var camera, scene, renderer;
var canvas;
var cameraControls;
var clock = new THREE.Clock();
var box = [];
function main() {
init();
fillScene();
addToDOM();
animate();
function init() {
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
var canvasRatio = canvasWidth / canvasHeight;
// RENDERER
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.gammaInput = true;
renderer.gammaOutput = true;
renderer.setSize(canvasWidth, canvasHeight);
renderer.setClearColor( 0xAAAAAA );
// CAMERA
camera = new THREE.PerspectiveCamera( 45, canvasRatio, 1, 1000 );
camera.position.set(20,15,10);
camera.lookAt( new THREE.Vector3(0,0,0) );
// CONTROLS
cameraControls = new THREE.OrbitControls(camera, renderer.domElement);
document.onkeydown = function(ev) {onKeyDown(ev);};
}
function addToDOM() {
var container = document.getElementById('WebGL-output');
var canvas = container.getElementsByTagName('canvas');
if (canvas.length>0) {
container.removeChild(canvas[0]);
}
container.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
var delta = clock.getDelta();
cameraControls.update(delta);
TWEEN.update();
renderer.render(scene, camera);
}
function onKeyDown(ev) {
switch( ev.keyCode) {
case 65: // a
rotate(0);
break;
case 83: // s
rotate(1);
break;
case 68: // d
rotate(2);
break;
default: break;
}
}
}
function rotate( ii ) {
var rotationGroup = new THREE.Object3D();
scene.add(rotationGroup);
THREE.SceneUtils.attach( box[ ii ], scene, rotationGroup );
rotationGroup.updateMatrixWorld();
if( TWEEN.getAll().length>0 )
return;
var tween;
var duration = 300;
var radiants = Math.PI/2;
tween = new TWEEN.Tween( rotationGroup.rotation ).to( { z: radiants }, duration );
tween.easing( TWEEN.Easing.Quadratic.InOut );
tween.onComplete(
function() {
THREE.SceneUtils.detach( box[ ii ], rotationGroup, scene );
box[ii].updateMatrixWorld();
scene.remove(rotationGroup);
}
);
tween.start();
}
function fillScene() {
scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0xAAAAAA, 2000, 4000 );
var axisHelper = new THREE.AxisHelper( 20 );
scene.add( axisHelper );
// LIGHTS
scene.add( new THREE.AmbientLight(0x333333) );
var light = new THREE.PointLight( 0x333333, 2 );
light.position.set(15,15,15);
scene.add(light);
light = new THREE.PointLight( 0x333333, 2 );
light.position.set(-15,-15,-15);
scene.add(light);
var boxGeometry = new THREE.BoxGeometry( 10, 3, 10);
var red = new THREE.MeshPhongMaterial({ color: 0xff0000 });
var blue = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
var green = new THREE.MeshPhongMaterial({ color: 0x0000ff });
var black = new THREE.MeshPhongMaterial({ color: 0x000000 });
var materials = [
[ red, red, red, red, black, black ],
[ green, green, green, green, black, black ],
[ blue, blue, blue, blue, black, black ]
];
for( var ii=0; ii<3; ii++ ) {
box[ii] = new THREE.Mesh( boxGeometry, new THREE.MeshFaceMaterial(materials[ii]) );
box[ii].rotation.x = Math.PI/2;
box[ii].position.set(0,0,3-(ii*3));
scene.add( box[ii] );
}
}
Printing on js console the rotation of rotationGroup
and box[ii]
it seems they don't match: the rotation of the Object3D
introduced a bit of error on the fourth decimal digit.
I modified the anonymous function executed after the tween completes adding some code which checks the rotation and sets the correct rotation value (kudos to @WestLangley for spotting the solution).
function() {
THREE.SceneUtils.detach( box[ ii ], rotationGroup, scene );
box[ii].updateMatrixWorld();
scene.remove(rotationGroup);
// Checks the rotation value and sets the correct one accordingly
var check = Math.floor( box[ii].rotation.z );
if( check==1 )
box[ii].rotation.z = Math.PI/2;
else if( check==3 )
box[ii].rotation.z = Math.PI;
else if( check==-2 )
box[ii].rotation.z = -Math.PI/2;
else
box[ii].rotation.z = 0;
}