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=f85b08fef30339996f41f5591371919fb676dbb9;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;
- -
const BACKGROUND = "#101010";
-const FOREGROUND = "#50FF50";
-const TEXT = "#FFFFFF";
- -
const ctx = game.getContext("2d");
- -
-

2. Cube Geometry

-
const vs = [ ... ];
- -
const fs = [ ... ];
- -
let dz = 1;
-let angle = 0;
-let activeAxes = ["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
-    ];
-}
- -

b. Multiply two quaternions

-
function quatMultiply(q1, q2) { ... }
- -

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);
-}
- -

Draw a point

-
function point({x, y}) {
-    const s = 20;
-    ctx.fillStyle = FOREGROUND;
-    ctx.fillRect(x - s/2, y - s/2, s, s);
-}
- -

Draw a line

-
function line(p1, p2) { ... }
- -

Convert 3D to screen coordinates

-
function screen(p) {
-    return {
-        x: (p.x + 1)/2 * game.width,
-        y: (1 -(p.y + 1)/2) * game.height
-    };
-}
- -

Project 3D to 2D

-
function project({x, y, z}) {
-    return { x: x/z, y: y/z };
-}
- -

Translate along Z

-
function translateZ({x,y,z}, dz) {
-    return {x, y, z: z + dz};
-}
- -
-

5. Animation Loop

-
const FPS = 60;
-
-function frame() {
-    const dt = 1/FPS;
-    angle += Math.PI*dt;
- -

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
-    });
- -

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)))
-            );
-        }
-    }
- -

Schedule next frame

-
    setTimeout(frame, 1000/FPS);
-}
-setTimeout(frame, 1000/FPS);
- -
-

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];
-};
- -
-

✅ Summary

- -
-

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?

-
- - -
-

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:

- -

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))]

- -

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
-    ];
-}
-

Math formula

-

Given: [ q_1 = (w_1, _1), q_2 = (w_2, _2)]

-

Quaternion multiplication is:

-

[ ]

-

Expanded component-wise → exactly your code.

-

Why this matters

- -

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] };
-}
-

The key formula

-

To rotate a vector ( ):

-
    -
  1. Convert it to a pure quaternion [ v_q = (0, v_x, -v_y, v_z)]

  2. -
  3. Apply: [ ]

  4. -
  5. Extract the vector part

  6. -
-

Why this works

-

Quaternion rotation encodes:

- -

The sandwich product rotates without:

- -
-

6. Why -quaternions are better than matrices (for rotation)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ProblemEuler AnglesMatricesQuaternions
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:

- -

Just tell me 👍

