diff options
Diffstat (limited to 'basicsuite/webengine/content/rubiks/js/rubik.js')
-rw-r--r-- | basicsuite/webengine/content/rubiks/js/rubik.js | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/basicsuite/webengine/content/rubiks/js/rubik.js b/basicsuite/webengine/content/rubiks/js/rubik.js new file mode 100644 index 0000000..4e6cf28 --- /dev/null +++ b/basicsuite/webengine/content/rubiks/js/rubik.js @@ -0,0 +1,492 @@ +Array.prototype.clone = function() { + var c = []; + var len = this.length; + for (var i=0;i<len;i++) { c.push(this[i]); } + return c; +} + +Array.prototype.random = function() { + return this[Math.floor(Math.random()*this.length)]; +} + +var Face = OZ.Class(); +Face.SIZE = 100; +Face.LEFT = 0; +Face.RIGHT = 1; +Face.TOP = 2; +Face.BOTTOM = 3; +Face.FRONT = 4; +Face.BACK = 5; + +Face.ROTATION = [ + [Face.TOP, Face.FRONT, Face.BOTTOM, Face.BACK].reverse(), + [Face.LEFT, Face.BACK, Face.RIGHT, Face.FRONT].reverse(), + [Face.LEFT, Face.BOTTOM, Face.RIGHT, Face.TOP].reverse() +]; + +Face.prototype.init = function(cube, type) { + this._cube = cube; + this._type = type; + this._color = null; + this._node = OZ.DOM.elm("div", {className:"face face"+type, width:Face.SIZE+"px", height:Face.SIZE+"px", position:"absolute", left:"0px", top:"0px"}); + OZ.CSS3.set(this._node, "box-sizing", "border-box"); +// OZ.CSS3.set(this._node, "backface-visibility", "hidden"); + + switch (type) { + case Face.LEFT: + OZ.CSS3.set(this._node, "transform-origin", "100% 50%"); + OZ.CSS3.set(this._node, "transform", "translate3d(-"+Face.SIZE+"px, 0px, 0px) rotateY(-90deg)"); + break; + case Face.RIGHT: + OZ.CSS3.set(this._node, "transform-origin", "0% 50%"); + OZ.CSS3.set(this._node, "transform", "translate3d("+Face.SIZE+"px, 0px, 0px) rotateY(90deg)"); + break; + case Face.TOP: + OZ.CSS3.set(this._node, "transform-origin", "50% 100%"); + OZ.CSS3.set(this._node, "transform", "translate3d(0px, -"+Face.SIZE+"px, 0px) rotateX(90deg)"); + break; + case Face.BOTTOM: + OZ.CSS3.set(this._node, "transform-origin", "50% 0%"); + OZ.CSS3.set(this._node, "transform", "translate3d(0px, "+Face.SIZE+"px, 0px) rotateX(-90deg)"); + break; + case Face.FRONT: + break; + case Face.BACK: + OZ.CSS3.set(this._node, "transform", "translate3d(0px, 0px, -"+Face.SIZE+"px) rotateY(180deg)"); + break; + } +} + +Face.prototype.getCube = function() { + return this._cube; +} + +Face.prototype.getNode = function() { + return this._node; +} + +Face.prototype.getType = function() { + return this._type; +} + +Face.prototype.setColor = function(color) { + this._color = color; + this._node.style.backgroundColor = color; +} + +Face.prototype.getColor = function() { + return this._color; +} + +var Cube = OZ.Class(); +Cube.prototype.init = function(position) { + this._rotation = null; + this._position = position; + this._node = OZ.DOM.elm("div", {className:"cube", position:"absolute", width:Face.SIZE+"px", height:Face.SIZE+"px"}); + this._faces = {}; + this._tmpFaces = {}; + OZ.CSS3.set(this._node, "transform-style", "preserve-3d"); + + this._update(); +} + +Cube.prototype.getFaces = function() { + return this._faces; +} + +Cube.prototype.setFace = function(type, color) { + if (!(type in this._faces)) { + var face = new Face(this, type); + this._node.appendChild(face.getNode()); + this._faces[type] = face; + } + this._faces[type].setColor(color); +} + +Cube.prototype.setRotation = function(rotation) { + this._rotation = rotation; + this._update(); +} + +Cube.prototype.complete = function() { + for (var i=0;i<6;i++) { + if (i in this._faces) { continue; } + this.addFace(i, "black"); + } +} + +Cube.prototype.prepareColorChange = function(sourceCube, rotation) { + this._tmpFaces = {}; + var sourceFaces = sourceCube.getFaces(); + for (var p in sourceFaces) { + var sourceType = parseInt(p); + var targetType = this._rotateType(sourceType, rotation); + this._tmpFaces[targetType] = sourceFaces[sourceType].getColor(); + } +} + +Cube.prototype.commitColorChange = function() { +// var parent = this._node.parentNode; +// parent.removeChild(this._node); + + OZ.DOM.clear(this._node); + this._faces = {}; + for (var p in this._tmpFaces) { + var type = parseInt(p); + this.setFace(type, this._tmpFaces[p]); + } + this._tmpFaces = {}; + + this._rotation = null; + this._update(); +// parent.appendChild(this._node); +} + +Cube.prototype._rotateType = function(type, rotation) { + for (var i=0;i<3;i++) { + if (!rotation[i]) { continue; } + var faces = Face.ROTATION[i]; + var index = faces.indexOf(type); + if (index == -1) { continue; } /* no rotation available */ + index = (index + rotation[i] + faces.length) % faces.length; + return faces[index]; + } + + return type; +} + +Cube.prototype._update = function() { + var transform = ""; + transform += "translate3d("+(-Face.SIZE/2)+"px, "+(-Face.SIZE/2)+"px, "+(-Face.SIZE/2)+"px) "; + if (this._rotation) { transform += this._rotation + " "; } + + var half = Math.floor(Rubik.SIZE/2); + var x = this._position[0]; + var y = this._position[1]; + var z = -this._position[2]; + x -= half; + y -= half; + z += half + 1/2; + transform += "translate3d("+(x*Face.SIZE)+"px, "+(y*Face.SIZE)+"px, "+(z*Face.SIZE)+"px)"; + + var prop = OZ.CSS3.getProperty("transform"); + var val = this._rotation ? prop + " 300ms" : ""; + OZ.CSS3.set(this._node, "transition", val); + + OZ.CSS3.set(this._node, "transform", transform); +} + +Cube.prototype.getPosition = function() { + return this._position; +} + +Cube.prototype.getNode = function() { + return this._node; +} + +Cube.prototype.getFaces = function() { + return this._faces; +} + +var Rubik = OZ.Class(); +Rubik.SIZE = 3; +Rubik.prototype.init = function() { + this._cubes = []; + this._faces = []; + this._faceNodes = []; + this._help = {}; + this._drag = { + ec: [], + mouse: [], + face: null + }; + + this._rotation = Quaternion.fromRotation([1, 0, 0], -35).multiply(Quaternion.fromRotation([0, 1, 0], 45)); + this._node = OZ.DOM.elm("div", {position:"absolute", left:"50%", top:"55%", width:"0px", height:"0px"}); + document.body.appendChild(this._node); + + OZ.CSS3.set(document.body, "perspective", "460px"); + OZ.CSS3.set(this._node, "transform-style", "preserve-3d"); + + this._build(); + this._update(); + OZ.Event.add(document.body, "mousedown touchstart", this._dragStart.bind(this)); + + setTimeout(this.randomize.bind(this), 500); +} + +Rubik.prototype.randomize = function() { + var remain = 10; + var cb = function() { + remain--; + if (remain > 0) { + this._rotateRandom(); + } else { + OZ.Event.remove(e); + + this._help.a = OZ.DOM.elm("p", {innerHTML:"Drag or swipe the background to rotate the whole cube."}); + this._help.b = OZ.DOM.elm("p", {innerHTML:"Drag or swipe the cube to rotate its layers."}); + document.body.appendChild(this._help.a); + document.body.appendChild(this._help.b); + OZ.CSS3.set(this._help.a, "transition", "opacity 500ms"); + OZ.CSS3.set(this._help.b, "transition", "opacity 500ms"); + + } + } + var e = OZ.Event.add(null, "rotated", cb.bind(this)); + this._rotateRandom(); +} + +Rubik.prototype._rotateRandom = function() { + var method = "_rotate" + ["X", "Y", "Z"].random(); + var dir = [-1, 1].random(); + var layer = Math.floor(Math.random()*Rubik.SIZE); + this[method](dir, layer); +} + +Rubik.prototype._update = function() { + OZ.CSS3.set(this._node, "transform", "translateZ(" + (-Face.SIZE/2 - Face.SIZE) + "px) " + this._rotation.toRotation() + " translateZ("+(Face.SIZE/2)+"px)"); +} + +Rubik.prototype._eventToFace = function(e) { + if (document.elementFromPoint) { + e = (e.touches ? e.touches[0] : e); + var node = document.elementFromPoint(e.clientX, e.clientY); + } else { + var node = OZ.Event.target(e); + } + var index = this._faceNodes.indexOf(node); + if (index == -1) { return null; } + return this._faces[index]; +} + +Rubik.prototype._dragStart = function(e) { + this._faces = []; + this._faceNodes = []; + for (var i=0;i<this._cubes.length;i++) { + var faces = this._cubes[i].getFaces(); + for (var p in faces) { + this._faces.push(faces[p]); + this._faceNodes.push(faces[p].getNode()); + } + } + + OZ.Event.prevent(e); + this._drag.face = this._eventToFace(e); + e = (e.touches ? e.touches[0] : e); + this._drag.mouse = [e.clientX, e.clientY]; + + this._drag.ec.push(OZ.Event.add(document.body, "mousemove touchmove", this._dragMove.bind(this))); + this._drag.ec.push(OZ.Event.add(document.body, "mouseup touchend", this._dragEnd.bind(this))); +} + +Rubik.prototype._dragMove = function(e) { + if (e.touches && e.touches.length > 1) { return; } + + if (this._drag.face) { /* check second face for rotation */ + var thisFace = this._eventToFace(e); + if (!thisFace || thisFace == this._drag.face) { return; } + this._dragEnd(); + this._rotate(this._drag.face, thisFace); + } else { /* rotate cube */ + e = (e.touches ? e.touches[0] : e); + var mouse = [e.clientX, e.clientY]; + var dx = mouse[0] - this._drag.mouse[0]; + var dy = mouse[1] - this._drag.mouse[1]; + var norm = Math.sqrt(dx*dx+dy*dy); + if (!norm) { return; } + var N = [-dy/norm, dx/norm]; + + this._drag.mouse = mouse; + this._rotation = Quaternion.fromRotation([N[0], N[1], 0], norm/2).multiply(this._rotation); + this._update(); + } +} + +Rubik.prototype._dragEnd = function(e) { + while (this._drag.ec.length) { OZ.Event.remove(this._drag.ec.pop()); } + + if (!this._drag.face && this._help.a) { + this._help.a.style.opacity = 0; + this._help.a = null; + } +} + +Rubik.prototype._rotate = function(face1, face2) { + var t1 = face1.getType(); + var t2 = face2.getType(); + var pos1 = face1.getCube().getPosition(); + var pos2 = face2.getCube().getPosition(); + + /* find difference between cubes */ + var diff = 0; + var diffIndex = -1; + for (var i=0;i<3;i++) { + var d = pos1[i]-pos2[i]; + if (d) { + if (diffIndex != -1) { return; } /* different in >1 dimensions */ + diff = (d > 0 ? 1 : -1); + diffIndex = i; + } + } + + if (t1 == t2) { /* same face => diffIndex != -1 */ + switch (t1) { + case Face.FRONT: + case Face.BACK: + var coef = (t1 == Face.FRONT ? 1 : -1); + if (diffIndex == 0) { this._rotateY(coef*diff, pos1[1]); } else { this._rotateX(coef*diff, pos1[0]); } + break; + + case Face.LEFT: + case Face.RIGHT: + var coef = (t1 == Face.LEFT ? 1 : -1); + if (diffIndex == 2) { this._rotateY(-coef*diff, pos1[1]); } else { this._rotateZ(coef*diff, pos1[2]); } + break; + + case Face.TOP: + case Face.BOTTOM: + var coef = (t1 == Face.TOP ? 1 : -1); + if (diffIndex == 0) { this._rotateZ(-coef*diff, pos1[2]); } else { this._rotateX(-coef*diff, pos1[0]); } + break; + } + return; + } + + switch (t1) { /* different face => same cube, diffIndex == 1 */ + case Face.FRONT: + case Face.BACK: + var coef = (t1 == Face.FRONT ? 1 : -1); + if (t2 == Face.LEFT) { this._rotateY(1 * coef, pos1[1]); } + if (t2 == Face.RIGHT) { this._rotateY(-1 * coef, pos1[1]); } + if (t2 == Face.TOP) { this._rotateX(1 * coef, pos1[0]); } + if (t2 == Face.BOTTOM) { this._rotateX(-1 * coef, pos1[0]); } + break; + + case Face.LEFT: + case Face.RIGHT: + var coef = (t1 == Face.LEFT ? 1 : -1); + if (t2 == Face.FRONT) { this._rotateY(-1 * coef, pos1[1]); } + if (t2 == Face.BACK) { this._rotateY(1 * coef, pos1[1]); } + if (t2 == Face.TOP) { this._rotateZ(1 * coef, pos1[2]); } + if (t2 == Face.BOTTOM) { this._rotateZ(-1 * coef, pos1[2]); } + break; + + case Face.TOP: + case Face.BOTTOM: + var coef = (t1 == Face.TOP ? 1 : -1); + if (t2 == Face.FRONT) { this._rotateX(-1 * coef, pos1[0]); } + if (t2 == Face.BACK) { this._rotateX(1 * coef, pos1[0]); } + if (t2 == Face.LEFT) { this._rotateZ(-1 * coef, pos1[2]); } + if (t2 == Face.RIGHT) { this._rotateZ(1 * coef, pos1[2]); } + break; + } + +} + +Rubik.prototype._rotateX = function(dir, layer) { + var cubes = []; + for (var i=0;i<Rubik.SIZE*Rubik.SIZE;i++) { + cubes.push(this._cubes[layer + i*Rubik.SIZE]); + } + this._rotateCubes(cubes, [dir, 0, 0]); +} + +Rubik.prototype._rotateY = function(dir, layer) { + var cubes = []; + for (var i=0;i<Rubik.SIZE;i++) { + for (var j=0;j<Rubik.SIZE;j++) { + cubes.push(this._cubes[j + layer*Rubik.SIZE + i*Rubik.SIZE*Rubik.SIZE]); + } + } + this._rotateCubes(cubes, [0, -dir, 0]); +} + +Rubik.prototype._rotateZ = function(dir, layer) { + var cubes = []; + var offset = layer * Rubik.SIZE * Rubik.SIZE; + for (var i=0;i<Rubik.SIZE*Rubik.SIZE;i++) { + cubes.push(this._cubes[offset+i]); + } + this._rotateCubes(cubes, [0, 0, dir]); +} + +Rubik.prototype._rotateCubes = function(cubes, rotation) { + var suffixes = ["X", "Y", ""]; + + var prefix = OZ.CSS3.getPrefix("transition"); + if (prefix === null) { + this._finalizeRotation(cubes, rotation); + } else { + var cb = function() { + OZ.Event.remove(e); + this._finalizeRotation(cubes, rotation); + } + var e = OZ.Event.add(document.body, "webkitTransitionEnd transitionend MSTransitionEnd oTransitionEnd", cb.bind(this)); + + var str = ""; + for (var i=0;i<3;i++) { + if (!rotation[i]) { continue; } + str = "rotate" + suffixes[i] + "(" + (90*rotation[i]) + "deg)"; + } + for (var i=0;i<cubes.length;i++) { cubes[i].setRotation(str); } + } + +} + +/** + * Remap colors + */ +Rubik.prototype._finalizeRotation = function(cubes, rotation) { + var direction = 0; + for (var i=0;i<3;i++) { + if (rotation[i]) { direction = rotation[i]; } + } + + if (rotation[0]) { direction *= -1; } /* FIXME wtf */ + + var half = Math.floor(Rubik.SIZE/2); + + for (var i=0;i<cubes.length;i++) { + var x = i % Rubik.SIZE - half; + var y = Math.floor(i / Rubik.SIZE) - half; + + var source = [y*direction + half, -x*direction + half]; + var sourceIndex = source[0] + Rubik.SIZE*source[1]; + + cubes[i].prepareColorChange(cubes[sourceIndex], rotation); + } + + for (var i=0;i<cubes.length;i++) { cubes[i].commitColorChange(); } + + setTimeout(function() { + if (this._help.b) { + this._help.b.style.opacity = 0; + this._help.b = null; + } + + this.dispatch("rotated"); + }.bind(this), 100); +} + +Rubik.prototype._build = function() { + for (var z=0;z<Rubik.SIZE;z++) { + for (var y=0;y<Rubik.SIZE;y++) { + for (var x=0;x<Rubik.SIZE;x++) { + var cube = new Cube([x, y, z]); + this._cubes.push(cube); + + if (z == 0) { cube.setFace(Face.FRONT, "red"); } + if (z == 2) { cube.setFace(Face.BACK, "blue"); } + + if (x == 0) { cube.setFace(Face.LEFT, "green"); } + if (x == 2) { cube.setFace(Face.RIGHT, "yellow"); } + + if (y == 0) { cube.setFace(Face.TOP, "cyan"); } + if (y == 2) { cube.setFace(Face.BOTTOM, "teal"); } + this._node.appendChild(cube.getNode()); + } + } + } + +} |