-
-/* ===================== SETUP ===================== */
-const game = document.getElementById("game");
-game.width = 800;
-game.height = 800;
-const ctx = game.getContext("2d");
-
-const BACKGROUND = "#101010";
-const FOREGROUND = "#50FF50";
-const TEXT = "#FFFFFF";
-
-const FPS = 60;
-const NEAR = 0.1;
-
-/* ===================== GEOMETRY ===================== */
-const vertices = [
- {x: 0.25, y: 0.25, z: 0.25},
- {x: -0.25, y: 0.25, z: 0.25},
- {x: -0.25, y: -0.25, z: 0.25},
- {x: 0.25, y: -0.25, z: 0.25},
- {x: 0.25, y: 0.25, z: -0.25},
- {x: -0.25, y: 0.25, z: -0.25},
- {x: -0.25, y: -0.25, z: -0.25},
- {x: 0.25, y: -0.25, z: -0.25},
-];
-
-const edges = [
- [0,1,2,3],
- [4,5,6,7],
- [0,4],[1,5],[2,6],[3,7]
-];
-
-/* ===================== OBJECT ===================== */
-let objectPos = { x: 0, y: 0, z: 2 };
-let angle = 0;
-
-const AXIS_MODES = [
- ["x"], ["y"], ["z"],
- ["x","y"], ["y","z"], ["x","z"],
- ["x","y","z"]
-];
-let axisModeIndex = AXIS_MODES.length - 1;
-let activeAxes = AXIS_MODES[axisModeIndex];
-
-/* ===================== CAMERA ===================== */
-let camera = {
- position: { x: 0, y: 0, z: 0 },
- rotation: [1,0,0,0],
- speed: 0.05,
- sensitivity: 0.002
-};
-
-/* ===================== INPUT ===================== */
-const keys = {};
-window.addEventListener("keydown", e => keys[e.key.toLowerCase()] = true);
-window.addEventListener("keyup", e => keys[e.key.toLowerCase()] = false);
-
-/* ===================== QUATERNIONS ===================== */
-function quatFromAxisAngle(axis, angle) {
- const h = angle / 2;
- const s = Math.sin(h);
- return [Math.cos(h), axis.x*s, axis.y*s, axis.z*s];
-}
-
-function quatMultiply(a, b) {
- const [w1,x1,y1,z1] = a;
- const [w2,x2,y2,z2] = b;
- return [
- w1*w2 - x1*x2 - y1*y2 - z1*z2,
- w1*x2 + x1*w2 + y1*z2 - z1*y2,
- w1*y2 - x1*z2 + y1*w2 + z1*x2,
- w1*z2 + x1*y2 - y1*x2 + z1*w2
- ];
+const canvas = document.getElementById("game");
+canvas.width = 800;
+canvas.height = 800;
+const ctx = canvas.getContext("2d");
+
+let objects = {
+ cube: {
+ vertices: [
+ // front
+ {x: -0.5 , y: -0.5, z: 0.5}, // top left
+ {x: 0.5 , y: -0.5, z: 0.5}, // top right
+ {x: 0.5 , y: 0.5, z: 0.5}, // bottom right
+ {x: -0.5 , y: 0.5, z: 0.5}, // bottom left
+
+ // back
+ {x: -0.5 , y: -0.5, z: -0.5}, // top left
+ {x: 0.5 , y: -0.5, z: -0.5}, // top right
+ {x: 0.5 , y: 0.5, z: -0.5}, // bottom right
+ {x: -0.5 , y: 0.5, z: -0.5}, // bottom left
+ ],
+ index_buffers: [
+ [0, 1, 2, 3],
+ [4, 5, 6, 7],
+ [0, 4],
+ [1, 5],
+ [2, 6],
+ [3, 7],
+ ]
+ }
}
-function quatConjugate(q) {
- return [q[0], -q[1], -q[2], -q[3]];
+function project({x, y, z}) {
+ return {
+ x: x/z,
+ y: y/z
+ }
}
-function quatRotateVector(q, v) {
- const qv = [0, v.x, v.y, v.z];
- const r = quatMultiply(quatMultiply(q, qv), quatConjugate(q));
- return { x: r[1], y: r[2], z: r[3] };
-}
+function screen({x, y}) {
+ //-1..1 -> 0..2 -> 0..1 -> 0..w/h
+ // p.y *= -1
+ return {
+ x: (x + 1)/2 * canvas.width,
+ y: (1 -(y + 1)/2) * canvas.height
-/* ===================== CAMERA VECTORS ===================== */
-function cameraForward() {
- return quatRotateVector(camera.rotation, {x:0,y:0,z:1});
-}
-function cameraRight() {
- return quatRotateVector(camera.rotation, {x:1,y:0,z:0});
-}
-function cameraUp() {
- return quatRotateVector(camera.rotation, {x:0,y:1,z:0});
+ }
}
-function applyCamera(p) {
- const v = {
- x: p.x - camera.position.x,
- y: p.y - camera.position.y,
- z: p.z - camera.position.z
- };
- return quatRotateVector(quatConjugate(camera.rotation), v);
+function clear() {
+ ctx.fillStyle = "#000000"
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
}
-/* ===================== PROJECTION ===================== */
-function project(p) {
- return { x: p.x / p.z, y: p.y / p.z };
-}
-function screen(p) {
- return {
- x: (p.x + 1) * 0.5 * game.width,
- y: (1 - (p.y + 1) * 0.5) * game.height
- };
+function translateZ({x, y, z}, dz) {
+ return {x: x, y: y, z: z + dz};
}
-/* ===================== DRAW ===================== */
-function clear() {
- ctx.fillStyle = BACKGROUND;
- ctx.fillRect(0,0,game.width,game.height);
-
- ctx.fillStyle = TEXT;
- ctx.font = "14px monospace";
- ctx.fillText("WASD: move | QE: up/down", 10, 18);
- ctx.fillText("Mouse: look | Click canvas to lock", 10, 36);
- ctx.fillText("Click canvas again: change cube axes", 10, 54);
- ctx.fillText("Cube axes: " + activeAxes.join(","), 10, 72);
-}
-
-function line(a,b) {
- ctx.strokeStyle = FOREGROUND;
- ctx.lineWidth = 2;
+function line(p1, p2) {
+ ctx.lineWidth = 3;
+ ctx.strokeStyle = "#FF0000";
ctx.beginPath();
- ctx.moveTo(a.x,a.y);
- ctx.lineTo(b.x,b.y);
+ ctx.moveTo(p1.x, p1.y);
+ ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
-/* ===================== LOOP ===================== */
-function frame() {
- angle += Math.PI / FPS;
-
- // --- cube rotation quaternion ---
- let q = [1,0,0,0];
- for (const ax of activeAxes) {
- if (ax==="x") q = quatMultiply(quatFromAxisAngle({x:1,y:0,z:0}, angle), q);
- if (ax==="y") q = quatMultiply(quatFromAxisAngle({x:0,y:1,z:0}, angle), q);
- if (ax==="z") q = quatMultiply(quatFromAxisAngle({x:0,y:0,z:1}, angle), q);
- }
-
- // --- camera movement ---
- if (keys["w"]) {
- const f = cameraForward();
- camera.position.x += f.x * camera.speed;
- camera.position.y += f.y * camera.speed;
- camera.position.z += f.z * camera.speed;
- }
- if (keys["s"]) {
- const f = cameraForward();
- camera.position.x -= f.x * camera.speed;
- camera.position.y -= f.y * camera.speed;
- camera.position.z -= f.z * camera.speed;
- }
- if (keys["a"]) {
- const r = cameraRight();
- camera.position.x -= r.x * camera.speed;
- camera.position.y -= r.y * camera.speed;
- camera.position.z -= r.z * camera.speed;
- }
- if (keys["d"]) {
- const r = cameraRight();
- camera.position.x += r.x * camera.speed;
- camera.position.y += r.y * camera.speed;
- camera.position.z += r.z * camera.speed;
- }
- if (keys["q"]) {
- const u = cameraUp();
- camera.position.x -= u.x * camera.speed;
- camera.position.y -= u.y * camera.speed;
- camera.position.z -= u.z * camera.speed;
- }
- if (keys["e"]) {
- const u = cameraUp();
- camera.position.x += u.x * camera.speed;
- camera.position.y += u.y * camera.speed;
- camera.position.z += u.z * camera.speed;
- }
+function frame() {
clear();
+ let dz = 1.5
- for (const e of edges) {
- for (let i=0;i<e.length;i++) {
- let a = vertices[e[i]];
- let b = vertices[e[(i+1)%e.length]];
+ let index_buffers = objects.cube.index_buffers;
+ let vertices = objects.cube.vertices;
- a = quatRotateVector(q, a);
- b = quatRotateVector(q, b);
+ for (const face of index_buffers) {
+ for (let j = 0; j < face.length; j++) {
+ let idA = face[j];
+ let idB = face[(j + 1) % face.length];
- a = { x:a.x+objectPos.x, y:a.y+objectPos.y, z:a.z+objectPos.z };
- b = { x:b.x+objectPos.x, y:b.y+objectPos.y, z:b.z+objectPos.z };
+ const a = vertices[idA];
+ const b = vertices[idB];
- a = applyCamera(a);
- b = applyCamera(b);
+ let ta = screen(project(translateZ(a, dz)));
+ let tb = screen(project(translateZ(b, dz)));
- if (a.z<=NEAR || b.z<=NEAR) continue;
- line(screen(project(a)), screen(project(b)));
+ line(ta, tb);
}
}
-
- setTimeout(frame, 1000/FPS);
}
-frame();
-
-/* ===================== INTERACTION ===================== */
-game.onclick = () => {
- game.requestPointerLock();
- axisModeIndex = (axisModeIndex + 1) % AXIS_MODES.length;
- activeAxes = AXIS_MODES[axisModeIndex];
-};
-
-window.addEventListener("mousemove", e => {
- if (document.pointerLockElement !== game) return;
-
- const yaw = quatFromAxisAngle({x:0,y:1,z:0}, -e.movementX * camera.sensitivity);
- const pitch = quatFromAxisAngle({x:1,y:0,z:0}, -e.movementY * camera.sensitivity);
- camera.rotation = quatMultiply(yaw, camera.rotation);
- camera.rotation = quatMultiply(camera.rotation, pitch);
-});
+frame()