From: A-Larsen
Date: Thu, 15 Oct 2026 01:22:41 +0000 (-0400)
Subject: first commit
X-Git-Url: https://nyquistdev.net/git/?a=commitdiff_plain;h=refs%2Fheads%2Fsimple-rotation;p=basic-3d-example.git
first commit
---
diff --git a/README.adoc b/README.adoc
index ac1ab78..856ccd4 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,28 +1,23 @@
= Basic 3D Example
-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.
-
-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
+++++
+
-== Graphics Pipeline
+
+
+++++
-----
-vertex
- -> rotate cube
- -> translate cube (object position)
- -> apply camera (inverse transform)
- -> project
- -> screen
-----
+A good explaination how 3D works. If you click the cube you can change the axis
+it will rotate on.
-NOTE: Some parts of these pipeline may be left out if that part of the pipeline
- is not needed
+NOTE: This started off as code from Tsoding
-== Basic Projection Formula
+== Formulas
Projection formula for a vector latexmath:[[x,y,z]]:
@@ -36,9 +31,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):
[latexmath]
@@ -64,6 +56,7 @@ z' &= x\sin\theta + z\cos\theta
\end{aligned} \]
++++
+
Rotate vector formula for z-axis (x-y plane):
[latexmath]
@@ -77,344 +70,15 @@ z' &= z
\]
++++
-== 2. Single-axis rotation matrices (Euler Angles)
-
-=== Rotation about X-axis (roll)
-
-[latexmath]
-++++
-\boxed{
-R_x(\alpha) =
-\begin{bmatrix}
-1 & 0 & 0 \\
-0 & \cos\alpha & -\sin\alpha \\
-0 & \sin\alpha & \cos\alpha
-\end{bmatrix}
-}
-++++
-
-=== Rotation about Y-axis (pitch)
-
-[latexmath]
-++++
-\boxed{
-R_y(\beta) =
-\begin{bmatrix}
-\cos\beta & 0 & \sin\beta \\
-0 & 1 & 0 \\
--\sin\beta & 0 & \cos\beta
-\end{bmatrix}
-}
-++++
-
-=== Rotation about Z-axis (yaw)
-
-[latexmath]
-++++
-\boxed{
-R_z(\gamma) =
-\begin{bmatrix}
-\cos\gamma & -\sin\gamma & 0 \\
-\sin\gamma & \cos\gamma & 0 \\
-0 & 0 & 1
-\end{bmatrix}
-}
-++++
-
-
-Euler angles will always have gimbal lock. You can switch which axis has gimbal
-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].
-
-=== 1. What is a quaternion (in graphics)?
-
-A quaternion is a 4-component number:
-
-[latexmath]
-++++
-q = (w, x, y, z)
-++++
-
-In 3D graphics, we use **unit quaternions** to represent **rotations**.
-
-A rotation by:
-
-* angle latexmath:[\theta]
-* around a **unit axis** latexmath:[\vec{u} = (u_x, u_y, u_z)]
-
-is encoded as:
-
-[latexmath]
-++++
-\boxed{
-q = \left(
-\cos\frac{\theta}{2},
-\; u_x \sin\frac{\theta}{2},
-\; u_y \sin\frac{\theta}{2},
-\; u_z \sin\frac{\theta}{2}
-\right)
-}
-++++
-
-This is the **axisâangle -> quaternion** formula.
-
-==== Why half-angle?
-Because quaternion rotation comes from complex numbers and Eulerâs formula â
-doubling happens naturally in the math.
-
-=== 2. quatFromAxisAngle
-
-[source, js]
-----
-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];
-}
-----
-
-Youâre directly implementing:
-
-[latexmath]
-++++
-q = (\cos(\theta/2), \vec{u}\sin(\theta/2))
-++++
-
-* latexmath:[h = angle / 2] -> half-angle
-* latexmath:[cos(h)] â scalar part (w)
-* latexmath:[axis * sin(h)] â vector part latexmath:[(x,y,z)]
-
-IMPORTANT: `axis` **must be normalized** (length = 1), otherwise rotation will
- scale or skew.
-
-=== 3. Quaternion multiplication (rotation composition)
-
-[source, js]
-----
-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
- ];
-}
-----
-
-formula:
-
-[latexmath]
-++++
-q_1 = (w_1, \vec{v}_1), \quad q_2 = (w_2, \vec{v}_2)
-++++
-
-Quaternion multiplication is:
-
-[latexmath]
-++++
-\boxed{
-q_1 q_2 =
-\left(
-w_1 w_2 - \vec{v}_1 \cdot \vec{v}_2,\;
-w_1 \vec{v}_2 + w_2 \vec{v}_1 + \vec{v}_1 \times \vec{v}_2
-\right)
-}
-++++
-
-Expanded component-wise -> exactly your code.
-
-==== Why this matters
-
-* Quaternion multiplication **combines rotations**
-* Order matters:
-
-[latexmath]
-++++
-q_{\text{final}} = q_{\text{new}} \cdot q_{\text{current}}
-++++
-
-This avoids **gimbal lock**, unlike Euler angles.
-
-=== 4. Quaternion conjugate
-
-[source, js]
-----
-function quatConjugate(q) {
- return [q[0], -q[1], -q[2], -q[3]];
-}
-----
-
-For:
-
-[latexmath]
-++++
-q = (w, x, y, z)
-++++
-
-The conjugate is:
-
-[latexmath]
-++++
-\boxed{
-q^* = (w, -x, -y, -z)
-}
-++++
-
-=== Why this works
-
-If (q) is a **unit quaternion**:
-
-[latexmath]
-++++
-q^{-1} = q^*
-++++
-
-So conjugate = **inverse rotation**.
-
-This is why cameras use conjugates â moving the world opposite to the camera
-rotation.
-
-=== 5. Rotating a vector using quaternions
-
-[source, js]
-----
-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] };
-}
-----
-
-The key formula
-
-To rotate a vector latexmath:[\vec{v}]:
-
-1. Convert it to a **pure quaternion**
-
-[latexmath]
-++++
-v_q = (0, v_x, v_y, v_z)
-++++
-
-2. Apply:
-
-[latexmath]
-++++
-\boxed{
-v' = q \; v_q \; q^{-1}
-}
-++++
-
-3. Extract the vector part
-
-==== Why this works
-
-Quaternion rotation encodes:
-
-* axis
-* angle
-* orientation
-
-The sandwich product rotates without:
-
-* scaling
-* skewing
-* drifting
-
-=== Visual intuition
-
-A rotation quaternion represents:
-
-[latexmath]
-++++
-q = \cos(\theta/2) + \sin(\theta/2)(u_x i + u_y j + u_z k)
-++++
-
-This is the 3D analog of:
-
-[latexmath]
-++++
-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
+lock.
-if (!mouseDown) canMoveObject = false
-----
+== ChatGpt
+for my chatGpt Responses checkout my https://nyquistdev.net/git/?p=basic-3d-example.git;a=tree;f=docs/chatgpt-dump/docs;h=60b7a07cb968290e6068fc7e5f52dfd9e16a06e0;hb=HEAD[chatgpt-dumps].
== Links
* https://www.youtube.com/watch?v=qjWkNZ0SXfo[One Formula That demystifies 3D Graphics]
* https://www.youtube.com/watch?v=EZufiIwwqFA[Rotation matrix derivation (step-by-step prove)]
* https://matthew-brett.github.io/teaching/rotation_2d.html[rotation 2D by mathew]
-* https://www.youtube.com/watch?v=zc8b2Jo7mno[gimbal lock visual representation]
-* https://www.youtube.com/watch?v=zjMuIxRvygQ[3Blue1Brown | Quaternions and 3d rotation, explained interactively]
-* https://www.youtube.com/watch?v=d4EgbgTm0Bg&t=17s[3Blue1Brown | Visualizing the 4d numbers Quanternions]
-* https://phas.ubc.ca/~berciu/TEACHING/PHYS206/LECTURES/FILES/euler.pdf[euler.pdf]
diff --git a/docs/chatgpt-dump/Makehtml b/docs/chatgpt-dump/Makehtml
index 615bffe..f65394f 100755
--- a/docs/chatgpt-dump/Makehtml
+++ b/docs/chatgpt-dump/Makehtml
@@ -1,15 +1,2 @@
#!/usr/bin/env bash
-set -e
-
-PANDOC_OPTS=(
- --from markdown+smart
- --to html
- --standalone
- --metadata charset=utf-8
- --mathjax
-)
-
-for md in *.md; do
- html="${md%.md}.html"
- pandoc "$md" "${PANDOC_OPTS[@]}" -o "$html" --mathjax
-done
+pandoc -s quaternions.md -o quaternions.html
diff --git a/docs/chatgpt-dump/quaternions.html b/docs/chatgpt-dump/quaternions.html
index 35f63d2..4b54d57 100644
--- a/docs/chatgpt-dump/quaternions.html
+++ b/docs/chatgpt-dump/quaternions.html
@@ -161,6 +161,7 @@
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
+ .display.math{display: block; text-align: center; margin: 0.5rem auto;}
/* CSS for syntax highlighting */
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { line-height: 1.25; }
@@ -335,475 +336,5 @@ together.
quaternions, so the cube rotates smoothly around multiple axes
and keeps your click-to-change-axis feature.
Do you want me to do that?
-Absolutely! Letâs go through your quaternion-based 3D cube
-code line by line. Iâll break it down into sections so itâs
-easier to digest.
-
-1. Canvas Setup
-const game = document . getElementById ("game" );
- game. width = 400 ;
- game. height = 400 ;
-
-Gets the <canvas> element with ID
-game.
-Sets the canvas dimensions to 400Ã400 pixels.
-
-const BACKGROUND = "#101010" ;
-const FOREGROUND = "#50FF50" ;
-const TEXT = "#FFFFFF" ;
-
-Defines colors for background, cube lines, and text.
-
-const ctx = game. getContext ("2d" );
-
-Gets the 2D drawing context so we can draw shapes on the
-canvas.
-
-
-2. Cube Geometry
-
-
-vs = vertices of a cube (8
-points).
-Each vertex has x, y, z coordinates in 3D space (values
-between -0.25 and 0.25).
-
-
-
-fs = faces/edges to connect
-vertices.
-Some are quads [0,1,2,3], some are edges
-[0,4].
-This tells the code which points to draw lines
-between .
-
-let dz = 1 ;
-let angle = 0 ;
-let activeAxes = ["x" ];
-
-dz: distance to move the cube along Z (so itâs in front
-of the camera).
-angle: current rotation angle.
-activeAxes: which axes to rotate around (initially just
-x).
-
-
-3. Quaternion
-Functions
-a. Create a quaternion from an axis + angle
-function quatFromAxisAngle (axis, angle) {
- const half = angle / 2 ;
- const s = Math . sin (half);
- return [
- Math . cos (half), // w
- axis. x * s, // x
- axis. y * s, // y
- axis. z * s // z
- ];
- }
-
-Converts axis-angle rotation into a quaternion
-[w, x, y, z].
-half = half the rotation angle.
-s = sin(half) scales the axis.
-w = cos(half).
-
-b. Multiply two quaternions
-function quatMultiply (q1, q2) { ... }
-
-Standard quaternion multiplication formula.
-Combines two rotations into one. Order matters!
-q1*q2 rotates q2 first, then q1.
-
-c. Rotate a vector by a quaternion
-function quatRotateVector (q, v) { ... }
-
-
-4. Drawing Functions
-Clear canvas and draw text
-function clear () {
- ctx. fillStyle = BACKGROUND;
- ctx. fillRect (0 , 0 , game. width , game. height );
- ctx. fillStyle = TEXT;
- ctx. font = "20px serif" ;
- ctx. fillText (" *click me to change rotation axes" , 10 , game. height - 15 );
- ctx. fillText ("current axes: " + activeAxes. join ("," ), 10 , 20 );
- }
-
-Clears the canvas with the background color.
-Draws instructions and current axes at the top/bottom.
-
-Draw a point
-function point ({x, y}) {
- const s = 20 ;
- ctx. fillStyle = FOREGROUND;
- ctx. fillRect (x - s/ 2 , y - s/ 2 , s, s);
- }
-
-Draws a small square at (x, y) on the screen.
-Not used in the final version, but useful for debugging
-vertices.
-
-Draw a line
-function line (p1, p2) { ... }
-
-Draws a thick line from p1 to p2 in canvas
-coordinates.
-
-Convert 3D to screen coordinates
-function screen (p) {
- return {
- x : (p. x + 1 )/ 2 * game. width ,
- y : (1 - (p. y + 1 )/ 2 ) * game. height
- };
- }
-
-Maps normalized coordinates [-1,1] to screen
-coordinates [0, width/height].
-Y-axis is flipped because canvas y=0 is top.
-
-Project 3D to 2D
-function project ({x, y, z}) {
- return { x : x/ z, y : y/ z };
- }
-
-Simple perspective projection : divides x and y by z
-(makes things farther away appear smaller).
-
-Translate along Z
-function translateZ ({x, y, z}, dz) {
- return {x, y, z : z + dz};
- }
-
-Moves the cube forward/backward so it appears in front of the
-camera.
-
-
-5. Animation Loop
-const FPS = 60 ;
-
-function frame () {
- const dt = 1 / FPS;
- angle += Math . PI * dt;
-
-Updates rotation angle every frame based on FPS.
-
-Create combined quaternion
- let q = [1 , 0 , 0 , 0 ]; // identity quaternion
- activeAxes. forEach (axis => {
- let qAxis;
- if (axis === "x" ) qAxis = quatFromAxisAngle ({x : 1 , y : 0 , z : 0 }, angle);
- if (axis === "y" ) qAxis = quatFromAxisAngle ({x : 0 , y : 1 , z : 0 }, angle);
- if (axis === "z" ) qAxis = quatFromAxisAngle ({x : 0 , y : 0 , z : 1 }, angle);
- q = quatMultiply (qAxis, q); // multiply in order
- });
-
-Starts with identity quaternion (no rotation).
-For each active axis, creates a quaternion for rotation around that
-axis.
-Combines them by multiplying â this lets the cube rotate around
-multiple axes simultaneously .
-
-Draw the cube
- clear ();
- for (const f of fs) {
- for (let i = 0 ; i < f. length ; ++ i) {
- const a = vs[f[i]];
- const b = vs[f[(i + 1 )% f. length ]];
- line (
- screen (project (translateZ (quatRotateVector (q, a), dz))),
- screen (project (translateZ (quatRotateVector (q, b), dz)))
- );
- }
- }
-
-Clears the canvas.
-Loops through each face/edge f.
-Rotates each vertex using the combined quaternion.
-Translates it forward along Z.
-Projects 3D coordinates into 2D screen coordinates.
-Draws lines connecting vertices.
-
-Schedule next frame
- setTimeout (frame, 1000 / FPS);
- }
-setTimeout (frame, 1000 / FPS);
-
-Calls frame() every 1/60th of a second to animate.
-
-
-6. Handle Clicks
- game. onclick = () => {
- const allAxes = [["x" ], ["y" ], ["z" ], ["x" , "y" ], ["y" , "z" ], ["x" , "z" ], ["x" , "y" , "z" ]];
- const index = allAxes. findIndex (a => JSON . stringify (a) === JSON . stringify (activeAxes));
- activeAxes = allAxes[(index + 1 ) % allAxes. length ];
- };
-
-On click, cycles through different axis
-combinations : x, y, z, xy, yz, xz, xyz.
-Updates activeAxes so the cube rotates differently next
-frame.
-
-
-â
Summary
-
-Vertices + Faces : Define cube geometry.
-Quaternions : Handle rotation around multiple axes
-safely.
-Project & Screen Functions : Convert 3D points
-to 2D canvas.
-Animation Loop : Updates rotation, applies
-quaternion rotation, and draws cube.
-Click Handler : Change which axes to rotate
-around.
-
-
-If you want, I can also draw a diagram showing the quaternion
-rotation concept visually â it really helps to understand how
-it rotates the cube around multiple axes at once.
-Do you want me to make that diagram?
-
-
-diagram
-
-Great question â this is exactly the right point to slow down and
-understand the math , because quaternions feel âmagicalâ
-until you see why each line exists.
-Iâll go step by step:
-
-1. What is a quaternion (in
-graphics)?
-A quaternion is a 4-component number:
-[ q = (w, x, y, z)]
-In 3D graphics, we use unit quaternions to represent
-rotations .
-A rotation by:
-
-angle ( )
-around a unit axis ( = (u_x, u_y, u_z) )
-
-is encoded as:
-[ ]
-This is the axisâangle â quaternion formula.
-Why half-angle? Because quaternion rotation comes from complex
-numbers and Eulerâs formula â doubling happens naturally in the
-math.
-
-2. quatFromAxisAngle
-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];
- }
-Math behind it
-Youâre directly implementing:
-[ q = ((/2), (/2))]
-
-h = angle / 2 â half-angle
-cos(h) â scalar part (w)
-axis * sin(h) â vector part ((x,y,z))
-
-Important rule
-axis must be normalized (length = 1),
-otherwise rotation will scale or skew.
-
-3. Quaternion
-multiplication (rotation composition)
-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
- ];
- }
-
-Given: [ q_1 = (w_1, _1), q_2 = (w_2, _2)]
-Quaternion multiplication is:
-[ ]
-Expanded component-wise â exactly your code.
-Why this matters
-
-Quaternion multiplication combines rotations
-Order matters: [ q_{} = q_{} q_{}]
-
-This avoids gimbal lock , unlike Euler angles.
-
-4. Quaternion conjugate
-function quatConjugate (q) {
- return [q[0 ], - q[1 ], - q[2 ], - q[3 ]];
- }
-Math meaning
-For: [ q = (w, x, y, z)]
-The conjugate is:
-[ ]
-Why this works
-If (q) is a unit quaternion :
-[ q^{-1} = q^*]
-So conjugate = inverse rotation .
-This is why cameras use conjugates â moving the world opposite to the
-camera rotation.
-
-5. Rotating a vector using
-quaternions
-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 ] };
- }
-
-To rotate a vector ( ):
-
-Convert it to a pure quaternion [ v_q = (0, v_x,
-v_y, v_z)]
-Apply: [ ]
-Extract the vector part
-
-Why this works
-Quaternion rotation encodes:
-
-axis
-angle
-orientation
-
-The sandwich product rotates without:
-
-scaling
-skewing
-drifting
-
-
-6. Why
-quaternions are better than matrices (for rotation)
-
-
-
-
-
-
-Gimbal lock
-â
-â
-â
-
-
-Smooth interpolation
-â
-â ï¸
-â
(SLERP)
-
-
-Compact
-â
-â
-â
-
-
-Numerical stability
-â ï¸
-â ï¸
-â
-
-
-
-
-7. Visual intuition
-A rotation quaternion represents:
-[ q = (/2) + (/2)(u_x i + u_y j + u_z k)]
-This is the 3D analog of:
-[ e^{i} = + i]
-
-8. Best learning
-resources (highly recommended)
-Beginner-friendly
-
-Graphics-oriented
-
-Math reference
-
-
-9. Mental model to keep
-
-Quaternions donât rotate vectors directly. They rotate
-space itself around the vector.
-
-Once that clicks, camera math suddenly makes sense.
-
-If you want next:
-
-ð quaternion â rotation matrix
-ð¥ why camera uses inverse rotation
-ð¯ SLERP (smooth rotation interpolation)
-ð§ geometric intuition with diagrams
-
-Just tell me ð