]> nyquistdev.net Git - basic-3d-example.git/commitdiff
first commit clickable
authorA-Larsen <austin.larsen@mailfence.com>
Thu, 15 Oct 2026 01:48:44 +0000 (21:48 -0400)
committerA-Larsen <austin.larsen@mailfence.com>
Thu, 15 Oct 2026 01:48:44 +0000 (21:48 -0400)
README.adoc
example.html
index.html
index.js

index ac1ab7848c5a1c7bb17429b28f8c0c48df4d4afc..ae0655a7f3b8fc415a36d2bcfe32a20241421533 100644 (file)
@@ -2,11 +2,11 @@
 
 click https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=example.html;hb=HEAD"[here] for an interactive example
 
-A good explaination of how 3D works.
+A good explaination how 3D works.
 
-NOTE: This started off as code from Tsoding, I used chatgpt to change
-      specific things, and also made some of my own changes. This is a learning
-      tool! not something I wrote off the top of my head
+NOTE: This started off as code from Tsoding, and then I used chatgpt to change
+      specific things. This is a learning tool! not something I wrote off the
+      top of my head
 
 == Graphics Pipeline
 
@@ -19,9 +19,6 @@ vertex
  -> screen
 ----
 
-NOTE: Some parts of these pipeline may be left out if that part of the pipeline
-      is not needed
-
 == Basic Projection Formula
 
 Projection formula for a vector latexmath:[[x,y,z]]:
@@ -37,7 +34,6 @@ y' &= y/z
 ++++
 
 == Vertex Rotation Formulas
-click https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=example.html;hb=refs/heads/simple-rotation[here] for an interactive example
 
 Rotate vector formula for x-axis (y-z plane):
 
@@ -127,8 +123,6 @@ lock by switching the order. Most application use quaternions instead of eular
 angles.
 
 == Quaternions
-click https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=example.html;hb=refs/heads/quaternions[here] for an interactive example
-
 Quaternions allow you to rotate smoothly around multiple axes without gimbal
 lock. Chat-gpt rewrote the code to use quaternions. Here are some of the
 replys about https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=docs/chatgpt-dump/quaternions.html;hb=HEAD[quaternions].
@@ -347,68 +341,8 @@ e^{i\theta} = \cos\theta + i\sin\theta
 ++++
 
 == Camera
-
 chat-gpt replys about https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=docs/chatgpt-dump/camera.html;hb=HEAD[camera].
 
-== Simple clickable object
-
-click https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=example.html;hb=refs/heads/clickable[here] for an interactive example
-
-This does not account for the rotation of the object but it does check to see if
-your mouse is within the rectangular bounds of the mesh.
-
-1. finding the min and max values for x and y of the mesh:
-
-[source, js]
-----
-function computeBounds(mesh) {
-    let min = {x: Infinity, y: Infinity}
-    let max = {x: -Infinity, y: -Infinity}
-
-    for (const v of mesh) {
-        min.x = Math.min(min.x, v.x)
-        min.y = Math.min(min.y, v.y)
-
-        max.x = Math.max(max.x, v.x)
-        max.y = Math.max(max.y, v.y)
-    }
-    return {min, max}
-}
-----
-
-2. Define the box corners of the rect for the mesh
-
-[source, js]
-----
-function boundingBoxCorners({min, max}) {
-    return {
-        bottom: {left: {x: min.x, y: max.y}, right: {x: max.x, y: max.y}},
-        top: {left: {x: min.x, y: min.y}, right: {x: max.x, y: min.y}},
-
-    }
-}
-----
-
-3. Check to see if the mouse is in the rect in the game loop. If it is, and the
-   mouse button is down, then you can move the object
-
-[source, js]
-----
-inObject =  (
-    mousePos.x > rect.top.left.x 
-    &&
-    mousePos.x < rect.top.right.x 
-    &&
-    mousePos.y > rect.top.left.y
-    &&
-    mousePos.y < rect.bottom.right.y
-) 
-
-if (mouseDown && inObject) canMoveObject = true
-
-if (!mouseDown) canMoveObject = false
-----
-
 == Links
 
 * https://www.youtube.com/watch?v=qjWkNZ0SXfo[One Formula That demystifies 3D Graphics]
index 28ab5d09c4f0200bf68cb87c460ca142e94e8a1b..3a1f17c791145291aaf06e5f7af6c912d9f6007b 100644 (file)
@@ -58,14 +58,14 @@ body {
 <div id="container">
     <canvas id="game"></canvas>
     <pre id="code-viewer">
-      <code class="language-javascript"> </code>
+      <code class="language-javascript"></code>
     </pre>
 
 </div>
 
-<script src="https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=index.js;hb=refs/heads/quaternions"></script>
+<script src="https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=index.js;hb=refs/heads/clickable"></script>
 <script>
-fetch('https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=index.js;hb=refs/heads/quaternions')
+fetch('https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=index.js;hb=refs/heads/clickable')
     .then(res => res.text())
     .then(code => {
         const codeEl = document.querySelector('#code-viewer code');
index b81f0bb95ca94380cd9707846f34ec4b63ce26f8..f2f8a471d230d650008dc9b477740c85da26af79 100644 (file)
@@ -1,55 +1,9 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-<meta charset="UTF-8">
-<title>Canvas + Code</title>
 <style>
-/* Container grid: canvas + code side by side */
-#container {
-    display: grid;
-    grid-template-columns: 800px 1fr; /* canvas fixed, code fills remaining space */
-    gap: 10px;
-    height: 100vh; /* full viewport height */
-    align-items: start;
-}
-
-/* Canvas itself */
 #game {
-    margin: 0;
-    padding: 0;
     display: block;
-}
-
-/* Code viewer */
-#code-viewer {
-    margin: 0;
-    box-sizing: border-box; /* include padding in height */
-    overflow: auto; /* scroll if content too long */
-    font-family: monospace;
-    background-color: #f5f5f5;
-    white-space: pre-wrap;
-    max-height: 600px;
+    margin: 0 auto;
 }
 </style>
-</head>
-<body>
-
-<div id="container">
-    <canvas id="game"></canvas>
-    <pre id="code-viewer"> </pre>
-</div>
-
-<script src="https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=index.js;hb=refs/heads/quaternions"></script>
-<script>
-// Load the contents of index.js and display it in the <pre>
-fetch('https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=index.js;hb=refs/heads/quaternions')
-    .then(res => res.text())
-    .then(code => {
-        document.getElementById('code-viewer').textContent = code;
-    })
-    .catch(err => console.error('Could not load index.js:', err));
-</script>
-
-</body>
-</html>
 
+<canvas id="game"></canvas>
+<script src="index.js"></script>
index 933682849cca40fd1edfa0cb796e33f67fe36538..5f97d1d5fec9f51049916f1e7049afe19e6731e3 100644 (file)
--- a/index.js
+++ b/index.js
 
