Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>3D Rubik's Cube Animation</title>
- <style>
- body {
- margin: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100vh;
- background: #1a1a2e;
- overflow: hidden;
- }
- canvas {
- background: #1a1a2e;
- display: block;
- }
- </style>
- </head>
- <body>
- <canvas id="c" width="850" height="850"></canvas>
- <script>
- (function() {
- const canvas = document.getElementById('c');
- const ctx = canvas.getContext('2d');
- const W = canvas.width;
- const H = canvas.height;
- // ====== COLORS ======
- const C = {
- red: '#E41B17',
- green: '#00B550',
- blue: '#1E5AFF',
- orange: '#FF6B00',
- white: '#F0F0F0',
- yellow: '#FFD500',
- black: '#111111'
- };
- // ====== MATRIX HELPERS ======
- function rotX(a) { let c = Math.cos(a), s = Math.sin(a); return [[1,0,0],[0,c,-s],[0,s,c]]; }
- function rotY(a) { let c = Math.cos(a), s = Math.sin(a); return [[c,0,s],[0,1,0],[-s,0,c]]; }
- function rotZ(a) { let c = Math.cos(a), s = Math.sin(a); return [[c,-s,0],[s,c,0],[0,0,1]]; }
- function matMul(A, B) {
- let R = [[0,0,0],[0,0,0],[0,0,0]];
- for (let i = 0; i < 3; i++)
- for (let j = 0; j < 3; j++)
- R[i][j] = A[i][0]*B[0][j] + A[i][1]*B[1][j] + A[i][2]*B[2][j];
- return R;
- }
- function matApply(M, v) {
- return [
- M[0][0]*v[0] + M[0][1]*v[1] + M[0][2]*v[2],
- M[1][0]*v[0] + M[1][1]*v[1] + M[1][2]*v[2],
- M[2][0]*v[0] + M[2][1]*v[1] + M[2][2]*v[2]
- ];
- }
- function getRotMat(axis, angle) {
- if (axis === 'x') return rotX(angle);
- if (axis === 'y') return rotY(angle);
- return rotZ(angle);
- }
- // ====== CUBIES ======
- let cubies = [];
- for (let x = -1; x <= 1; x++)
- for (let y = -1; y <= 1; y++)
- for (let z = -1; z <= 1; z++)
- cubies.push({
- x: x, y: y, z: z,
- ox: x, oy: y, oz: z,
- rot: [[1,0,0],[0,1,0],[0,0,1]]
- });
- // ====== FACE COLORS ======
- function getFaceColors(cubie) {
- let cols = [null, null, null, null, null, null];
- if (cubie.ox === 1) cols[0] = C.red;
- if (cubie.ox === -1) cols[1] = C.orange;
- if (cubie.oy === 1) cols[2] = C.white;
- if (cubie.oy === -1) cols[3] = C.yellow;
- if (cubie.oz === 1) cols[4] = C.green;
- if (cubie.oz === -1) cols[5] = C.blue;
- return cols;
- }
- const FACE_NORMALS = [[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]];
- // ====== FACE VERTICES ======
- function getFaceVerts(face, inset) {
- let h = inset;
- switch (face) {
- case 0: return [[0.5,h,h],[0.5,h,-h],[0.5,-h,-h],[0.5,-h,h]];
- case 1: return [[-0.5,h,-h],[-0.5,h,h],[-0.5,-h,h],[-0.5,-h,-h]];
- case 2: return [[h,0.5,h],[-h,0.5,h],[-h,0.5,-h],[h,0.5,-h]];
- case 3: return [[h,-0.5,-h],[-h,-0.5,-h],[-h,-0.5,h],[h,-0.5,h]];
- case 4: return [[h,h,0.5],[-h,h,0.5],[-h,-h,0.5],[h,-h,0.5]];
- case 5: return [[h,h,-0.5],[h,-h,-0.5],[-h,-h,-0.5],[-h,h,-0.5]];
- }
- }
- // ====== MOVE DEFINITIONS ======
- function getMove(face) {
- switch (face) {
- case 'R': return { axis: 'x', layer: 1, angle: -Math.PI/2 };
- case 'R\'': return { axis: 'x', layer: 1, angle: Math.PI/2 };
- case 'L': return { axis: 'x', layer: -1, angle: Math.PI/2 };
- case 'L\'': return { axis: 'x', layer: -1, angle: -Math.PI/2 };
- case 'U': return { axis: 'y', layer: 1, angle: Math.PI/2 };
- case 'U\'': return { axis: 'y', layer: 1, angle: -Math.PI/2 };
- case 'D': return { axis: 'y', layer: -1, angle: -Math.PI/2 };
- case 'D\'': return { axis: 'y', layer: -1, angle: Math.PI/2 };
- case 'F': return { axis: 'z', layer: 1, angle: -Math.PI/2 };
- case 'F\'': return { axis: 'z', layer: 1, angle: Math.PI/2 };
- case 'B': return { axis: 'z', layer: -1, angle: Math.PI/2 };
- case 'B\'': return { axis: 'z', layer: -1, angle: -Math.PI/2 };
- }
- }
- // ====== ANIMATION STATE ======
- let anim = null;
- let phase = 0;
- let moveIdx = 0;
- let phaseStart = 0;
- let startTime = null;
- const MOVE_DURATION = 0.5;
- const INITIAL_DURATION = 2;
- const PAUSE_DURATION = 2;
- const scrambleMoves = ['R','U','F\'','D','L\'','B','R\'','U\'','F','D\''];
- const solveMoves = ['D','F\'','U','R','B\'','L','D\'','F','U\'','R\''];
- function startMove(moveDef) {
- let affected = cubies.filter(c => {
- if (moveDef.axis === 'x') return c.x === moveDef.layer;
- if (moveDef.axis === 'y') return c.y === moveDef.layer;
- return c.z === moveDef.layer;
- });
- let originals = affected.map(c => ({
- x: c.x, y: c.y, z: c.z,
- rot: c.rot.map(row => [...row])
- }));
- anim = {
- moveDef: moveDef,
- affected: affected,
- originals: originals,
- startTime: performance.now() / 1000,
- duration: MOVE_DURATION
- };
- }
- function completeMove() {
- let moveDef = anim.moveDef;
- let rotMat = getRotMat(moveDef.axis, moveDef.angle);
- for (let i = 0; i < anim.affected.length; i++) {
- let c = anim.affected[i];
- let orig = anim.originals[i];
- let pos = matApply(rotMat, [orig.x, orig.y, orig.z]);
- c.x = Math.round(pos[0]);
- c.y = Math.round(pos[1]);
- c.z = Math.round(pos[2]);
- c.rot = matMul(rotMat, orig.rot);
- }
- anim = null;
- }
- function updateState(time) {
- if (startTime === null) {
- startTime = time;
- phaseStart = time;
- }
- // Handle animation completion
- if (anim) {
- if (time - anim.startTime >= anim.duration) {
- completeMove();
- moveIdx++;
- if (phase === 1) {
- if (moveIdx >= scrambleMoves.length) {
- phase = 2;
- phaseStart = time;
- } else {
- startMove(getMove(scrambleMoves[moveIdx]));
- }
- } else if (phase === 3) {
- if (moveIdx >= solveMoves.length) {
- phase = 4;
- phaseStart = time;
- } else {
- startMove(getMove(solveMoves[moveIdx]));
- }
- }
- }
- }
- // Phase transitions
- if (!anim) {
- if (phase === 0 && time - phaseStart >= INITIAL_DURATION) {
- phase = 1;
- phaseStart = time;
- moveIdx = 0;
- startMove(getMove(scrambleMoves[0]));
- } else if (phase === 2 && time - phaseStart >= PAUSE_DURATION) {
- phase = 3;
- phaseStart = time;
- moveIdx = 0;
- startMove(getMove(solveMoves[0]));
- }
- }
- }
- // ====== GET CUBIE STATE (with animation) ======
- function getCubieState(cubie) {
- if (anim && anim.affected.includes(cubie)) {
- let idx = anim.affected.indexOf(cubie);
- let orig = anim.originals[idx];
- let t = Math.min((performance.now() / 1000 - anim.startTime) / anim.duration, 1);
- let angle = anim.moveDef.angle * t;
- let rotMat = getRotMat(anim.moveDef.axis, angle);
- let pos = matApply(rotMat, [orig.x, orig.y, orig.z]);
- let rot = matMul(rotMat, orig.rot);
- return { x: pos[0], y: pos[1], z: pos[2], rot: rot };
- }
- return { x: cubie.x, y: cubie.y, z: cubie.z, rot: cubie.rot };
- }
- // ====== SHADING ======
- function computeShade(normal) {
- let len = Math.sqrt(normal[0] ** 2 + normal[1] ** 2 + normal[2] ** 2);
- if (len === 0) return 1;
- let n = [normal[0] / len, normal[1] / len, normal[2] / len];
- let light = [0.3, 0.7, 0.5];
- let llen = Math.sqrt(light[0] ** 2 + light[1] ** 2 + light[2] ** 2);
- light = [light[0] / llen, light[1] / llen, light[2] / llen];
- let dot = n[0] * light[0] + n[1] * light[1] + n[2] * light[2];
- return 0.35 + 0.65 * Math.max(0, dot);
- }
- // ====== PROJECTION ======
- function projectVertex(v, camDist, fov) {
- let depth = camDist - v[2];
- return {
- x: W / 2 + v[0] * fov / depth,
- y: H / 2 - v[1] * fov / depth,
- depth: depth
- };
- }
- // ====== DRAW FACE ======
- function drawFace(face) {
- let r1 = parseInt(face.color.slice(1, 3), 16);
- let g1 = parseInt(face.color.slice(3, 5), 16);
- let b1 = parseInt(face.color.slice(5, 7), 16);
- let shade = face.shade;
- let r = Math.min(255, Math.round(r1 * shade));
- let g = Math.min(255, Math.round(g1 * shade));
- let b = Math.min(255, Math.round(b1 * shade));
- ctx.fillStyle = `rgb(${r},${g},${b})`;
- ctx.beginPath();
- ctx.moveTo(face.verts[0].x, face.verts[0].y);
- for (let i = 1; i < 4; i++) {
- ctx.lineTo(face.verts[i].x, face.verts[i].y);
- }
- ctx.closePath();
- ctx.fill();
- ctx.strokeStyle = 'rgba(0,0,0,0.35)';
- ctx.lineWidth = 1;
- ctx.stroke();
- }
- // ====== RENDER ======
- function render(time) {
- ctx.clearRect(0, 0, W, H);
- // Global slow rotation
- let gY = rotY(time * 0.45);
- let gX = rotX(Math.sin(time * 0.28) * 0.25);
- let globalRot = matMul(gY, gX);
- let camDist = 6.5;
- let fov = 520;
- let faces = [];
- for (let cubie of cubies) {
- let state = getCubieState(cubie);
- let pos = [state.x, state.y, state.z];
- let rot = state.rot;
- let colors = getFaceColors(cubie);
- for (let f = 0; f < 6; f++) {
- // Body face
- let bodyVerts = getFaceVerts(f, 0.5);
- let bodyWorldVerts = [];
- let bodyNormal = matApply(rot, FACE_NORMALS[f]);
- bodyNormal = matApply(globalRot, bodyNormal);
- let bodyAvgZ = 0;
- for (let v of bodyVerts) {
- let p = matApply(rot, v);
- p = [p[0] + pos[0], p[1] + pos[1], p[2] + pos[2]];
- p = matApply(globalRot, p);
- bodyWorldVerts.push(p);
- bodyAvgZ += p[2];
- }
- bodyAvgZ /= 4;
- let bodyDepth = camDist - bodyAvgZ;
- let bodyScreenVerts = bodyWorldVerts.map(v => projectVertex(v, camDist, fov));
- faces.push({
- verts: bodyScreenVerts,
- color: C.black,
- depth: bodyDepth,
- shade: 1,
- isSticker: false
- });
- // Sticker
- if (colors[f]) {
- let stickerVerts = getFaceVerts(f, 0.38);
- let stickerWorldVerts = [];
- let stickerAvgZ = 0;
- for (let v of stickerVerts) {
- let p = matApply(rot, v);
- p = [p[0] + pos[0], p[1] + pos[1], p[2] + pos[2]];
- p = matApply(globalRot, p);
- stickerWorldVerts.push(p);
- stickerAvgZ += p[2];
- }
- stickerAvgZ /= 4;
- let stickerDepth = camDist - stickerAvgZ - 0.02; // slightly closer
- let stickerScreenVerts = stickerWorldVerts.map(v => projectVertex(v, camDist, fov));
- let shade = computeShade(bodyNormal);
- faces.push({
- verts: stickerScreenVerts,
- color: colors[f],
- depth: stickerDepth,
- shade: shade,
- isSticker: true
- });
- }
- }
- }
- // Sort: far to near
- faces.sort((a, b) => {
- let diff = b.depth - a.depth;
- if (Math.abs(diff) < 0.005) {
- return a.isSticker ? 1 : -1;
- }
- return diff;
- });
- for (let face of faces) {
- drawFace(face);
- }
- }
- // ====== ANIMATION LOOP ======
- function animate(msTime) {
- let t = msTime / 1000;
- updateState(t);
- render(t);
- requestAnimationFrame(animate);
- }
- requestAnimationFrame(animate);
- })();
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment