diff --git a/main.js b/main.js index 7c5fdfb..3f1fb30 100644 --- a/main.js +++ b/main.js @@ -1,6 +1,8 @@ -// TODO: corner mesh -// TODO: cylinder mesh -// TODO: end point mesh +// TODO: refactor: make cap/end naming consistent +// TODO: feat: webxr +// TODO: refactor: extract components to different files +// TODO: chore: embed dependencies +// TODO: feat: generate random puzzle import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; @@ -33,16 +35,6 @@ const redMaterial = new THREE.MeshLambertMaterial({ color: 0xff0000 }); const greenMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff00 }); const blueMaterial = new THREE.MeshLambertMaterial({ color: 0x0000ff }); -// Make cubes -function makeCube(xCoord, material) { - const geometry = new THREE.BoxGeometry(); - const cube = new THREE.Mesh(geometry, material); - cube.position.set(xCoord, 0, 0); - scene.add(cube); - - return cube; -} - function makeCylinder(material) { const radius = .25; const height = 1; @@ -61,9 +53,9 @@ class CornerCurve extends THREE.Curve { } getPoint(t) { - const tx = Math.sin(.5 * Math.PI * t) * this.scale - .5; + const tx = 0; const ty = Math.cos(.5 * Math.PI * t) * this.scale - .5; - const tz = 0; + const tz = -Math.sin(.5 * Math.PI * t) * this.scale + .5; return new THREE.Vector3(tx, ty, tz); } } @@ -106,28 +98,151 @@ function makeCap(material) { return mesh; } +const PIECE_TYPE_END = 0; +const PIECE_TYPE_CORNER = 1; +const PIECE_TYPE_STRAIGHT = 2; + +// Piece encoding +// +// Each piece can be aligned to either one or two faces of a cube. We associate each face +// of a cube with the position of a bit in a 6-bit number, starting with the bottom face, +// clockwise (looking up) around the middle faces starting with the closest face to the +// viewer, and ending with the top face. +// +// 32 +// +// +---------+ +// /| /| +// / | 8 / | +// 16 +---------+ | 4 +// | +------|--+ +// | / 2 | / +// |/ |/ +// +---------+ +// +// 1 +// +// Ends are associated with one face, corners with two adjacent faces and lines with +// two opposing faces. +// +// To correctly orient pieces we need to associate an encoding with a particular orientation +// of a mesh. Orientations are 3D vectors containing multiples of 90ยบ rotations around the x, +// y and z axes. + +const encodings = new Map(); +// End pieces +encodings.set(1, {type: PIECE_TYPE_END, rotation: new THREE.Vector3(0, 0, 0)}); +encodings.set(2, {type: PIECE_TYPE_END, rotation: new THREE.Vector3(3, 0, 0)}); +encodings.set(4, {type: PIECE_TYPE_END, rotation: new THREE.Vector3(0, 0, 1)}); +encodings.set(8, {type: PIECE_TYPE_END, rotation: new THREE.Vector3(1, 0, 0)}); +encodings.set(16, {type: PIECE_TYPE_END, rotation: new THREE.Vector3(0, 0, 3)}); +encodings.set(32, {type: PIECE_TYPE_END, rotation: new THREE.Vector3(2, 0, 0)}); +// Corners +encodings.set(1|2, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(0, 0, 0)}); +encodings.set(1|4, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(0, 1, 0)}); +encodings.set(1|8, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(0, 2, 0)}); +encodings.set(1|16, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(0, 3, 0)}); +encodings.set(32|2, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(2, 2, 0)}); +encodings.set(32|4, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(2, 1, 0)}); +encodings.set(32|8, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(2, 0, 0)}); +encodings.set(32|16,{type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(2, 3, 0)}); +encodings.set(2|4, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(0, 0, 1)}); +encodings.set(4|8, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(0, 1, 1)}); +encodings.set(8|16, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(1, 3, 0)}); +encodings.set(16|2, {type: PIECE_TYPE_CORNER, rotation: new THREE.Vector3(0, 0, 3)}); +// Straights +encodings.set(1|32, {type: PIECE_TYPE_STRAIGHT, rotation: new THREE.Vector3(0, 0, 0)}); +encodings.set(2|8, {type: PIECE_TYPE_STRAIGHT, rotation: new THREE.Vector3(1, 0, 0)}); +encodings.set(4|16, {type: PIECE_TYPE_STRAIGHT, rotation: new THREE.Vector3(0, 0, 1)}); + +function makePiece(code) { + const pieceData = encodings.get(code); + let mesh = null; + switch (pieceData.type) { + case PIECE_TYPE_END: + mesh = makeCap(blueMaterial); + break; + case PIECE_TYPE_CORNER: + mesh = makeCorner(greenMaterial); + break; + case PIECE_TYPE_STRAIGHT: + mesh = makeCylinder(redMaterial); + break; + } + + const rotation = pieceData.rotation; + mesh.rotation.x = rotation.x * Math.PI * .5; + mesh.rotation.y = rotation.y * Math.PI * .5; + mesh.rotation.z = rotation.z * Math.PI * .5; + + return mesh; +} + let meshes = []; -const cylinder = makeCylinder(redMaterial); -cylinder.position.x = -1; -meshes.push(cylinder); -const corner = makeCorner(greenMaterial); -meshes.push(corner); +function addPieceGrid() { + const encodingsArray = [...encodings.entries()]; + encodingsArray.forEach(([key, value], index) => { + let mesh = makePiece(key); -const cap = makeCap(blueMaterial); -cap.position.x = 1; -meshes.push(cap); + // Arrange pieces in a grid + const sideLength = Math.round(Math.sqrt(encodingsArray.length)); + mesh.position.x = (Math.floor(index % sideLength) - sideLength * .5) * 1.2; + mesh.position.y = (Math.floor(index / sideLength) - sideLength * .5) * 1.2; + + meshes.push(mesh); + }); +} + +function addTestPuzzle() { + let puzzleData = [ + [4, -1, 1, 1], + [4|16, 0, 1, 1], + [16|8, 1, 1, 1], + [2|8, 1, 1, 0], + [2|16, 1, 1, -1], + [16|4, 0, 1, -1], + [4|2, -1, 1, -1], + [8|4, -1, 1, 0], + [16|1, 0, 1, 0], + [32|4, 0, 0, 0], + [16|8, 1, 0, 0], + [2|16, 1, 0, -1], + [4|16, 0, 0, -1], + [4|2, -1, 0, -1], + [8|2, -1, 0, 0], + [8|4, -1, 0, 1], + [16|4, 0, 0, 1], + [16|1, 1, 0, 1], + [32|16, 1, -1, 1], + [4|16, 0, -1, 1], + [4|8, -1, -1, 1], + [2|8, -1, -1, 0], + [2|4, -1, -1, -1], + [16|4, 0, -1, -1], + [16|2, 1, -1, -1], + [8|16, 1, -1, 0], + [4, 0, -1, 0] + ]; + + for (var i = 0; i < puzzleData.length; i++) { + const datum = puzzleData[i]; + const key = datum[0]; + const mesh = makePiece(key); + + mesh.position.set(datum[1], datum[2], datum[3]); + + meshes.push(mesh); + } +} + +//addPieceGrid(); +addTestPuzzle(); // Animation loop function animate() { requestAnimationFrame(animate); - for (let i = 0; i < meshes.length; i++) { - const mesh = meshes[i]; - mesh.rotation.x += 0.01; - mesh.rotation.y += 0.01; - } - controls.update(); renderer.render(scene, camera); }