diff --git a/docs/chatgpt-dump/quaternions.md b/docs/chatgpt-dump/quaternions.md index c406140..6c5ad5b 100644 --- a/docs/chatgpt-dump/quaternions.md +++ b/docs/chatgpt-dump/quaternions.md @@ -1,6 +1,4 @@ -Absolutely! I can show you how to incorporate **quaternions** into your code to -allow smooth rotation around **multiple axes at once**, avoiding the limitations -of your current `rotate` function. +Absolutely! I can show you how to incorporate **quaternions** into your code to allow smooth rotation around **multiple axes at once**, avoiding the limitations of your current `rotate` function. Let’s go step by step. @@ -124,859 +122,3 @@ If you want, I can rewrite your **entire code snippet using quaternions**, so th 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** - -```js -const game = document.getElementById("game"); -game.width = 400; -game.height = 400; -``` - -* Gets the `` element with ID `game`. -* Sets the canvas dimensions to 400×400 pixels. - -```js -const BACKGROUND = "#101010"; -const FOREGROUND = "#50FF50"; -const TEXT = "#FFFFFF"; -``` - -* Defines colors for background, cube lines, and text. - -```js -const ctx = game.getContext("2d"); -``` - -* Gets the 2D drawing context so we can draw shapes on the canvas. - ---- - -## **2. Cube Geometry** - -```js -const vs = [ ... ]; -``` - -* `vs` = **vertices** of a cube (8 points). -* Each vertex has `x, y, z` coordinates in 3D space (values between -0.25 and 0.25). - -```js -const fs = [ ... ]; -``` - -* `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**. - -```js -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** - -```js -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** - -```js -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** - -```js -function quatRotateVector(q, v) { ... } -``` - -* Rotates a 3D vector `v` using quaternion `q`. -* Steps: - - 1. Convert vector to quaternion `qv = [0, vx, vy, vz]`. - 2. Compute conjugate `qConj = [w, -x, -y, -z]`. - 3. Rotate: `v' = q * qv * q⁻¹`. - 4. Return new `x,y,z` of rotated vector. - ---- - -## **4. Drawing Functions** - -**Clear canvas and draw text** - -```js -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** - -```js -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** - -```js -function line(p1, p2) { ... } -``` - -* Draws a thick line from `p1` to `p2` in canvas coordinates. - -**Convert 3D to screen coordinates** - -```js -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** - -```js -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** - -```js -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** - -```js -const FPS = 60; - -function frame() { - const dt = 1/FPS; - angle += Math.PI*dt; -``` - -* Updates rotation angle every frame based on FPS. - -**Create combined quaternion** - -```js - 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** - -```js - 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** - -```js - setTimeout(frame, 1000/FPS); -} -setTimeout(frame, 1000/FPS); -``` - -* Calls `frame()` every 1/60th of a second to animate. - ---- - -## **6. Handle Clicks** - -```js -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](https://nyquistdev.net/git/?p=basic-3d-example.git;a=blob_plain;f=docs/chatgpt-dump/quaternion-rotation.png;hb=HEAD) - -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 ( \theta ) -* around a **unit axis** ( \vec{u} = (u_x, u_y, u_z) ) - -is encoded as: - -[ -\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` - -```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]; -} -``` - -### Math behind it - -You’re directly implementing: - -[ -q = (\cos(\theta/2), \vec{u}\sin(\theta/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) - -```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 - ]; -} -``` - -### Math formula - -Given: -[ -q_1 = (w_1, \vec{v}_1), \quad q_2 = (w_2, \vec{v}_2) -] - -Quaternion multiplication is: - -[ -\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: - [ - q_{\text{final}} = q_{\text{new}} \cdot q_{\text{current}} - ] - -This avoids **gimbal lock**, unlike Euler angles. - ---- - -# 4. Quaternion conjugate - -```js -function quatConjugate(q) { - return [q[0], -q[1], -q[2], -q[3]]; -} -``` - -### Math meaning - -For: -[ -q = (w, x, y, z) -] - -The conjugate is: - -[ -\boxed{ -q^* = (w, -x, -y, -z) -} -] - -### 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 - -```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 ( \vec{v} ): - -1. Convert it to a **pure quaternion** - [ - v_q = (0, v_x, v_y, v_z) - ] - -2. Apply: - [ - \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 - ---- - -# 6. Why quaternions are better than matrices (for rotation) - -| Problem | Euler Angles | Matrices | Quaternions | -| -------------------- | ------------ | -------- | ----------- | -| Gimbal lock | ❌ | ❌ | ✅ | -| Smooth interpolation | ❌ | ⚠️ | ✅ (SLERP) | -| Compact | ✅ | ❌ | ✅ | -| Numerical stability | ⚠️ | ⚠️ | ✅ | - ---- - -# 7. Visual intuition - -A rotation quaternion represents: - -[ -q = \cos(\theta/2) + \sin(\theta/2)(u_x i + u_y j + u_z k) -] - -This is the 3D analog of: - -[ -e^{i\theta} = \cos\theta + i\sin\theta -] - ---- - -# 8. Best learning resources (highly recommended) - -### Beginner-friendly - -* **3Blue1Brown – Quaternions** - - * [https://www.youtube.com/watch?v=zjMuIxRvygQ](https://www.youtube.com/watch?v=zjMuIxRvygQ) -* **Visualizing Quaternions** - - * [https://eater.net/quaternions](https://eater.net/quaternions) - -### Graphics-oriented - -* **LearnOpenGL – Camera & Quaternions** - - * [https://learnopengl.com/Getting-started/Camera](https://learnopengl.com/Getting-started/Camera) -* **Scratchapixel – Quaternions** - - * [https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/quaternions](https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/quaternions) - -### Math reference - -* Wikipedia (surprisingly solid) - - * [https://en.wikipedia.org/wiki/Quaternion](https://en.wikipedia.org/wiki/Quaternion) - * [https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation](https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation) - ---- - -# 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 👍 - -Absolutely — let’s unpack those sentences slowly and connect the symbols to intuition. - ---- - -## 1. “A quaternion is an expression of the form” - -[ -a + b,\mathbf{i} + c,\mathbf{j} + d,\mathbf{k} -] - -This means that **every quaternion can be written as a sum of four parts**: - -* one **real (scalar)** part -* three **imaginary (vector-like)** parts - -So a quaternion has **four real numbers** in it, not just two like a complex number. - ---- - -## 2. The coefficients (a, b, c, d) - -> “where (a, b, c, d) are real numbers” - -* These are just ordinary real numbers (like (2), (-1.3), (\pi), etc.). -* They tell you **how much** of each basis element the quaternion contains. - -Think of them as coordinates. - ---- - -## 3. The symbols (\mathbf{i}, \mathbf{j}, \mathbf{k}) - -> “and (\mathbf{i}, \mathbf{j}, \mathbf{k}) are symbols that can be interpreted as unit-vectors pointing along the three spatial axes.” - -These symbols are **not numbers** in the usual sense. They are **basis elements** that behave in special ways. - -### Interpretation: - -* (\mathbf{i}) → x-axis direction -* (\mathbf{j}) → y-axis direction -* (\mathbf{k}) → z-axis direction - -So: -[ -b,\mathbf{i} + c,\mathbf{j} + d,\mathbf{k} -] -looks exactly like a 3D vector: -[ -(b, c, d) -] - ---- - -## 4. Scalar part vs vector part - -The quaternion naturally splits into two pieces: - -[ -\underbrace{a}*{\text{scalar part}} -\quad + \quad -\underbrace{(b,\mathbf{i} + c,\mathbf{j} + d,\mathbf{k})}*{\text{vector part}} -] - -This matches the earlier notation: -[ -\mathbf{q} = (r, \vec{v}) -] -where: - -* (r = a) -* (\vec{v} = (b, c, d)) - ---- - -## 5. How this extends complex numbers - -Complex numbers look like: -[ -a + b,\mathbf{i} -] - -Quaternions **extend** this idea: - -* one real axis -* three imaginary axes - -So instead of a 2D number system, quaternions live in **4D**. - ---- - -## 6. Important difference from vectors - -Although (\mathbf{i}, \mathbf{j}, \mathbf{k}) *look* like unit vectors, they are actually **algebraic objects with multiplication rules**: - -[ -\mathbf{i}^2 = \mathbf{j}^2 = \mathbf{k}^2 = \mathbf{i}\mathbf{j}\mathbf{k} = -1 -] - -and multiplication is **not commutative**: -[ -\mathbf{i}\mathbf{j} = \mathbf{k}, \quad -\mathbf{j}\mathbf{i} = -\mathbf{k} -] - -This is why quaternions are so powerful for rotations. - ---- - -## 7. Geometric intuition - -You can think of a quaternion as: - -* a **real number** (a), plus -* a **3D direction and magnitude** ((b, c, d)) - -When used for rotations: - -* (a) relates to the rotation angle -* ((b, c, d)) relates to the rotation axis - ---- - -## 8. Plain-English summary - -> A quaternion is built from one real number and three imaginary components. The imaginary parts behave like directions in 3D space and follow special multiplication rules. Together, they form a 4-dimensional number system that can represent 3D rotations cleanly and efficiently. - -If you’d like, I can next: - -* show how quaternion multiplication works geometrically -* explain why quaternions avoid gimbal lock -* relate quaternions directly to rotation matrices - -Great question — this is where quaternions really become *geometric*, not just algebraic. - -I’ll build this up step by step, from intuition to the final geometric meaning. - ---- - -## 1. Unit quaternions represent rotations - -A **unit quaternion** (length = 1) represents a 3D rotation. - -It is written as: -[ -q = \cos\frac{\theta}{2} + \hat{u},\sin\frac{\theta}{2} -] -where: - -* (\theta) = rotation angle -* (\hat{u} = u_x\mathbf{i} + u_y\mathbf{j} + u_z\mathbf{k}) is a **unit vector** (rotation axis) - -This already encodes geometry: - -* **axis** → direction of the vector part -* **angle** → stored in the sine/cosine terms - ---- - -## 2. Rotating a vector using a quaternion - -A 3D vector (\vec{v}) is treated as a **pure quaternion**: -[ -\vec{v} ;\longrightarrow; 0 + v_x\mathbf{i} + v_y\mathbf{j} + v_z\mathbf{k} -] - -The rotated vector is: -[ -\vec{v},' = q,\vec{v},q^{-1} -] - -This is not arbitrary — this *is* the geometric action of rotation. - ---- - -## 3. What quaternion multiplication does geometrically - -### Key idea: - -> **Quaternion multiplication composes rotations.** - -If: - -* (q_1) rotates by (\theta_1) around axis (\hat{u}_1) -* (q_2) rotates by (\theta_2) around axis (\hat{u}_2) - -Then: -[ -q = q_2 q_1 -] -represents: - -> “First rotate by (q_1), then rotate by (q_2)” - -This is exactly how matrix multiplication works for rotations. - ---- - -## 4. Why order matters (non-commutativity) - -Quaternion multiplication is **not commutative**: -[ -q_1 q_2 \neq q_2 q_1 -] - -Geometrically: - -* Rotating around **x then y** is not the same as **y then x** - -This matches real-world 3D rotations. - ---- - -## 5. Vector–scalar decomposition of multiplication - -Let: -[ -q_1 = (r_1, \vec{v}_1), \quad -q_2 = (r_2, \vec{v}_2) -] - -Their product is: -[ -q_2 q_1 = -\big( -r_2 r_1 - \vec{v}_2 \cdot \vec{v}_1,; -r_2 \vec{v}_1 + r_1 \vec{v}_2 + \vec{v}_2 \times \vec{v}_1 -\big) -] - -### Geometric meaning of each term: - -* **Dot product** → angle alignment -* **Cross product** → axis perpendicular to both rotations -* **Linear terms** → mixing scalar and vector parts - -So quaternion multiplication naturally combines: - -* angle information (dot) -* axis orientation (cross) - ---- - -## 6. Visual intuition - -Think of a unit quaternion as: - -* a point on a **4D unit sphere** -* encoding a rotation in 3D space - -Multiplying two unit quaternions: - -* moves you along that sphere -* in a way that corresponds exactly to doing one rotation after another - ---- - -## 7. Why the half-angle appears - -You might wonder: - -> Why (\theta/2), not (\theta)? - -Geometrically: - -* Rotations form a curved space (SO(3)) -* Unit quaternions live in a simpler space (SU(2)) that *double-covers* rotations -* The half-angle makes multiplication correspond cleanly to rotation composition - -That’s why: -[ -q \text{ and } -q -] -represent the **same rotation**. - ---- - -## 8. Simple concrete example - -Rotate 90° about the z-axis: -[ -q = \cos 45^\circ + \mathbf{k}\sin 45^\circ -] - -Apply it to (\mathbf{i}) (the x-axis): -[ -\mathbf{i}' = q,\mathbf{i},q^{-1} = \mathbf{j} -] - -Geometrically: - -* x-axis rotates into y-axis -* exactly what you expect - ---- - -## 9. Plain-English summary - -> Quaternion multiplication is not “multiplying numbers.” -> It is **combining rotations in 3D space**. -> The algebra mirrors the geometry: dot products handle angles, cross products handle axes, and order encodes rotation order. - -If you want, next I can: - -* derive the rotation formula step-by-step -* show the link to rotation matrices -* explain the double-cover and why (q) and (-q) are the same rotation - diff --git a/example.html b/example.html index 0883092..6195d76 100644 --- a/example.html +++ b/example.html @@ -63,9 +63,9 @@ body { - + + +