-const canvas = document.getElementById("game");
-const ctx = canvas.getContext("2d")
-const BACKGROUND = "#000000";
-const FOREGROUND = "#00FF00";
+/* ===================== 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},
+];
-// let debug = true;
-let debug = false;
-
-let debugColors = [
- "#FF0000",
- "#00FF00",
- "#0000FF",
-]
-
-const highContrastColors = [
- "#FFFF00", // 1. Bright Yellow
- "#FF00FF", // 2. Magenta
- "#00FFFF", // 3. Cyan / Aqua
- "#FFA500", // 4. Pure Orange
- "#800080", // 5. Deep Purple
- "#008080", // 6. Teal
- "#A52A2A", // 7. Brown
- "#800000", // 8. Maroon
- "#008000", // 9. Forest Green
- "#000080", // 10. Navy Blue
- "#808000", // 11. Olive
- "#C71585", // 12. Hot Pink
- "#FFC0CB", // 13. Light Pink
- "#7FFFD4", // 14. Aquamarine
- "#FFE4E1", // 15. Misty Rose
- "#F0E68C", // 16. Khaki
- "#E6E6FA", // 17. Lavender
- "#B0C4DE", // 18. Light Steel Blue
- "#FF4500", // 19. Red-Orange
- "#1E90FF", // 20. Dodger Blue
- "#FFD700", // 21. Gold
- "#9370DB", // 22. Medium Purple
- "#8B008B", // 23. Dark Magenta
- "#556B2F", // 24. Dark Olive Green
- "#FF8C00", // 25. Dark Orange
- "#48D1CC", // 26. Medium Turquoise
- "#32CD32", // 27. Bright Green
- "#2F4F4F", // 28. Dark Slate Gray
- "#98FF98", // 29. Mint Green
- "#FF7F50", // 30. Coral
- "#4B0082", // 31. Indigo
- "#D2B48C", // 32. Tan / Sand
- "#FF7F55", // 30. Coral
+const edges = [
+ [0,1,2,3],
+ [4,5,6,7],
+ [0,4],[1,5],[2,6],[3,7]
];
-canvas.width = 800;
-canvas.height = 800;
+/* ===================== OBJECT ===================== */
+let objectPos = { x: 0, y: 0, z: 2 };
+let angle = 0;
-function clear() {
- ctx.fillStyle = BACKGROUND;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
+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 line(p1, p2) {
- ctx.lineWidth = 8;
- if (!debug) ctx.strokeStyle = FOREGROUND;
- ctx.beginPath();
- ctx.moveTo(p1.x, p1.y);
- ctx.lineTo(p2.x, p2.y);
- ctx.stroke();
+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
+ ];
}
-function getMagnitude(a, b) {
- let x = (b.x - a.x);
- let y = (b.y - a.y);
- return Math.sqrt(x * x + y * y);
+function quatConjugate(q) {
+ return [q[0], -q[1], -q[2], -q[3]];
}
-function interpolate(a, b, y) {
- let startingPos = a.x;
- let incX = (b.x - a.x) / (b.y - a.y);
- let numberOfyIncs = y - a.y;
- return startingPos + incX * numberOfyIncs;
+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 drawTriangle(coords, color) {
- coords.sort((a,b) => a.y - b.y);
-
- let blueisSteeperThanRed =
- (coords[2].y - coords[0].y) * (coords[1].x - coords[0].x) >
- (coords[1].y - coords[0].y) * (coords[2].x - coords[0].x)
+/* ===================== 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});
+}
- for (let y = coords[0].y; y <= coords[2].y; ++y) {
+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);
+}
- let x1 = interpolate(coords[0], coords[2], y);
+/* ===================== 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
+ };
+}
- let x2 = (y < coords[1].y) ?
- interpolate(coords[0], coords[1], y) :
- interpolate(coords[1], coords[2], y);
+/* ===================== 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);
+}
- let fill = (i, y) => {
- ctx.fillStyle = color;
- ctx.fillRect(i, y, 2, 2);
- }
+function line(a,b) {
+ ctx.strokeStyle = FOREGROUND;
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ ctx.moveTo(a.x,a.y);
+ ctx.lineTo(b.x,b.y);
+ ctx.stroke();
+}
- if (blueisSteeperThanRed) {
- for (let i = x1; i < x2; ++i) fill(i, y);
- } else {
- for (let i = x1; i > x2; --i) fill(i, y);
+/* ===================== 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);
}
- if (debug) {
- for (const i in coords) {
- ctx.strokeStyle = debugColors[i];
- let a = coords[i];
- let b = coords[ (i + 1) % coords.length];
- line(a, b);
- }
-
+ // --- 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;
}
+ clear();
-}
+ 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]];
+ a = quatRotateVector(q, a);
+ b = quatRotateVector(q, b);
-clear();
-let moveX = 0;
-
-let triangleCoords = [
- // --- CENTER CRADLE (Core focal points) ---
- [{x: 400, y: 400}, {x: 300, y: 300}, {x: 500, y: 250}], // High acute top wedge
- [{x: 400, y: 400}, {x: 500, y: 250}, {x: 550, y: 420}], // Narrow vertical sliver
- [{x: 400, y: 400}, {x: 550, y: 420}, {x: 480, y: 550}], // Rotated downward blade
- [{x: 400, y: 400}, {x: 480, y: 550}, {x: 280, y: 480}], // Broad foundational center piece
- [{x: 400, y: 400}, {x: 280, y: 480}, {x: 300, y: 300}], // Left-leaning internal wedge
-
- // --- NORTH FLANK (Radiating to the top edge: y = 0) ---
- [{x: 300, y: 300}, {x: 0, y: 0}, {x: 250, y: 0}],
- [{x: 300, y: 300}, {x: 250, y: 0}, {x: 500, y: 0}],
- [{x: 300, y: 300}, {x: 500, y: 0}, {x: 500, y: 250}], // Massive sweeping North-East shard
- [{x: 500, y: 250}, {x: 500, y: 0}, {x: 700, y: 0}],
- [{x: 500, y: 250}, {x: 700, y: 0}, {x: 800, y: 150}], // Long, sharp slope
-
- // --- EAST FLANK (Radiating to the right edge: x = 800) ---
- [{x: 500, y: 250}, {x: 800, y: 150}, {x: 550, y: 420}],
- [{x: 550, y: 420}, {x: 800, y: 150}, {x: 800, y: 450}], // Tight accent block
- [{x: 550, y: 420}, {x: 800, y: 450}, {x: 800, y: 600}],
- [{x: 550, y: 420}, {x: 800, y: 600}, {x: 480, y: 550}], // Giant horizontal slice
-
- // --- SOUTH FLANK (Radiating to the bottom edge: y = 800) ---
- [{x: 480, y: 550}, {x: 800, y: 600}, {x: 800, y: 800}], // Clears the bottom right corner
- [{x: 480, y: 550}, {x: 800, y: 800}, {x: 550, y: 800}],
- [{x: 480, y: 550}, {x: 550, y: 800}, {x: 350, y: 800}], // Long vertical grounding anchor
- [{x: 480, y: 550}, {x: 350, y: 800}, {x: 150, y: 800}],
- [{x: 480, y: 550}, {x: 150, y: 800}, {x: 280, y: 480}], // Sweeping South-West transitional floor
-
- // --- WEST FLANK (Radiating to the left edge: x = 0) ---
- [{x: 280, y: 480}, {x: 150, y: 800}, {x: 0, y: 800}], // Clears the bottom left corner
- [{x: 280, y: 480}, {x: 0, y: 800}, {x: 0, y: 500}],
- [{x: 280, y: 480}, {x: 0, y: 500}, {x: 0, y: 300}], // Tall vertical perimeter guard
- [{x: 280, y: 480}, {x: 0, y: 300}, {x: 300, y: 300}], // Steep internal left block
-
- // --- NORTH-WEST FILLER ZONE (Completing upper left frame gaps) ---
- [{x: 300, y: 300}, {x: 0, y: 300}, {x: 0, y: 120}],
- [{x: 300, y: 300}, {x: 0, y: 120}, {x: 0, y: 0}],
-
- // --- OUTER SUBDIVISIONS (Adding complexity to the borders for visual contrast) ---
- [{x: 700, y: 0}, {x: 800, y: 0}, {x: 800, y: 150}], // Top right micro-corner
- [{x: 550, y: 800}, {x: 680, y: 800}, {x: 480, y: 550}], // Tiny baseline shard A
- [{x: 680, y: 800}, {x: 800, y: 800}, {x: 480, y: 550}], // Tiny baseline shard B
- [{x: 350, y: 800}, {x: 250, y: 800}, {x: 480, y: 550}], // Tiny baseline shard C
- [{x: 250, y: 800}, {x: 150, y: 800}, {x: 480, y: 550}], // Tiny baseline shard D
- [{x: 0, y: 500}, {x: 0, y: 650}, {x: 280, y: 480}], // Left margin splitter
- [{x: 0, y: 650}, {x: 0, y: 800}, {x: 280, y: 480}], // Left floor splitter
- [{x: 0, y: 300}, {x: 0, y: 200}, {x: 300, y: 300}], // Left ceiling splitter
- [{x: 0, y: 200}, {x: 0, y: 0}, {x: 300, y: 300}] // Left corner lock
-];
+ 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 };
-// drawTriangle([{x: moveX + 200, y: 200}, {x: moveX + 600, y: 310},{x: moveX + 450, y: 400}]);
-// drawTriangle([{x: 120, y: 200}, {x:200, y: 310},{x: 450, y: 400}], "#FF0000");
-// drawTriangle([{x: 200, y: 200}, {x:600, y: 230},{x: 250, y: 300}]);
+ a = applyCamera(a);
+ b = applyCamera(b);
+ if (a.z<=NEAR || b.z<=NEAR) continue;
+ line(screen(project(a)), screen(project(b)));
+ }
+ }
-for (const i in triangleCoords) {
- drawTriangle(triangleCoords[i], highContrastColors[i % highContrastColors.length]);
- await new Promise(r => setTimeout(r, 2000));
+ 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);
+});