-/* ===================== 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];
-}
+// Meshes -------------------------------------------------
+const CUBE_MESH = [
+    // front face
+    {x: -1, y: 1,  z: -1},
+    {x: 1,  y: 1,  z: -1},
+    {x: 1,  y: -1, z: -1},
+
+    {x: 1,  y: -1, z: -1},
+    {x: -1, y: -1, z: -1},
+    {x: -1, y: 1,  z: -1},
+
+    // backface
+    {x: -1,  y: 1,  z: 1},
+    {x:  1,  y: 1,  z: 1},
+    {x:  1,  y: -1, z: 1},
+
+    {x:  1,  y: -1,  z: 1},
+    {x: -1,  y: -1,  z: 1},
+    {x: -1,  y:  1,  z: 1},
+
+
+    // left face
+    {x: -1, y:  1,  z: -1},
+    {x: -1, y:  1,  z: 1},
+    {x: -1, y: -1,  z: 1},
+
+    {x: -1, y: -1,  z: 1},
+    {x: -1, y: -1,  z: -1},
+    {x: -1, y: 1,  z: -1},
+
+    // right face
+    {x: 1, y:  1,  z: -1},
+    {x: 1, y:  1,  z: 1},
+    {x: 1, y: -1,  z: 1},
+
+    {x: 1, y: -1,  z: 1},
+    {x: 1, y: -1,  z: -1},
+    {x: 1, y: 1,  z: -1},
+
+    // top face
+    {x: -1, y: 1,  z: -1},
+    {x: 1, y: 1,  z: -1},
+    {x: -1, y: 1,  z: 1},
+
+    // bottom face
+    {x: -1, y: -1,  z: -1},
+    {x: 1, y: -1,  z: -1},
+    {x: -1, y: -1,  z: 1},
+
+]
+
+const PYRAMID_MESH = [
+  // front face
+  {x:  0, y:  1, z:  0},
+  {x: -1, y: -1, z:  1},
+  {x:  1, y: -1, z:  1},
+
+  // right face
+  {x:  0, y:  1, z:  0},
+  {x:  1, y: -1, z:  1},
+  {x:  1, y: -1, z: -1},
+
+  // back face
+  {x:  0, y:  1, z:  0},
+  {x:  1, y: -1, z: -1},
+  {x: -1, y: -1, z: -1},
+
+  // left face
+  {x:  0, y:  1, z:  0},
+  {x: -1, y: -1, z: -1},
+  {x: -1, y: -1, z:  1},
+
+  // bottom (two triangles)
+  {x: -1, y: -1, z:  1},
+  {x:  1, y: -1, z:  1},
+  {x:  1, y: -1, z: -1},
+
+  {x:  1, y: -1, z: -1},
+  {x: -1, y: -1, z: -1},
+  {x: -1, y: -1, z:  1},
+]
+
+const DIAMOND_MESH = [
+  // top
+  {x: 0,y: 1,z: 0},{x:-1,y: 0,z: 1},{x: 1,y: 0,z: 1},
+  {x: 0,y: 1,z: 0},{x: 1,y: 0,z: 1},{x: 1,y: 0,z:-1},
+  {x: 0,y: 1,z: 0},{x: 1,y: 0,z:-1},{x:-1,y: 0,z:-1},
+  {x: 0,y: 1,z: 0},{x:-1,y: 0,z:-1},{x:-1,y: 0,z: 1},
+
+  // bottom
+  {x: 0,y:-1,z: 0},{x: 1,y: 0,z: 1},{x:-1,y: 0,z: 1},
+  {x: 0,y:-1,z: 0},{x: 1,y: 0,z:-1},{x: 1,y: 0,z: 1},
+  {x: 0,y:-1,z: 0},{x:-1,y: 0,z:-1},{x: 1,y: 0,z:-1},
+  {x: 0,y:-1,z: 0},{x:-1,y: 0,z: 1},{x:-1,y: 0,z:-1},
+]
+
+const TETRAHEDRON_MESH = [
+  // face 1
+  {x:  1, y:  1, z:  1},
+  {x: -1, y: -1, z:  1},
+  {x: -1, y:  1, z: -1},
+
+  // face 2
+  {x:  1, y:  1, z:  1},
+  {x: -1, y: -1, z:  1},
+  {x:  1, y: -1, z: -1},
+
+  // face 3
+  {x:  1, y:  1, z:  1},
+  {x: -1, y:  1, z: -1},
+  {x:  1, y: -1, z: -1},
+
+  // face 4 (base)
+  {x: -1, y: -1, z:  1},
+  {x: -1, y:  1, z: -1},
+  {x:  1, y: -1, z: -1},
+]
+
+const OCTAHEDRON_MESH = [
+  {x: 0,y: 1,z: 0},{x:-1,y: 0,z: 0},{x: 0,y: 0,z: 1},
+  {x: 0,y: 1,z: 0},{x: 0,y: 0,z: 1},{x: 1,y: 0,z: 0},
+  {x: 0,y: 1,z: 0},{x: 1,y: 0,z: 0},{x: 0,y: 0,z:-1},
+  {x: 0,y: 1,z: 0},{x: 0,y: 0,z:-1},{x:-1,y: 0,z: 0},
+
+  {x: 0,y:-1,z: 0},{x: 0,y: 0,z: 1},{x:-1,y: 0,z: 0},
+  {x: 0,y:-1,z: 0},{x: 1,y: 0,z: 0},{x: 0,y: 0,z: 1},
+  {x: 0,y:-1,z: 0},{x: 0,y: 0,z:-1},{x: 1,y: 0,z: 0},
+  {x: 0,y:-1,z: 0},{x:-1,y: 0,z: 0},{x: 0,y: 0,z:-1},
+]
+
+// Global constant variables ------------------------------
+const CTX = game.getContext("2d")
+const VERTSIZE = 10
+const FOREGROUND_COLOR = "#b3ff66"
+const BOUNDRIES_COLOR = "#FFFFFF"
+const TEXT_COLOR = "#FFFFFF"
+const AXIS_MODES = ["none", "yz", "xz", "xy"]
+const KEYS = {};
+const NEAR = 0.1
+const FPS = 60
+const DT = 1/FPS
+
+// Other global variables ----------------------------------
+let meshes = [CUBE_MESH,PYRAMID_MESH, DIAMOND_MESH, TETRAHEDRON_MESH, OCTAHEDRON_MESH]
+let mesh = meshes[0]
+let inObject = false
+let canMoveObject = false
+let angle = Math.PI
+let position = {x: 0, y: 0, z: 8}
+let rotation = "none"
+let mouseDown = false
+let mousePos = {x: 0, y: 0}
+let showCollisionBox = true
+let moveObject = false
+let objectScale = 0.25
+let triangle = []
+let ri = 0
+let mi = 0
+let ti = -1
+
+// Canvas changes ------------------------------------------
+game.width = 800
+game.height = 800
+
+// Controls ------------------------------------------------
+window.addEventListener("keydown", e => {
+    if (KEYS[e.key.toLowerCase()]) return
+    KEYS[e.key.toLowerCase()] = true
+
+    if (KEYS["r"]) {
+        changeRotation()
+    }
+    if (KEYS["d"]) {
+        changeMesh()
+    }
+});
+
+window.addEventListener("keyup",   e => KEYS[e.key.toLowerCase()] = false);
+
+game.addEventListener("mousedown", e => {
+    if (e.button === 0) { // left mouse button
+        mouseDown = true
+    }
+})
+
+game.addEventListener("mouseup", e => {
+    if (e.button === 0) {
+        mouseDown = false
+    }
+})
+
+game.addEventListener("mousemove", e => {
+    let m = getMousePos(e)
+    if (canMoveObject) {
+        position.x = (m.x/game.width * 2 - 1) * position.z
+        position.y = ((1 - m.y/game.height) * 2 - 1) * position.z
+    }
+    mousePos.x = m.x;
+    mousePos.y = m.y;
+})
+
+game.addEventListener("wheel", e => {
+    e.preventDefault() // prevent page from scrolling
 
-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
-    ];
+    if (e.deltaY < 0) {
+        position.z += 0.4
+    } else if (e.deltaY > 0) {
+        position.z -= 0.4
+    }
+})
+
+// Control Functions ---------------------------------------
+function getMousePos(e) {
+    const rect = game.getBoundingClientRect()
+
+    return {
+        x: (e.clientX - rect.left) * (game.width  / rect.width),
+        y: (e.clientY - rect.top)  * (game.height / rect.height),
+    }
 }
 
-function quatConjugate(q) {
-    return [q[0], -q[1], -q[2], -q[3]];
+// Screen drawing functions --------------------------------
+function clear() {
+    CTX.fillStyle = "black"
+    CTX.fillRect(0, 0, game.width, game.height)
+
+    CTX.fillStyle = TEXT_COLOR;
+    CTX.font = "14px monospace";
+    CTX.fillText("press r to change rotation", 10, 18);
+    CTX.fillText("press d to change mesh", 10, 36);
+    CTX.fillText("click and drag to move object" , 10, 54);
+    CTX.fillText("scroll to change objects z position" , 10, 72);
 }
 
-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 point({x, y}, color) {
+    CTX.fillStyle = color
+    CTX.fillRect(x - VERTSIZE / 2,y - VERTSIZE/2, VERTSIZE, VERTSIZE)
 }
 
-/* ===================== CAMERA VECTORS ===================== */
-function cameraForward() {
-    return quatRotateVector(camera.rotation, {x:0,y:0,z:1});
+function line(a, b, color) {
+    CTX.strokeStyle = color
+    CTX.lineWidth = 2
+    CTX.beginPath()
+    CTX.moveTo(a.x , a.y )
+    CTX.lineTo(b.x , b.y)
+    CTX.stroke()
 }
-function cameraRight() {
-    return quatRotateVector(camera.rotation, {x:1,y:0,z:0});
+
+// Mesh functions ------------------------------------------
+function changeMesh() {
+    mesh = meshes[(mi += 1)%meshes.length]
 }
-function cameraUp() {
-    return quatRotateVector(camera.rotation, {x:0,y:1,z:0});
+
+// Rotation functions --------------------------------------
+function rotate({x, y, z}, axis, angle) {
+    // rotating a vector
+    const c = Math.cos(angle)
+    const s = Math.sin(angle)
+    switch (axis) {
+        case "none": return {x: x, y: y, z: z}
+        case   "yz": return {x, y:y*c-z*s, z: y*s+z*c} // x-axis
+        case   "xz": return {x: x*c-z*s, y, z: x*s+z*c} // y-axis
+        case   "xy": return {x: x*c-y*s, y: x*s+y*c, z} // z-axis
+    }
 }
 
-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 changeRotation() {
+    rotation = AXIS_MODES[(ri += 1)%AXIS_MODES.length]
 }
 
-/* ===================== PROJECTION ===================== */
-function project(p) {
-    return { x: p.x / p.z, y: p.y / p.z };
+// Graphics pipeline functions -----------------------------
+function project({ x, y, z }) {
+    const eps = 0.001
+    return { x: x / Math.max(z, eps), y: y / Math.max(z, eps), z }
 }
-function screen(p) {
+
+function screen({x, y}) {
+    // -1 .. 1 -> 0 .. w/h
     return {
-        x: (p.x + 1) * 0.5 * game.width,
-        y: (1 - (p.y + 1) * 0.5) * game.height
-    };
+        x: ((x + 1) * 0.5 * game.width) ,
+        y: ((1 - (y + 1) * 0.5) * game.width),
+    }
 }
 
-/* ===================== 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 makeTriangles (vert, color) {
+    ti = (ti + 1) % 3
+    triangle.push(vert)
 
-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 (ti != 2) return
+    for (let ti = 0; ti < 3; ++ti) {
+        let a = triangle[ti]
+        let b = triangle[(ti+1)%3]
+        // if (a.z <= NEAR || b.z <= NEAR) continue
+        line(a, b, color)
+    }
+    triangle = []
 }
 
-/* ===================== 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);
+function translate(p1, p2) {
+    return {
+        x: p1.x + p2.x, 
+        y: p1.y + p2.y, 
+        z: p1.z + p2.z
     }
+}
 
-    // --- 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;
+// Bounds checking functions -------------------------------
+function computeBounds(mesh) {
+    let min = {x: Infinity, y: Infinity}
+    let max = {x: -Infinity, y: -Infinity}
+
+    for (const v of mesh) {
+        min.x = Math.min(min.x, v.x)
+        min.y = Math.min(min.y, v.y)
+
+        max.x = Math.max(max.x, v.x)
+        max.y = Math.max(max.y, v.y)
     }
+    return {min, max}
+}
 
-    clear();
+function boundingBoxCorners({min, max}) {
+    return {
+        bottom: {left: {x: min.x, y: max.y}, right: {x: max.x, y: max.y}},
+        top: {left: {x: min.x, y: min.y}, right: {x: max.x, y: min.y}},
 
-    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);
+// Game loop ----------------------------------------------
+function frame() {
+    clear()
+    angle += Math.PI * DT
+    let translated_mesh = []
 
-            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 };
+    for (let i = 0; i < mesh.length; ++i) {
+        vert = mesh[i]
 
-            a = applyCamera(a);
-            b = applyCamera(b);
+        const translated = translate(rotate(vert, rotation, angle), position)
+        const projected = project(translated)
+        let obj = screen(projected)
+        translated_mesh.push(obj)
 
-            if (a.z<=NEAR || b.z<=NEAR) continue;
-            line(screen(project(a)), screen(project(b)));
-        }
+        // point(obj, FOREGROUND_COLOR)
+        makeTriangles(obj, FOREGROUND_COLOR)
+
+    }
+    const bounds = computeBounds(translated_mesh)
+    const rect = boundingBoxCorners(bounds)
+
+    if (showCollisionBox) {
+        line(rect.bottom.left, rect.bottom.right, BOUNDRIES_COLOR)
+        line(rect.top.left, rect.top.right, BOUNDRIES_COLOR)
+        line(rect.bottom.left, rect.top.left, BOUNDRIES_COLOR)
+        line(rect.bottom.right, rect.top.right, BOUNDRIES_COLOR)
     }
 
-    setTimeout(frame, 1000/FPS);
-}
-frame();
+    inObject =  (
+        mousePos.x > rect.top.left.x 
+        &&
+        mousePos.x < rect.top.right.x 
+        &&
+        mousePos.y > rect.top.left.y
+        &&
+        mousePos.y < rect.bottom.right.y
+    ) 
 
-/* ===================== INTERACTION ===================== */
-game.onclick = () => {
-    game.requestPointerLock();
-    axisModeIndex = (axisModeIndex + 1) % AXIS_MODES.length;
-    activeAxes = AXIS_MODES[axisModeIndex];
-};
+    if (mouseDown && inObject) canMoveObject = true
 
-window.addEventListener("mousemove", e => {
-    if (document.pointerLockElement !== game) return;
+    if (!mouseDown) canMoveObject = false
 
-    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);
+    setTimeout(frame, 1000/FPS)
+}
 
-    camera.rotation = quatMultiply(yaw, camera.rotation);
-    camera.rotation = quatMultiply(camera.rotation, pitch);
-});
+frame()