Created
April 28, 2013 14:59
-
-
Save edom18/5477116 to your computer and use it in GitHub Desktop.
CSS RubikCube Game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#CSS+JSでルービックキューブを描いていきます。 | |
* オートクリア機能を追加。 | |
* 回転方向の履歴を保持するよう機能追加。 | |
* ルービックキューブ完成時に完成のイベントを発火するよう修正。 | |
* 初期状態をランダムな状態からスタートするよう修正 | |
* パフォーマンス・チューニング。iPhone5でだいぶ快適に動作するように。 | |
* ルービックキューブのキューブ間の間隔を指定できるよう修正。 | |
* DEMOモードを追加。 | |
* パフォーマンス・チューニングを若干。 | |
* ルービックキューブ上でもgestureが正しく動くよう修正。 | |
* ライティングを正常に動作するよう修正。 | |
* 各面の色を実物を元に色設定しました。(公式の配色とかあるのかな・・) | |
* いくつかの面でドラッグ方向と逆に回転するのを修正。 | |
* ドラッグで直感的にキューブを回転できるよう実装。(でもまだたまに逆方向に回転したりします・・) | |
* ボタン操作でそれぞれの面を回転できます。 | |
* マウスホイールでズームできるように。 | |
* Object3Dクラスを実装。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import "compass/reset"; | |
html, body { | |
background-color: #111; | |
width: 100%; | |
height: 100%; | |
} | |
#ctrl { | |
position: absolute; | |
z-index: 100; | |
padding: 5px; | |
background-color: rgba(255, 255, 255, 0.5); | |
p { | |
padding: 8px; | |
margin: 5px 0; | |
display: inline-block; | |
background: #fff; | |
line-height: 1; | |
vertical-align: top; | |
input { | |
line-height: 1; | |
} | |
} | |
} | |
#demo1, | |
#demo2, | |
#demo3 , | |
#btnClear { | |
position: absolute; | |
z-index: 1000; | |
left: 5px; | |
bottom: 40px; | |
} | |
#demo2 { | |
left: 5px; | |
bottom: 10px; | |
} | |
#demo3 { | |
left: 110px; | |
bottom: 10px; | |
} | |
#btnClear { | |
left: 110px; | |
} | |
#stage { | |
position: relative; | |
width: 100%; | |
height: 100%; | |
-webkit-perspective: 1000px; | |
perspective: 1000px; | |
* { | |
-webkit-transform-style: preserve-3d; | |
transform-style: preserve-3d; | |
} | |
} | |
.Object3D { | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
} | |
$base: 50px; | |
.rubikcube { | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
margin-left: -$base * 3 / 2; | |
margin-top: -$base * 3 / 2; | |
width: $base * 3; | |
height: $base * 3; | |
} | |
.line { | |
position: absolute; | |
border-top: solid 1px blue; | |
left: 50%; | |
top: 50%; | |
width: 100%; | |
height: 0; | |
} | |
.face { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
background-color: #000; | |
text-align: center; | |
-webkit-backface-visibility: hidden; | |
backface-visibility: hidden; | |
&.on { | |
z-index: 100; | |
.controller { | |
display: block; | |
} | |
} | |
.controller { | |
display: none; | |
position: absolute; | |
left: 0; | |
top: 0; | |
z-index: 100; | |
width: 100%; | |
height: 100%; | |
> div { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
opacity: 0.65; | |
// background-color: rgba(255, 255, 255, 0.5); | |
} | |
$pos: -99%; | |
.up { | |
top: $pos; | |
background: url(http://jsrun.it/assets/p/G/o/w/pGowz.png) center center no-repeat; | |
} | |
.down { | |
bottom: $pos; | |
background: url(http://jsrun.it/assets/o/4/g/G/o4gGv.png) center center no-repeat; | |
} | |
.left { | |
left: $pos; | |
background: url(http://jsrun.it/assets/a/7/w/S/a7wS7.png) center center no-repeat; | |
} | |
.right { | |
right: $pos; | |
background: url(http://jsrun.it/assets/3/s/Q/v/3sQvb.png) center center no-repeat; | |
} | |
} | |
$margin: 2px; | |
.faceBgColor { | |
position: absolute; | |
left: $margin; | |
right: $margin; | |
top: $margin; | |
bottom: $margin; | |
display: block; | |
background-color: #000; | |
border-radius: 8px; | |
} | |
.faceColor { | |
cursor: pointer; | |
position: absolute; | |
left: $margin; | |
right: $margin; | |
top: $margin; | |
bottom: $margin; | |
display: block; | |
background-color: #ccc; | |
border-radius: 8px; | |
opacity: 0.8; | |
} | |
&:hover { | |
.faceColor { | |
opacity: 1 !important; | |
} | |
} | |
} | |
.cube { | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
width: $base; | |
height: $base; | |
margin-top: -$base / 2; | |
margin-left: -$base / 2; | |
&.on { | |
z-index: 100; | |
} | |
p { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
text-align: center; | |
line-height: $base; | |
} | |
} | |
.timer { | |
position: absolute; | |
right: 5px; | |
bottom: 5px; | |
color: #fff; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="ctrl"> | |
<p id="top">TOP</p> | |
<p id="bottom">BOTTOM</p> | |
<p id="front">FRONT</p> | |
<p id="back">BACK</p> | |
<p id="left">LEFT</p> | |
<p id="right">RIGHT</p> | |
<p id="middle1">MID1</p> | |
<p id="middle2">MID2</p> | |
<p id="middle3">MID3</p> | |
<!-- /#ctrl --></div> | |
<div id="demo1"><input type="button" id="btnDemo1" value="Auto demo Start" /></div> | |
<div id="clear"><input type="button" id="btnClear" value="Forced clear" /></div> | |
<div id="demo2"><input type="button" id="btnDemo2" value="Go to demo1" /></div> | |
<div id="demo3"><input type="button" id="btnDemo3" value="Go to demo2" /></div> | |
<div id="stage"></div> | |
<script id="template-face" type="text/x-template"> | |
<span class="faceBgColor"><span class="faceColor"></span></span> | |
</script> | |
<script id="template-face-controller" type="text/x-template"> | |
<div class="controller"> | |
<div data-dir="up" class="up"></div> | |
<div data-dir="down" class="down"></div> | |
<div data-dir="left" class="left"></div> | |
<div data-dir="right" class="right"></div> | |
</div> | |
</script> | |
<script id="template-cube" type="text/x-template"> | |
<div class="face top"><span class="faceBgColor"><span class="faceColor">T</span></span></div> | |
<div class="face bottom"><span class="faceBgColor"><span class="faceColor">Bo</span></span></div> | |
<div class="face left"><span class="faceBgColor"><span class="faceColor">L</span></span></div> | |
<div class="face right"><span class="faceBgColor"><span class="faceColor">R</span></span></div> | |
<div class="face front"><span class="faceBgColor"><span class="faceColor">F</span></span></div> | |
<div class="face back"><span class="faceBgColor"><span class="faceColor">Ba</span></span></div> | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (win, doc, exports, undefined) { | |
'use strict'; | |
var fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; | |
function Class() { /* noop. */ } | |
Class.extend = function (props) { | |
var SuperClass = this; | |
function Class() { | |
if (typeof this.init === 'function') { | |
this.init.apply(this, arguments); | |
} | |
} | |
Class.prototype = Object.create(SuperClass.prototype, { | |
constructor: { | |
value: Class, | |
writable: true, | |
configurable: true | |
} | |
}); | |
Object.keys(props).forEach(function (key) { | |
var prop = props[key], | |
_super = SuperClass.prototype[key], | |
isMethodOverride = (typeof prop === 'function' && typeof _super === 'function' && fnTest.test(prop)); | |
if (isMethodOverride) { | |
Class.prototype[key] = function () { | |
var ret, | |
tmp = this._super; | |
Object.defineProperty(this, '_super', { | |
value: _super, | |
configurable: true | |
}); | |
ret = prop.apply(this, arguments); | |
Object.defineProperty(this, '_super', { | |
value: tmp, | |
configurable: true | |
}); | |
return ret; | |
}; | |
} | |
else { | |
Class.prototype[key] = prop; | |
} | |
}); | |
Class.extend = SuperClass.extend; | |
return Class; | |
}; | |
exports.Class = Class; | |
}(window, window.document, window)); | |
;(function (win, doc, exports) { | |
var cos = Math.cos, | |
sin = Math.sin; | |
function epsilon(value) { | |
return Math.abs(value) < 0.000001 ? 0 : value; | |
} | |
function Matrix4(){} | |
Matrix4.createMatrix = function () { | |
return this.identity(this.create()); | |
}; | |
Matrix4.create = function () { | |
return new Float32Array(16); | |
}; | |
Matrix4.copy = function (mat, dest) { | |
dest[0] = mat[0]; dest[1] = mat[1]; dest[2] = mat[2]; dest[3] = mat[3]; | |
dest[4] = mat[4]; dest[5] = mat[5]; dest[6] = mat[6]; dest[7] = mat[7]; | |
dest[8] = mat[8]; dest[9] = mat[9]; dest[10] = mat[10]; dest[11] = mat[11]; | |
dest[12] = mat[12]; dest[13] = mat[13]; dest[14] = mat[14]; dest[15] = mat[15]; | |
return dest; | |
}; | |
Matrix4.identity = function (dest) { | |
dest[0] = 1; dest[1] = 0; dest[2] = 0; dest[3] = 0; | |
dest[4] = 0; dest[5] = 1; dest[6] = 0; dest[7] = 0; | |
dest[8] = 0; dest[9] = 0; dest[10] = 1; dest[11] = 0; | |
dest[12] = 0; dest[13] = 0; dest[14] = 0; dest[15] = 1; | |
return dest; | |
}; | |
Matrix4.multiply = function (mat1, mat2, dest) { | |
var a = mat1[0], b = mat1[1], c = mat1[2], d = mat1[3], | |
e = mat1[4], f = mat1[5], g = mat1[6], h = mat1[7], | |
i = mat1[8], j = mat1[9], k = mat1[10], l = mat1[11], | |
m = mat1[12], n = mat1[13], o = mat1[14], p = mat1[15], | |
A = mat2[0], B = mat2[1], C = mat2[2], D = mat2[3], | |
E = mat2[4], F = mat2[5], G = mat2[6], H = mat2[7], | |
I = mat2[8], J = mat2[9], K = mat2[10], L = mat2[11], | |
M = mat2[12], N = mat2[13], O = mat2[14], P = mat2[15]; | |
dest[0] = A * a + B * e + C * i + D * m; | |
dest[1] = A * b + B * f + C * j + D * n; | |
dest[2] = A * c + B * g + C * k + D * o; | |
dest[3] = A * d + B * h + C * l + D * p; | |
dest[4] = E * a + F * e + G * i + H * m; | |
dest[5] = E * b + F * f + G * j + H * n; | |
dest[6] = E * c + F * g + G * k + H * o; | |
dest[7] = E * d + F * h + G * l + H * p; | |
dest[8] = I * a + J * e + K * i + L * m; | |
dest[9] = I * b + J * f + K * j + L * n; | |
dest[10] = I * c + J * g + K * k + L * o; | |
dest[11] = I * d + J * h + K * l + L * p; | |
dest[12] = M * a + N * e + O * i + P * m; | |
dest[13] = M * b + N * f + O * j + P * n; | |
dest[14] = M * c + N * g + O * k + P * o; | |
dest[15] = M * d + N * h + O * l + P * p; | |
return dest; | |
}; | |
Matrix4.scale = function (mat, vec, dest) { | |
dest[0] = mat[0] * vec[0]; | |
dest[1] = mat[1] * vec[0]; | |
dest[2] = mat[2] * vec[0]; | |
dest[3] = mat[3] * vec[0]; | |
dest[4] = mat[4] * vec[1]; | |
dest[5] = mat[5] * vec[1]; | |
dest[6] = mat[6] * vec[1]; | |
dest[7] = mat[7] * vec[1]; | |
dest[8] = mat[8] * vec[2]; | |
dest[9] = mat[9] * vec[2]; | |
dest[10] = mat[10] * vec[2]; | |
dest[11] = mat[11] * vec[2]; | |
dest[12] = mat[12]; | |
dest[13] = mat[13]; | |
dest[14] = mat[14]; | |
dest[15] = mat[15]; | |
return dest; | |
}; | |
Matrix4.lookAt = function (eye, center, up, dest) { | |
var eyeX = eye[0], eyeY = eye[1], eyeZ = eye[2], | |
upX = up[0], upY = up[1], upZ = up[2], | |
centerX = center[0], centerY = center[1], centerZ = center[2]; | |
if(eyeX == centerX && eyeY == centerY && eyeZ == centerZ){return this.identity(dest);} | |
var x0, x1, x2, y0, y1, y2, z0, z1, z2, l; | |
z0 = eyeX - center[0]; z1 = eyeY - center[1]; z2 = eyeZ - center[2]; | |
l = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); | |
z0 *= l; z1 *= l; z2 *= l; | |
x0 = upY * z2 - upZ * z1; | |
x1 = upZ * z0 - upX * z2; | |
x2 = upX * z1 - upY * z0; | |
l = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); | |
if(!l){ | |
x0 = 0; x1 = 0; x2 = 0; | |
} | |
else { | |
l = 1 / l; | |
x0 *= l; x1 *= l; x2 *= l; | |
} | |
y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; | |
l = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); | |
if(!l){ | |
y0 = 0; y1 = 0; y2 = 0; | |
} | |
else { | |
l = 1 / l; | |
y0 *= l; y1 *= l; y2 *= l; | |
} | |
dest[0] = x0; dest[1] = y0; dest[2] = z0; dest[3] = 0; | |
dest[4] = x1; dest[5] = y1; dest[6] = z1; dest[7] = 0; | |
dest[8] = x2; dest[9] = y2; dest[10] = z2; dest[11] = 0; | |
dest[12] = -(x0 * eyeX + x1 * eyeY + x2 * eyeZ); | |
dest[13] = -(y0 * eyeX + y1 * eyeY + y2 * eyeZ); | |
dest[14] = -(z0 * eyeX + z1 * eyeY + z2 * eyeZ); | |
dest[15] = 1; | |
return dest; | |
}; | |
Matrix4.perspective = function (fovy, aspect, near, far, dest) { | |
var t = near * Math.tan(fovy * Math.PI / 360); | |
var r = t * aspect; | |
var a = r * 2, b = t * 2, c = far - near; | |
dest[0] = near * 2 / a; | |
dest[1] = 0; | |
dest[2] = 0; | |
dest[3] = 0; | |
dest[4] = 0; | |
dest[5] = near * 2 / b; | |
dest[6] = 0; | |
dest[7] = 0; | |
dest[8] = 0; | |
dest[9] = 0; | |
dest[10] = -(far + near) / c; | |
dest[11] = -1; | |
dest[12] = 0; | |
dest[13] = 0; | |
dest[14] = -(far * near * 2) / c; | |
dest[15] = 0; | |
return dest; | |
}; | |
Matrix4.translate = function (mat, vec, dest) { | |
dest[0] = mat[0]; dest[1] = mat[1]; dest[2] = mat[2]; dest[3] = mat[3]; | |
dest[4] = mat[4]; dest[5] = mat[5]; dest[6] = mat[6]; dest[7] = mat[7]; | |
dest[8] = mat[8]; dest[9] = mat[9]; dest[10] = mat[10]; dest[11] = mat[11]; | |
dest[12] = mat[0] * vec[0] + mat[4] * vec[1] + mat[8] * vec[2] + mat[12]; | |
dest[13] = mat[1] * vec[0] + mat[5] * vec[1] + mat[9] * vec[2] + mat[13]; | |
dest[14] = mat[2] * vec[0] + mat[6] * vec[1] + mat[10] * vec[2] + mat[14]; | |
dest[15] = mat[3] * vec[0] + mat[7] * vec[1] + mat[11] * vec[2] + mat[15]; | |
return dest; | |
}; | |
Matrix4.rotate = function (mat, angle, axis, dest) { | |
var sq = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]); | |
if(!sq){return null;} | |
var a = axis[0], b = axis[1], c = axis[2]; | |
//normalize. | |
if(sq != 1) { | |
sq = 1 / sq; | |
a *= sq; | |
b *= sq; | |
c *= sq; | |
} | |
var d = sin(angle), e = cos(angle), f = 1 - e, | |
g = mat[0], h = mat[1], i = mat[2], j = mat[3], | |
k = mat[4], l = mat[5], m = mat[6], n = mat[7], | |
o = mat[8], p = mat[9], q = mat[10], r = mat[11], | |
//m11 | |
s = a * a * f + e, | |
//m12 | |
t = b * a * f + c * d, | |
//m13 | |
u = c * a * f - b * d, | |
//m21 | |
v = a * b * f - c * d, | |
//m22 | |
w = b * b * f + e, | |
//m23 | |
x = c * b * f + a * d, | |
//m31 | |
y = a * c * f + b * d, | |
//m32 | |
z = b * c * f - a * d, | |
//m33 | |
A = c * c * f + e; | |
if(angle){ | |
if(mat != dest){ | |
dest[12] = mat[12]; dest[13] = mat[13]; | |
dest[14] = mat[14]; dest[15] = mat[15]; | |
} | |
} | |
else { | |
dest = mat; | |
} | |
dest[0] = g * s + k * t + o * u; | |
dest[1] = h * s + l * t + p * u; | |
dest[2] = i * s + m * t + q * u; | |
dest[3] = j * s + n * t + r * u; | |
dest[4] = g * v + k * w + o * x; | |
dest[5] = h * v + l * w + p * x; | |
dest[6] = i * v + m * w + q * x; | |
dest[7] = j * v + n * w + r * x; | |
dest[8] = g * y + k * z + o * A; | |
dest[9] = h * y + l * z + p * A; | |
dest[10] = i * y + m * z + q * A; | |
dest[11] = j * y + n * z + r * A; | |
return dest; | |
}; | |
Matrix4.inverse = function (mat, dest) { | |
var a = mat[0], b = mat[1], c = mat[2], d = mat[3], | |
e = mat[4], f = mat[5], g = mat[6], h = mat[7], | |
i = mat[8], j = mat[9], k = mat[10], l = mat[11], | |
m = mat[12], n = mat[13], o = mat[14], p = mat[15], | |
q = a * f - b * e, r = a * g - c * e, | |
s = a * h - d * e, t = b * g - c * f, | |
u = b * h - d * f, v = c * h - d * g, | |
w = i * n - j * m, x = i * o - k * m, | |
y = i * p - l * m, z = j * o - k * n, | |
A = j * p - l * n, B = k * p - l * o, | |
ivd = 1 / (q * B - r * A + s * z + t * y - u * x + v * w); | |
dest[0] = ( f * B - g * A + h * z) * ivd; | |
dest[1] = (-b * B + c * A - d * z) * ivd; | |
dest[2] = ( n * v - o * u + p * t) * ivd; | |
dest[3] = (-j * v + k * u - l * t) * ivd; | |
dest[4] = (-e * B + g * y - h * x) * ivd; | |
dest[5] = ( a * B - c * y + d * x) * ivd; | |
dest[6] = (-m * v + o * s - p * r) * ivd; | |
dest[7] = ( i * v - k * s + l * r) * ivd; | |
dest[8] = ( e * A - f * y + h * w) * ivd; | |
dest[9] = (-a * A + b * y - d * w) * ivd; | |
dest[10] = ( m * u - n * s + p * q) * ivd; | |
dest[11] = (-i * u + j * s - l * q) * ivd; | |
dest[12] = (-e * z + f * x - g * w) * ivd; | |
dest[13] = ( a * z - b * x + c * w) * ivd; | |
dest[14] = (-m * t + n * r - o * q) * ivd; | |
dest[15] = ( i * t - j * r + k * q) * ivd; | |
return dest; | |
}; | |
Matrix4.toString = function (mat) { | |
var ret = ''; | |
var tmp = ''; | |
for (var i = 0, l = mat.length; i < l; i++) { | |
tmp = '' + mat[i]; | |
if (/e/i.test(tmp)) { | |
tmp = '' + (mat[i] | 0); | |
} | |
ret += (i !== 0 ? ',' : '') + tmp; | |
} | |
return ret; | |
}; | |
Matrix4.getCameraCSSMatrix = function (mat) { | |
//this.transformToCSSMatrix(mat, mat); | |
return 'matrix3d(' + | |
epsilon(mat[0]) + ',' + | |
epsilon(mat[1]) + ',' + | |
epsilon(mat[2]) + ',' + | |
epsilon(mat[3]) + ',' + | |
epsilon(mat[4]) + ',' + | |
epsilon(mat[5]) + ',' + | |
epsilon(mat[6]) + ',' + | |
epsilon(mat[7]) + ',' + | |
epsilon(mat[8]) + ',' + | |
epsilon(mat[9]) + ',' + | |
epsilon(mat[10]) + ',' + | |
epsilon(mat[11]) + ',' + | |
epsilon(mat[12]) + ',' + | |
epsilon(mat[13]) + ',' + | |
epsilon(mat[14]) + ',' + | |
epsilon(mat[15]) + | |
')'; | |
}; | |
Matrix4.transformToCSSMatrix = function (mat, dest) { | |
dest[0] = epsilon(mat[0]); | |
dest[1] = epsilon(-mat[1]); | |
dest[2] = epsilon(mat[2]); | |
dest[3] = epsilon(mat[3]); | |
dest[4] = epsilon(mat[4]); | |
dest[5] = epsilon(-mat[5]); | |
dest[6] = epsilon(mat[6]); | |
dest[7] = epsilon(mat[7]); | |
dest[8] = epsilon(mat[8]); | |
dest[9] = epsilon(-mat[9]); | |
dest[10] = epsilon(mat[10]); | |
dest[11] = epsilon(mat[11]); | |
dest[12] = epsilon(mat[12]); | |
dest[13] = epsilon(-mat[13]); | |
dest[14] = epsilon(mat[14]); | |
dest[15] = epsilon(mat[15]); | |
return dest; | |
}; | |
Matrix4.prototype = { | |
constructor: Matrix4, | |
createMatrix: Matrix4.createMatrix, | |
create: Matrix4.create, | |
copy: Matrix4.copy, | |
identity: Matrix4.identity, | |
multiply: Matrix4.multiply, | |
scale: Matrix4.scale, | |
lookAt: Matrix4.lookAt, | |
perspective: Matrix4.perspective, | |
translate: Matrix4.translate, | |
rotate: Matrix4.rotate, | |
inverse: Matrix4.inverse, | |
toString: Matrix4.toString, | |
getCameraCSSMatrix: Matrix4.getCameraCSSMatrix, | |
transformToCSSMatrix: Matrix4.transformToCSSMatrix | |
}; | |
exports.Matrix4 = Matrix4; | |
}(window, window.document, window)); | |
;(function (win, doc, Class, exports) { | |
var EventDispatcher = Class.extend({ | |
/** | |
* @param {string} typ | |
* @param {?Object=} opt_evt | |
* @return {void} | |
*/ | |
trigger: function (typ, opt_evt) { | |
if (!typ) { | |
throw "INVALID EVENT TYPE " + typ; | |
} | |
var obj = this.handlers || (this.handlers = {}), | |
arr = [].concat(obj[typ] || []), //Use copy | |
evt = opt_evt || {}, | |
len, i, fnc; | |
evt.type || (evt.type = typ); | |
// handle specified event type | |
for (i = 0, len = arr.length; i < len; ++i) { | |
(fnc = arr[i][0]) && fnc.call(arr[i][1] || this, this, evt); | |
} | |
// handle wildcard "*" event | |
arr = obj["*"] || []; | |
for (i = 0, len = arr.length; i < len; ++i) { | |
(fnc = arr[i][0]) && fnc.call(arr[i][1] || this, this, evt); | |
} | |
}, | |
/** | |
* @param {string} typ | |
* @param {function(evt:Object):void} fnc | |
* @param {Object} [context] if would like to be called context is set this param. | |
* @return {void} | |
*/ | |
on: function (typ, fnc, context) { | |
if (!typ) { | |
throw "addEventListener:INVALID EVENT TYPE " + typ + " " + fnc; | |
} | |
var obj = this.handlers || (this.handlers = {}); | |
(obj[typ] || (obj[typ] = [])).push([fnc, context]); | |
}, | |
/** | |
* @param {string} typ | |
* @param {function(evt:object):void} fnc | |
*/ | |
off: function (typ, fnc) { | |
if (!typ) { | |
throw "removeEventListener:INVALID EVENT TYPE " + typ + " " + fn; | |
} | |
var obj = this.handlers || (this.handlers = {}), | |
arr = obj[typ] || [], | |
i = arr.length; | |
while(i) { | |
arr[--i][0] === fnc && arr.splice(i, 1); | |
} | |
}, | |
one: function (typ, fnc, context) { | |
var self = this; | |
function _fnc() { | |
self.removeEventListener(typ, _fnc, context); | |
fnc.apply(context || self, arguments); | |
} | |
this.addEventListener(typ, _fnc, context); | |
} | |
}); | |
exports.EventDispatcher = EventDispatcher; | |
}(window, window.document, window.Class, window)); | |
;(function (win, doc, Matrix4, EventDispatcher) { | |
var PI = Math.PI; | |
var DEG_TO_RAD = PI / 180; | |
var Object3D = EventDispatcher.extend({ | |
className: 'Object3D', | |
init: function () { | |
this.el = doc.createElement('div'); | |
this.el.className = this.className; | |
this.el.CSS3DObject = this; | |
this.children = []; | |
this.position = new CSS3D.Vector3(0, 0, 0); | |
this.m = new Matrix4(); | |
this.matrix = Matrix4.createMatrix(); | |
this.matrixWorld = Matrix4.createMatrix(); | |
this.translateMatrix = Matrix4.createMatrix(); | |
this.scaleMatrix = Matrix4.createMatrix(); | |
this.rotateMatrix = Matrix4.createMatrix(); | |
this.updateMatrixWorld(); | |
}, | |
/** | |
* Add object. | |
* @param {Object3D} object | |
*/ | |
add: function (object) { | |
if (this === object) { | |
return null; | |
} | |
if (object.parent != null) { | |
object.parent.remove(object); | |
} | |
this.children.push(object); | |
object.parent = this; | |
this.el.appendChild(object.el); | |
}, | |
/** | |
* Remove object. | |
* @param {Object3D} object | |
* @return {Object3D} | |
*/ | |
remove: function (object) { | |
var index, ret; | |
if (this === object) { | |
return null; | |
} | |
index = this.children.indexOf(object); | |
if (index === -1) { | |
return null; | |
} | |
ret = this.children.splice(index, 1); | |
return ret; | |
}, | |
updateMatrix: function () { | |
//各種行列のかける順番(OpenGL方式・列オーダーの場合) | |
//translate * rotation * scale | |
var mat = Matrix4.createMatrix(); | |
Matrix4.multiply(mat, this.translateMatrix, mat); | |
Matrix4.multiply(mat, this.rotateMatrix, mat); | |
Matrix4.multiply(mat, this.scaleMatrix, mat); | |
this.matrix = mat; | |
this.updateMatrixWorld(); | |
}, | |
updateMatrixWorld: function () { | |
if (!this.parent) { | |
Matrix4.copy(this.matrix, this.matrixWorld); | |
} | |
else { | |
Matrix4.multiply(this.parent.matrixWorld, this.matrix, this.matrixWorld); | |
} | |
var children = this.children; | |
for (var i = 0, l = children.length; i < l; i++) { | |
children[i].updateMatrixWorld(); | |
} | |
}, | |
updateStyle: function () { | |
if ('transform' in this.el.style) { | |
this.el.style.transform = this.m.getCameraCSSMatrix(this.matrix); | |
} | |
else { | |
this.el.style.webkitTransform = this.m.getCameraCSSMatrix(this.matrix); | |
} | |
}, | |
scale: function (scale) { | |
Matrix4.scale(this.scaleMatrix, scale, this.scaleMatrix); | |
this.updateMatrix(); | |
this.updateStyle(); | |
}, | |
translate: function (translate) { | |
Matrix4.translate(this.translateMatrix, translate, this.translateMatrix); | |
this.updateMatrix(); | |
this.updateStyle(); | |
}, | |
rotateX: function (angle) { | |
var rad = angle * DEG_TO_RAD; | |
Matrix4.rotate(this.rotateMatrix, rad, [1, 0, 0], this.rotateMatrix); | |
this.updateMatrix(); | |
this.updateStyle(); | |
}, | |
rotateY: function (angle) { | |
var rad = angle * DEG_TO_RAD; | |
Matrix4.rotate(this.rotateMatrix, rad, [0, 1, 0], this.rotateMatrix); | |
this.updateMatrix(); | |
this.updateStyle(); | |
}, | |
rotateZ: function (angle) { | |
var rad = angle * DEG_TO_RAD; | |
Matrix4.rotate(this.rotateMatrix, rad, [0, 0, 1], this.rotateMatrix); | |
this.updateMatrix(); | |
this.updateStyle(); | |
} | |
}); | |
window.CSS3D = { | |
Object3D: Object3D | |
}; | |
}(window, window.document, window.Matrix4, window.EventDispatcher)); | |
;(function (win, doc, CSS3D, Class) { | |
/** | |
* Vector3 class | |
* @constructor | |
* @param {number} x Position of x. | |
* @param {number} y Position of y. | |
* @param {number} z Position of z. | |
*/ | |
var Vector3 = Class.extend({ | |
init: function (x, y, z) { | |
this.x = x || 0; | |
this.y = y || 0; | |
this.z = z || 0; | |
}, | |
zero: function () { | |
this.x = this.y = this.z = 0; | |
return this; | |
}, | |
equal: function (v) { | |
return (this.x === v.x) && (this.y === v.y) && (this.z === v.z); | |
}, | |
set: function (x, y, z) { | |
this.x = x || 0; | |
this.y = y || 0; | |
this.z = z || 0; | |
return this; | |
}, | |
sub: function (v) { | |
this.x -= v.x; | |
this.y -= v.y; | |
this.z -= v.z; | |
return this | |
}, | |
subVectors: function (a, b) { | |
this.x = a.x - b.x; | |
this.y = a.y - b.y; | |
this.z = a.z - b.z; | |
return this; | |
}, | |
add: function (v) { | |
this.x += v.x; | |
this.y += v.y; | |
this.z += v.z; | |
return this; | |
}, | |
addVectors: function (a, b) { | |
this.x = a.x + b.x; | |
this.y = a.y + b.y; | |
this.z = a.z + b.z; | |
return this; | |
}, | |
copy: function (v) { | |
this.x = v.x; | |
this.y = v.y; | |
this.z = v.z; | |
return this; | |
}, | |
norm: function () { | |
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); | |
}, | |
normalize: function () { | |
var nrm = this.norm(); | |
if (nrm !== 0) { | |
nrm = 1 / nrm; | |
this.x *= nrm; | |
this.y *= nrm; | |
this.z *= nrm; | |
} | |
return this; | |
}, | |
multiply: function (v) { | |
this.x *= v.x; | |
this.y *= v.y; | |
this.z *= v.z; | |
return this; | |
}, | |
//scalar multiplication | |
multiplyScalar: function (s) { | |
this.x *= s; | |
this.y *= s; | |
this.z *= s; | |
return this; | |
}, | |
multiplyVectors: function (a, b) { | |
this.x = a.x * b.x; | |
this.y = a.y * b.y; | |
this.z = a.z * b.z; | |
return this; | |
}, | |
//dot product | |
dot: function (v) { | |
return this.x * v.x + this.y * v.y + this.z * v.z; | |
}, | |
cross: function (v, w) { | |
if (w) { | |
return this.crossVectors(v, w); | |
} | |
x = this.x; | |
y = this.y; | |
z = this.z; | |
this.x = (y * v.z) - (z * v.y); | |
this.y = (z * v.x) - (x * v.z); | |
this.z = (x * v.y) - (y * v.x); | |
return this; | |
}, | |
//cross product | |
crossVectors: function (v, w) { | |
this.x = (w.y * v.z) - (w.z * v.y); | |
this.y = (w.z * v.x) - (w.x * v.z); | |
this.z = (w.x * v.y) - (w.y * v.x); | |
return this; | |
}, | |
applyMatrix4: function (m) { | |
x = this.x; | |
y = this.y; | |
z = this.z; | |
this.x = m[0] * x + m[4] * y + m[8] * z + m[12]; | |
this.y = m[1] * x + m[5] * y + m[9] * z + m[13]; | |
this.z = m[2] * x + m[6] * y + m[10] * z + m[14]; | |
return this; | |
}, | |
applyMatrix4withoutW: function (m) { | |
var clone = this.clone(); | |
x = clone.x; | |
y = clone.y; | |
z = clone.z; | |
clone.x = m[0] * x + m[4] * y + m[8] * z; | |
clone.y = m[1] * x + m[5] * y + m[9] * z; | |
clone.z = m[2] * x + m[6] * y + m[10] * z; | |
return clone; | |
}, | |
clone: function () { | |
var vec3 = new Vector3(); | |
vec3.copy(this); | |
return vec3; | |
}, | |
toArray: function () { | |
return [this.x, this.y, this.z]; | |
}, | |
toString: function () { | |
return '' + this.x + ',' + this.y + ',' + this.z; | |
} | |
}); | |
CSS3D.Vector3 = Vector3; | |
}(window, window.document, window.CSS3D, window.Class)); | |
;(function (win, doc, CSS3D, Matrix4, Class) { | |
var PI = Math.PI; | |
var DEG_TO_RAD = PI / 180; | |
var Cube = CSS3D.Object3D.extend({ | |
className: 'cube', | |
cubeId: null, | |
/** | |
* @param {number} size width and height size. | |
* @param {number} n RubikCube count. | |
* @param {number} index current index number. | |
*/ | |
init: function (size, n, index) { | |
this.pseudoMatrixWorld = Matrix4.createMatrix(); | |
this._super(); | |
size = size || 50; | |
var _2n = n * 2; | |
var _n2 = n * n; | |
var _2n2 = (n * n) * 2; | |
var opt = {}; | |
var noOpt = { | |
color: 'black' | |
}; | |
//var top = new CSS3D.Face(size, size, 'blue', 'http://placekitten.com/' + size + '/' + size); | |
//For TOP. | |
if (index % _n2 < n) { | |
opt = { | |
color: 'white', | |
useController: true | |
}; | |
} | |
else { | |
opt = noOpt; | |
} | |
var top = new CSS3D.Face(size, size, opt); | |
top.rotateX(90); | |
top.translate([0, -size / 2, 0]); | |
this.add(top); | |
//For BOTTOM. | |
if (index % _n2 >= _2n) { | |
opt = { | |
color: 'blue', | |
useController: true | |
}; | |
} | |
else { | |
opt = noOpt; | |
} | |
var bottom = new CSS3D.Face(size, size, opt); | |
bottom.rotateX(-90); | |
bottom.translate([0, size / 2, 0]); | |
this.add(bottom); | |
//For FRONT. | |
if (_2n2 <= index) { | |
opt = { | |
color: 'orange', | |
useController: true | |
}; | |
} | |
else { | |
opt = noOpt; | |
} | |
var front = new CSS3D.Face(size, size, opt); | |
front.translate([0, 0, size / 2]); | |
this.add(front); | |
//For BACK. | |
if (_n2 > index) { | |
opt = { | |
color: 'red', | |
useController: true | |
}; | |
} | |
else { | |
opt = noOpt; | |
} | |
var back = new CSS3D.Face(size, size, opt); | |
back.rotateY(180); | |
back.translate([0, 0, -size / 2]); | |
this.add(back); | |
//For LEFT. | |
if (index % n === 0) { | |
opt = { | |
color: 'yellow', | |
useController: true | |
}; | |
} | |
else { | |
opt = noOpt; | |
} | |
var left = new CSS3D.Face(size, size, opt); | |
left.rotateY(-90); | |
left.translate([-size / 2, 0, 0]); | |
this.add(left); | |
//For RIGHT. | |
if (index % n === (n - 1)) { | |
opt = { | |
color: 'green', | |
useController: true | |
}; | |
} | |
else { | |
opt = noOpt; | |
} | |
var right = new CSS3D.Face(size, size, opt); | |
right.rotateY(90); | |
right.translate([size / 2, 0, 0]); | |
this.add(right); | |
this.el.style.width = this.el.style.height = size + 'px'; | |
this.el.style.marginTop = this.el.style.marginLeft = -(size / 2) + 'px'; | |
}, | |
updateStyle: (function () { | |
var mat = Matrix4.createMatrix(); | |
return function () { | |
Matrix4.multiply(this.pseudoMatrixWorld, this.matrix, mat); | |
var propName = ('transform' in this.el.style) ? 'transform' : 'webkitTransform'; | |
this.el.style[propName] = Matrix4.getCameraCSSMatrix(mat); | |
}; | |
}()), | |
rotateWorldX: function (angle) { | |
this.rotateWorld(angle, [1, 0, 0]); | |
}, | |
rotateWorldY: function (angle) { | |
this.rotateWorld(angle, [0, 1, 0]); | |
}, | |
rotateWorldZ: function (angle) { | |
this.rotateWorld(angle, [0, 0, 1]); | |
}, | |
rotateWorld: function (angle, axis) { | |
var rad = angle * DEG_TO_RAD; | |
var rotMat = Matrix4.createMatrix(); | |
var pseudoMatrixWorld = this.pseudoMatrixWorld; | |
Matrix4.rotate(rotMat, rad, axis, rotMat); | |
Matrix4.multiply(rotMat, pseudoMatrixWorld, pseudoMatrixWorld); | |
this.updateMatrixWorld(); | |
this.updateStyle(); | |
}, | |
/** @override */ | |
updateMatrixWorld: (function () { | |
var mat = Matrix4.createMatrix(); | |
return function () { | |
if (!this.parent) { | |
Matrix4.multiply(this.pseudoMatrixWorld, this.matrix, mat); | |
Matrix4.copy(mat, this.matrixWorld); | |
} | |
else { | |
Matrix4.multiply(this.parent.matrixWorld, this.pseudoMatrixWorld, mat); | |
Matrix4.multiply(mat, this.matrix, this.matrixWorld); | |
} | |
var children = this.children; | |
for (var i = 0, l = children.length; i < l; i++) { | |
children[i].updateMatrixWorld(); | |
} | |
} | |
}()) | |
}); | |
CSS3D.Cube = Cube; | |
}(window, window.document, window.CSS3D, window.Matrix4, window.Class)); | |
;(function (win, doc, CSS3D, Matrix4, Class) { | |
var DirectionalLight = CSS3D.Object3D.extend({ | |
init: function (vector) { | |
vector = vector || new CSS3D.Vector3(1, 1, 1); | |
this.directorn = vector.normalize(); | |
}, | |
/** | |
* Calucrate light strength. | |
* @param {CSS3D.Object3D} object | |
*/ | |
calc: function (object) { | |
if (!(object instanceof CSS3D.Face)) { | |
var children = object.children; | |
for (var i = 0, l = children.length; i < l; i++) { | |
this.calc(children[i]); | |
} | |
} | |
else { | |
if (!object.useController) { | |
return; | |
} | |
var normal = object.getNormal(); | |
var strength = normal.dot(this.directorn); | |
if (strength < 0) { | |
strength = 0; | |
} | |
object.setOpacity(strength); | |
} | |
} | |
}); | |
CSS3D.DirectionalLight = DirectionalLight; | |
}(window, window.document, window.CSS3D, window.Matrix4, window.Class)); | |
;(function (win, doc, CSS3D, Matrix4, Class) { | |
var isTouch = 'ontouchstart' in window; | |
var M_DOWN = (isTouch) ? 'touchstart' : 'mousedown'; | |
var M_UP = (isTouch) ? 'touchend' : 'mouseup'; | |
var M_MOVE = (isTouch) ? 'touchmove' : 'mousemove'; | |
function getTemplate (id) { | |
var ele = doc.getElementById('template-' + id); | |
if (ele) { return ele.innerHTML; } | |
return ''; | |
} | |
/** | |
* Get current axis direction. | |
* @param {CSS3D.Vector3} axis | |
* @return {string} current axis direction. | |
*/ | |
function getCurAxisDir (axis) { | |
if (Math.abs(axis.x) > 0.00001) { | |
return 'x'; | |
} | |
else if (Math.abs(axis.y) > 0.00001) { | |
return 'y'; | |
} | |
else { | |
return 'z'; | |
} | |
} | |
var Face = CSS3D.Object3D.extend({ | |
className: 'face', | |
init: function (width, height, opt) { | |
this._super(); | |
opt || (opt = {}); | |
this.color = opt.color || '#000'; | |
this.textureSource = opt.textureSource; | |
this.width = width || 50; | |
this.height = height || 50; | |
this.useController = opt.useController; | |
var rangeCtrl = doc.createRange(); | |
var range = doc.createRange(); | |
if (this.useController) { | |
rangeCtrl.selectNodeContents(doc.body); | |
this.el.appendChild(rangeCtrl.createContextualFragment(getTemplate('face-controller'))); | |
rangeCtrl.detach(); | |
rangeCtrl = null; | |
} | |
range.selectNodeContents(doc.body); | |
this.el.appendChild(range.createContextualFragment(getTemplate('face'))); | |
range.detach(); | |
range = null; | |
var faceEl = this.faceEl = this.el.querySelector('.faceColor'); | |
this.setEvents(); | |
if (opt.textureSource) { | |
this.texture = new Image(); | |
this.texture.width = width; | |
this.texture.height = height; | |
this.texture.onload = function () { | |
faceEl.appendChild(this); | |
}; | |
this.texture.src = textureSource; | |
} | |
this.el.style.width = width + 'px'; | |
this.el.style.height = height + 'px'; | |
this.faceEl.style.backgroundColor = opt.color; | |
}, | |
/** | |
* Set events to face. | |
*/ | |
setEvents: function () { | |
[].slice.call(this.el.querySelectorAll('.controller > div')).forEach(function (panel) { | |
var that = this; | |
panel.addEventListener(M_MOVE, function (e) { | |
that.hoverCtrl(this); | |
}, false); | |
}, this); | |
}, | |
/** | |
* Hover face. | |
*/ | |
hoverOn: function (e) { | |
if (this.hovering) { | |
return; | |
} | |
this.hovering = true; | |
this.el.classList.add('on'); | |
this.parent.el.classList.add('on'); | |
var rubikcube = this.getRubikcube(); | |
var invRubikcubeMatrix = Matrix4.createMatrix(); | |
Matrix4.inverse(rubikcube.matrix, invRubikcubeMatrix); | |
var invPseudoMatrix = Matrix4.createMatrix(); | |
Matrix4.inverse(this.parent.pseudoMatrixWorld, invPseudoMatrix); | |
//法線(Z軸)の現在の方向を取得 | |
var normal = new CSS3D.Vector3(0, 0, 1); | |
//最終座標系にルービックキューブ座標系を掛け、 | |
//ルービックキューブ空間での方向を算出 | |
var mat = Matrix4.createMatrix(); | |
Matrix4.multiply(invRubikcubeMatrix, this.matrixWorld, mat); | |
Matrix4.multiply(invPseudoMatrix, mat, mat); | |
//W値を考慮しないことで、回転情報のみを反映 | |
//(ローカル座標のXYZ軸がルービックキューブ空間でどちらを向いているかを判別) | |
normal = normal.applyMatrix4withoutW(mat); | |
normal.normalize(); | |
normal.x = Math.round(normal.x); | |
normal.y = Math.round(normal.y); | |
normal.z = Math.round(normal.z); | |
var base = 5; | |
this.translate([base * normal.x, base * normal.y, base * normal.z]); | |
}, | |
/** | |
* Hover off face. | |
*/ | |
hoverOff: function (e) { | |
if (!this.hovering) { | |
return; | |
} | |
this.hovering = false; | |
this.el.classList.remove('on'); | |
this.parent.el.classList.remove('on'); | |
var rubikcube = this.getRubikcube(); | |
var invRubikcubeMatrix = Matrix4.createMatrix(); | |
Matrix4.inverse(rubikcube.matrix, invRubikcubeMatrix); | |
var invPseudoMatrix = Matrix4.createMatrix(); | |
Matrix4.inverse(this.parent.pseudoMatrixWorld, invPseudoMatrix); | |
//法線(Z軸)の現在の方向を取得 | |
var normal = new CSS3D.Vector3(0, 0, 1); | |
//最終座標系にルービックキューブ座標系を掛け、 | |
//ルービックキューブ空間での方向を算出 | |
var mat = Matrix4.createMatrix(); | |
Matrix4.multiply(invRubikcubeMatrix, this.matrixWorld, mat); | |
Matrix4.multiply(invPseudoMatrix, mat, mat); | |
//W値を考慮しないことで、回転情報のみを反映 | |
//(ローカル座標のXYZ軸がルービックキューブ空間でどちらを向いているかを判別) | |
normal = normal.applyMatrix4withoutW(mat); | |
normal.normalize(); | |
normal.x = Math.round(normal.x); | |
normal.y = Math.round(normal.y); | |
normal.z = Math.round(normal.z); | |
var base = -5; | |
this.translate([base * normal.x, base * normal.y, base * normal.z]); | |
}, | |
hoverCtrl: function (ctrl) { | |
this.el.classList.remove('on'); | |
this.parent.el.classList.remove('on'); | |
this.hoverOff(); | |
//ルービックキューブ座標空間の逆行列を生成 | |
var rubikcube = this.getRubikcube(); | |
var invRubikcubeMatrix = Matrix4.createMatrix(); | |
Matrix4.inverse(rubikcube.matrix, invRubikcubeMatrix); | |
//該当面の各軸を抽出するようのベクトル | |
var cur_axis = { | |
x: new CSS3D.Vector3(1, 0, 0), | |
y: new CSS3D.Vector3(0, 1, 0), | |
z: new CSS3D.Vector3(0, 0, 1) | |
}; | |
//最終座標系にルービックキューブ座標系を掛け、 | |
//ルービックキューブ空間での方向を算出 | |
var matrixWorld = this.matrixWorld; | |
var mat = Matrix4.createMatrix(); | |
Matrix4.multiply(invRubikcubeMatrix, matrixWorld, mat); | |
//W値を考慮しないことで、回転情報のみを反映 | |
//(ローカル座標のXYZ軸がルービックキューブ空間でどちらを向いているかを判別) | |
cur_axis.x = cur_axis.x.applyMatrix4withoutW(mat); | |
cur_axis.y = cur_axis.y.applyMatrix4withoutW(mat); | |
cur_axis.z = cur_axis.z.applyMatrix4withoutW(mat); | |
var cur_target; | |
var tmp_dir = 1; | |
switch (ctrl.dataset.dir) { | |
case 'up': | |
cur_target = 'y'; | |
tmp_dir = 1; | |
break; | |
case 'down': | |
cur_target = 'y'; | |
tmp_dir = -1; | |
break; | |
case 'left': | |
cur_target = 'x'; | |
tmp_dir = 1; | |
break; | |
case 'right': | |
cur_target = 'x'; | |
tmp_dir = -1; | |
break; | |
} | |
//Faceの法線方向は回転に含まれないので除外 | |
var noAxis = []; | |
noAxis.push(getCurAxisDir(cur_axis.z)); | |
//ドラッグされた方向と平行の軸は回転に含まれないので除外 | |
noAxis.push(getCurAxisDir(cur_axis[cur_target])); | |
//除外された軸以外を抽出 | |
var axis = ['x', 'y', 'z']; | |
noAxis.forEach(function (type) { | |
var index; | |
if ((index = axis.indexOf(type)) !== -1) { | |
axis.splice(index, 1); | |
} | |
}); | |
//抽出された軸を変数に保持 | |
var res = {}; | |
res.target = axis[0]; | |
//ドラッグ方向の値から回転方向を決める要因を算出。 | |
//プラス方向にドラッグされている場合は正回転、マイナスの場合は負の回転にする | |
var dir_axis = getCurAxisDir(cur_axis[cur_target]); | |
tmp_dir *= cur_axis[cur_target][dir_axis] > 0 ? 1 : -1; | |
//法線が向いている方向に応じて回転方向をさらに識別する。 | |
//マイナス方向に向いている場合は逆回転の必要がある。 | |
var norm_dir_axis = getCurAxisDir(cur_axis.z); | |
if (dir_axis === 'z') { | |
tmp_dir *= cur_axis.z[norm_dir_axis] < 0 ? 1 : -1; | |
} | |
else if (dir_axis === 'y') { | |
if (norm_dir_axis === 'x') { | |
tmp_dir *= cur_axis.z[norm_dir_axis] < 0 ? 1 : -1; | |
} | |
else if (norm_dir_axis === 'z') { | |
tmp_dir *= cur_axis.z[norm_dir_axis] > 0 ? 1 : -1; | |
} | |
} | |
else if (dir_axis === 'x') { | |
tmp_dir *= cur_axis.z[norm_dir_axis] > 0 ? 1 : -1; | |
} | |
res.dir = tmp_dir > 0 ? CSS3D.RubikCube.RotationDirection.FORWARD : CSS3D.RubikCube.RotationDirection.BACKWARD; | |
rubikcube.rotateCubes(res.target, res.dir); | |
}, | |
/** | |
* Set opacity. | |
* set opacity as alpha. max alpha is 0.8 + 0.3. | |
* 0.3 is ambent light as opacity. | |
* | |
* @param {number} alpha 0.0 ~ 1.0 | |
*/ | |
setOpacity: function (alpha) { | |
this.faceEl.style.opacity = 0.8 * alpha + 0.3; | |
}, | |
getRubikcube: function () { | |
return this.parent.parent; | |
}, | |
getPosition: function () { | |
var mat = Matrix4.createMatrix(); | |
this.m.multiply(this.matrixWorld, this.matrix, mat); | |
return this.position.clone().applyMatrix4(mat); | |
}, | |
getNormal: (function () { | |
var rubikcube = null, | |
//法線(Z軸)の現在の方向を取得 | |
normal = new CSS3D.Vector3(0, 0, 1), | |
invRubikcubeMatrix = Matrix4.createMatrix(), | |
resultMatrix = Matrix4.createMatrix(); | |
return function () { | |
if (!rubikcube) { | |
rubikcube = this.getRubikcube(); | |
} | |
Matrix4.inverse(rubikcube.matrix, invRubikcubeMatrix); | |
//最終座標系にルービックキューブ座標系を掛け、 | |
//ルービックキューブ空間での方向を算出 | |
Matrix4.multiply(invRubikcubeMatrix, this.matrixWorld, resultMatrix); | |
//W値を考慮しないことで、回転情報のみを反映 | |
//(ローカル座標のXYZ軸がルービックキューブ空間でどちらを向いているかを判別) | |
var _normal = normal.applyMatrix4withoutW(resultMatrix); | |
_normal.normalize(); | |
return _normal; | |
}; | |
}()) | |
}); | |
CSS3D.Face = Face; | |
}(window, window.document, window.CSS3D, window.Matrix4, window.Class)); | |
;(function (win, doc, CSS3D, Matrix4, Class) { | |
var Line = CSS3D.Object3D.extend({ | |
className: 'line', | |
init: function (length, color) { | |
this._super(); | |
this.length = length || 10; | |
this.color = color || '#fff'; | |
this.el.style.width = length + 'px'; | |
this.el.style.borderTopColor = this.color; | |
} | |
}); | |
CSS3D.Line = Line; | |
}(window, window.document, window.CSS3D, window.Matrix4, window.Class)); | |
;(function (win, doc, CSS3D, Matrix4, Class) { | |
var isTouch = 'ontouchstart' in window; | |
var M_DOWN = (isTouch) ? 'touchstart' : 'mousedown'; | |
var M_UP = (isTouch) ? 'touchend' : 'mouseup'; | |
var M_MOVE = (isTouch) ? 'touchmove' : 'mousemove'; | |
//Import | |
var cos = Math.cos; | |
var abs = Math.abs; | |
var RubikCube = CSS3D.Object3D.extend({ | |
className: 'rubikcube', | |
handleEvent: function (e) { | |
switch(e.type) { | |
case M_DOWN: | |
this._onMouseDown(e); | |
break; | |
case M_UP: | |
this._onMouseUp(e); | |
break; | |
case M_MOVE: | |
this._onMouseMove(e); | |
break; | |
} | |
}, | |
init: function (size, num, margin) { | |
this._super(); | |
this.cubes = []; | |
this.history = []; | |
//Create a directional light. | |
this.light = new CSS3D.DirectionalLight(new CSS3D.Vector3(1, -1, 1)); | |
size = size || 50; | |
num = num || 3; | |
margin = (margin === 0 || margin) ? margin : 3; | |
var index = 0, | |
cube = null; | |
for (var i = 0; i < num; i++) { | |
for (var j = 0; j < num; j++) { | |
for (var k = 0; k < num; k++) { | |
cube = new CSS3D.Cube(size, num, index); | |
cube.cubeId = index; | |
cube.translate([ | |
k * size - (size - (margin * (k - 1))), | |
j * size - (size - (margin * (j - 1))), | |
i * size - (size - (margin * (i - 1))) | |
]); | |
this.cubes.push(cube); | |
this.add(cube); | |
index++; | |
} | |
} | |
} | |
this.setEvents(); | |
this.updateMatrix(); | |
this.updateLight(); | |
}, | |
/** | |
* Set events to the document. | |
*/ | |
//TODO | |
setEvents: function () { | |
this.el.addEventListener(M_DOWN, this, false); | |
doc.addEventListener(M_UP, this, false); | |
doc.addEventListener(M_MOVE, this, false); | |
var that = this; | |
var xdeg = -50; | |
var ydeg = 40; | |
var prevZ = 0; | |
var baseZ = 10; | |
var prevX = 0; | |
var prevY = 0; | |
var handler = { | |
dragging: false, | |
handleEvent: function (e) { | |
switch(e.type) { | |
case 'gesturestart': | |
prevZ = 1; | |
e.preventDefault(); | |
break; | |
case 'gesturechange': | |
var num = e.scale; | |
that.translate([0, 0, (num - prevZ) * 400]); | |
prevZ = num; | |
e.preventDefault(); | |
break; | |
case 'mousewheel': | |
that.translate([0, 0, e.wheelDelta / 5]); | |
e.preventDefault(); | |
break; | |
case M_DOWN: | |
if (that.dragging) { | |
return; | |
} | |
this.dragging = true; | |
prevX = e.pageX; | |
prevY = e.pageY; | |
break; | |
case M_MOVE: | |
if (!this.dragging) { | |
return false; | |
} | |
if (e.touches && e.touches.length > 1) { | |
this.dragging = false; | |
return; | |
} | |
var x = prevX - e.pageX; | |
var y = prevY - e.pageY; | |
prevX = e.pageX; | |
prevY = e.pageY; | |
that.rotateY(-x); | |
that.rotateX(y); | |
break; | |
case M_UP: | |
this.dragging = false; | |
break; | |
} | |
e.preventDefault(); | |
return false; | |
} | |
}; | |
//Rotation. | |
doc.addEventListener(M_DOWN, handler, false); | |
doc.addEventListener(M_UP, handler, false); | |
doc.addEventListener(M_MOVE, handler, false); | |
doc.addEventListener('mousewheel', handler, false); | |
doc.addEventListener('gesturechange', handler, false); | |
doc.addEventListener('gesturestart', handler, false); | |
}, | |
/** | |
* Update light strength. | |
* if this method will be invoked with an argument, it calculates only `cubes`. | |
* @param {Array.<CSS3D.Cube>} cubes. | |
*/ | |
updateLight: function (cubes) { | |
var light = this.light; | |
cubes = cubes || this.cubes; | |
for (var i = 0, l = cubes.length; i < l; i++) { | |
light.calc(cubes[i]); | |
} | |
}, | |
rotateFace: function (type, direction, noAnimate) { | |
if (this.moving) { | |
return false; | |
} | |
this.moving = true; | |
var that = this; | |
var cubes = this.cubes.slice(0); | |
var firstLetter = type.charAt(0).toUpperCase(); | |
var methodName = 'get' + firstLetter + (type.slice(1)) + 'Face'; | |
var faceArr = this[methodName](); | |
var rotateMap = { | |
'back' : 'rotateWorldZ', | |
'front' : 'rotateWorldZ', | |
'top' : 'rotateWorldY', | |
'bottom' : 'rotateWorldY', | |
'left' : 'rotateWorldX', | |
'right' : 'rotateWorldX', | |
'middle1': 'rotateWorldZ', | |
'middle2': 'rotateWorldX', | |
'middle3': 'rotateWorldY' | |
} | |
var rotateDir = rotateMap[type]; | |
if (!rotateDir) { | |
return false; | |
} | |
//Add a history. | |
this.history.push({ | |
type: type, | |
direction: direction | |
}); | |
//TODO | |
if (/y/i.test(rotateDir)) { | |
direction *= RubikCube.RotationDirection.BACKWARD; | |
} | |
var angle = direction === RubikCube.RotationDirection.FORWARD ? 90 : -90; | |
var sub = direction === RubikCube.RotationDirection.BACKWARD ? 3 : -3; | |
function leap(a, b, x) { | |
var f = (1.0 - cos(x * 3.1415927)) * 0.5; | |
return a * (1.0 - f) + b * f; | |
} | |
var prev = 0; | |
var x = 0.0; | |
var cnt = 0; | |
if (noAnimate) { | |
for (var i = 0, l = faceArr.length; i < l; i++) { | |
cubes[faceArr[i]][rotateDir](angle); | |
} | |
this.updateLight(cubes); | |
this.moving = false; | |
} | |
else { | |
(function loop() { | |
if (x <= 1) { | |
var val = leap(0, angle, x); | |
var next = val - prev; | |
prev = val; | |
x += 0.06; | |
for (var i = 0, l = faceArr.length; i < l; i++) { | |
cubes[faceArr[i]][rotateDir](next); | |
if (cnt++ % 2 === 0) { | |
that.updateLight(cubes); | |
} | |
} | |
} | |
else { | |
if (abs(prev) < abs(angle)) { | |
for (var i = 0, l = faceArr.length; i < l; i++) { | |
cubes[faceArr[i]][rotateDir](angle - prev); | |
} | |
} | |
that.moving = false; | |
return; | |
} | |
setTimeout(loop, 16); | |
}()); | |
} | |
//replace cubes. | |
var b0 = this.cubes[faceArr[0]]; | |
var b1 = this.cubes[faceArr[1]]; | |
var b2 = this.cubes[faceArr[2]]; | |
var b3 = this.cubes[faceArr[3]]; | |
var b4 = this.cubes[faceArr[4]]; | |
var b5 = this.cubes[faceArr[5]]; | |
var b6 = this.cubes[faceArr[6]]; | |
var b7 = this.cubes[faceArr[7]]; | |
var b8 = this.cubes[faceArr[8]]; | |
if (direction === RubikCube.RotationDirection.FORWARD) { | |
this.cubes[faceArr[0]] = b2; | |
this.cubes[faceArr[1]] = b5; | |
this.cubes[faceArr[2]] = b8; | |
this.cubes[faceArr[3]] = b1; | |
this.cubes[faceArr[4]] = b4; | |
this.cubes[faceArr[5]] = b7; | |
this.cubes[faceArr[6]] = b0; | |
this.cubes[faceArr[7]] = b3; | |
this.cubes[faceArr[8]] = b6; | |
} | |
else { | |
this.cubes[faceArr[0]] = b6; | |
this.cubes[faceArr[1]] = b3; | |
this.cubes[faceArr[2]] = b0; | |
this.cubes[faceArr[3]] = b7; | |
this.cubes[faceArr[4]] = b4; | |
this.cubes[faceArr[5]] = b1; | |
this.cubes[faceArr[6]] = b8; | |
this.cubes[faceArr[7]] = b5; | |
this.cubes[faceArr[8]] = b2; | |
} | |
//check correct order. | |
if (this.checkCorrect()) { | |
this.trigger('correct'); | |
} | |
}, | |
getRightFace: function () { | |
return [2, 11, 20, 5, 14, 23, 8, 17, 26]; | |
}, | |
getLeftFace: function () { | |
return [0, 9, 18, 3, 12, 21, 6, 15, 24]; | |
}, | |
getBottomFace: function () { | |
return [6, 7, 8, 15, 16, 17, 24, 25, 26]; | |
}, | |
getTopFace: function () { | |
return [0, 1, 2, 9, 10, 11, 18, 19, 20]; | |
}, | |
getMiddle1Face: function () { | |
return [11, 10, 9, 14, 13, 12, 17, 16, 15]; | |
}, | |
getMiddle2Face: function () { | |
return [1, 10, 19, 4, 13, 22, 7, 16, 25]; | |
}, | |
getMiddle3Face: function () { | |
return [3, 4, 5, 12, 13, 14, 21, 22, 23]; | |
}, | |
getFrontFace: function () { | |
return [20, 19, 18, 23, 22, 21, 26, 25, 24]; | |
}, | |
getBackFace: function () { | |
return [2, 1, 0, 5, 4, 3, 8, 7, 6]; | |
}, | |
rotateCubes: function (target, dir) { | |
var type = this._selectRotateDir(this._faceType, target); | |
type = type.replace('get', '').replace('Face', ''); | |
var a = type.charAt(0).toLowerCase(); | |
type = a + type.slice(1); | |
this.rotateFace(type, dir); | |
}, | |
random: function (count) { | |
count = count || 10; | |
var rotateMap = ['top', 'bottom', 'front', 'back', 'left', 'right', 'middle1', 'middle2', 'middle3'], | |
rand, method; | |
for (var i = 0; i < count; i++) { | |
rand = ~~(Math.random() * rotateMap.length); | |
method = rotateMap[rand]; | |
this.rotateFace(method, RubikCube.RotationDirection.FORWARD, true); | |
} | |
}, | |
/* ---------------------------------------------------------------- | |
PRIVATE METHODS. | |
------------------------------------------------------------------- */ | |
_onMouseDown: function (e) { | |
if (e.touches && e.touches.length > 1) { | |
this.dragging = false; | |
return; | |
} | |
var obj3d = this._getCSS3Object(e.target); | |
if (obj3d) { | |
obj3d.hoverOn(e); | |
} | |
this.dragging = true; | |
this._selectedCubeIndex = this.cubes.indexOf(obj3d.parent); | |
this._faceType = this._getBelongTo(this._selectedCubeIndex); | |
this.prevX = e.pageX; | |
this.prevY = e.pageY; | |
e.preventDefault(); | |
}, | |
_onMouseUp: function (e) { | |
var obj3d = this._getCSS3Object(e.target); | |
if (obj3d) { | |
obj3d.hoverOff(e); | |
} | |
this.dragging = false; | |
this._selectedCubeIndex = null; | |
this._faceType = null; | |
}, | |
_onMouseMove: function (e) { | |
var obj3d = this._getCSS3Object(e.target); | |
if (e.touches && e.touches.length > 1) { | |
this.dragging = false; | |
if (obj3d) { | |
obj3d.hoverOff(e); | |
} | |
return; | |
} | |
var pageX = isTouch ? e.touches[0].pageX : e.pageX; | |
var pageY = isTouch ? e.touches[0].pageY : e.pageY; | |
var target = doc.elementFromPoint(pageX, pageY); | |
if (/left|right|up|down/.test(target.className)) { | |
var css3obj = this._getCSS3Object(target); | |
css3obj.hoverCtrl(target); | |
} | |
}, | |
_selectRotateDir: function (types, target) { | |
var ret; | |
var rotateMap = { | |
'getBackFace' : 'z', | |
'getFrontFace' : 'z', | |
'getTopFace' : 'y', | |
'getBottomFace' : 'y', | |
'getLeftFace' : 'x', | |
'getRightFace' : 'x', | |
'getMiddle1Face': 'z', | |
'getMiddle2Face': 'x', | |
'getMiddle3Face': 'y' | |
}; | |
types.some(function (type) { | |
if (rotateMap[type] === target) { | |
ret = type; | |
return true; | |
} | |
}); | |
return ret; | |
}, | |
_getBelongTo: function (index) { | |
var faceMethodNames = ['getRightFace', 'getLeftFace', 'getBottomFace', 'getTopFace', 'getMiddle1Face', 'getMiddle2Face', 'getMiddle3Face', 'getFrontFace', 'getBackFace']; | |
var ret = []; | |
for (var i = 0, l = faceMethodNames.length; i < l; i++) { | |
var arr = this[faceMethodNames[i]](); | |
if (arr.indexOf(index) !== -1) { | |
ret.push(faceMethodNames[i]); | |
} | |
} | |
return ret; | |
}, | |
_getCSS3Object: function (node) { | |
if (!node.CSS3DObject) { | |
if (!node.parentNode) { | |
return null; | |
} | |
return this._getCSS3Object(node.parentNode); | |
} | |
return node.CSS3DObject; | |
}, | |
/** | |
* Check correct order in cubes array. | |
*/ | |
checkCorrect: function () { | |
var cubes = this.cubes; | |
for (var i = 0, l = cubes.length; i < l; i++) { | |
if (cubes[i].cubeId !== i) { | |
return false; | |
} | |
} | |
return true; | |
} | |
}); | |
RubikCube.RotationDirection = { | |
FORWARD: 1, | |
BACKWARD: -1 | |
} | |
CSS3D.RubikCube = RubikCube; | |
}(window, window.document, window.CSS3D, window.Matrix4, window.Class)); | |
;(function (win,doc, Class, exports) { | |
'use strict'; | |
var Timer = Class.extend({ | |
init: function () { | |
this.el = doc.createElement('div'); | |
this.el.className = 'timer'; | |
this.time = 0; | |
}, | |
start: function () { | |
var that = this; | |
var prevTime = Date.now(); | |
var el = this.el; | |
(function loop() { | |
var now = Date.now(); | |
var cur = now - prevTime; | |
prevTime = now; | |
that.time += cur; | |
el.innerHTML = that.parse(that.time); | |
that.timer = setTimeout(loop, 32); | |
}()); | |
}, | |
stop: function () { | |
clearTimeout(this.timer); | |
}, | |
parse: function (ms) { | |
var m = '' + ~~(ms / (60 * 1000)); | |
ms %= (60 * 1000); | |
var s = '' + ~~(ms / 1000); | |
ms %= 1000; | |
m = (m = '0' + m).slice(-2); | |
s = (s = '0' + s).slice(-2); | |
ms = (ms = '00' + ms).slice(-3); | |
var ret = m + ':' + s + ':' + ms; | |
return ret; | |
} | |
}); | |
exports.Timer = Timer; | |
}(window, window.document, window.Class, window)); | |
;(function (win, doc, Matrix4, Class, CSS3D) { | |
var isTouch = 'ontouchstart' in window; | |
var M_DOWN = (isTouch) ? 'touchstart' : 'mousedown'; | |
var M_UP = (isTouch) ? 'touchend' : 'mouseup'; | |
var M_MOVE = (isTouch) ? 'touchmove' : 'mousemove'; | |
function $(selector) { | |
return doc.querySelector(selector); | |
} | |
function $$(selector) { | |
return doc.querySelectorAll(selector); | |
} | |
function getTemplate (id) { | |
var ele = doc.getElementById('template-' + id); | |
if (ele) { return ele.innerHTML; } | |
return ''; | |
} | |
(function () { | |
[].slice.call($$('#ctrl p')).forEach(function (btn) { | |
btn.addEventListener(M_DOWN, function (e) { | |
rubikcube.rotateFace(e.target.id, CSS3D.RubikCube.RotationDirection.FORWARD); | |
}, false); | |
}); | |
var query = location.search.slice(1); | |
var rubikcube = null; | |
if (/demo2/i.test(query)) { | |
rubikcube = new CSS3D.RubikCube(80, 3, 400); | |
rubikcube.translate([0, 40, -3200]); | |
} | |
else { | |
rubikcube = new CSS3D.RubikCube(80, 3); | |
rubikcube.translate([0, 40, -500]); | |
} | |
rubikcube.rotateX(-50); | |
rubikcube.rotateY(40); | |
rubikcube.random(20); | |
doc.querySelector('#stage').appendChild(rubikcube.el); | |
win.rubikcube = rubikcube; | |
var cnt = 0; | |
var rotateMap = ['top', 'bottom', 'front', 'back', 'left', 'right', 'middle1', 'middle2', 'middle3']; | |
function demoStart() { | |
(function loop() { | |
cnt = (cnt + 1) % 80; | |
if (cnt === 0) { | |
var rand = ~~(Math.random() * rotateMap.length); | |
var method = rotateMap[rand]; | |
rubikcube.rotateFace(method, CSS3D.RubikCube.RotationDirection.FORWARD); | |
} | |
rubikcube.rotateY(0.1); | |
rubikcube.rotateX(-0.3); | |
setTimeout(loop, 16); | |
}()); | |
} | |
if (/autodemo/i.test(query)) { | |
demoStart(); | |
} | |
doc.querySelector('#demo1 > input').addEventListener(M_DOWN, demoStart, false); | |
doc.querySelector('#demo2 > input').addEventListener(M_DOWN, function () { | |
location.href = 'http://' + location.host + location.pathname + '?demo1'; | |
}, false); | |
doc.querySelector('#demo3 > input').addEventListener(M_DOWN, function () { | |
location.href = 'http://' + location.host + location.pathname + '?demo2'; | |
}, false); | |
doc.querySelector('#clear > input').addEventListener(M_DOWN, function (e) { | |
var len = rubikcube.history.length; | |
(function loop() { | |
len--; | |
rubikcube.rotateFace(rubikcube.history[len].type, -rubikcube.history[len].direction); | |
if (!len) { | |
return; | |
} | |
setTimeout(loop, 500); | |
}()); | |
}, false); | |
var timer = new Timer(); | |
doc.body.appendChild(timer.el); | |
timer.start(); | |
rubikcube.on('correct', function (e) { | |
rubikcube.history = []; | |
timer.stop(); | |
}, false); | |
}()); | |
}(window, window.document, window.Matrix4, window.Class, window.CSS3D)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment