time for a snapshot
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Step 02 — Length, Normalize, Dot
|
||||
|
||||
## Concept
|
||||
|
||||
### Length (magnitude)
|
||||
|
||||
An arrow `(x, y)` has a length: how far it reaches. Pythagoras:
|
||||
`length = sqrt(x*x + y*y)`. A velocity's length is its **speed**.
|
||||
|
||||
### Normalize
|
||||
|
||||
Often you want *just the direction* of an arrow, with length exactly 1 (a "unit
|
||||
vector"). You get it by dividing the arrow by its own length:
|
||||
`(x / len, y / len)`.
|
||||
|
||||
This is how your engine turns "the player pressed up+left" into a clean diagonal
|
||||
direction before multiplying by a speed — so diagonal movement isn't faster than
|
||||
straight movement.
|
||||
|
||||
> ⚠️ **The zero-vector trap.** What is the length of `(0, 0)`? Zero. What is
|
||||
> `0 / 0`? `NaN`. If you normalize a zero vector naively, you poison it with
|
||||
> `NaN`, and `NaN` spreads through every later calculation silently. A correct
|
||||
> `normalize` must check for zero length and return `(0, 0)` instead of dividing.
|
||||
> Remember this trap — it is exactly the kind of bug that hides in a real engine.
|
||||
|
||||
### Dot product
|
||||
|
||||
`dot(a, b) = a.x*b.x + a.y*b.y`. One number out of two vectors. For now just
|
||||
implement it; in step 09 you'll learn that it answers "how much of arrow A points
|
||||
along arrow B?" — the key to sliding along a wall.
|
||||
|
||||
## Task
|
||||
|
||||
Implement `length`, `normalize` (zero-safe!), and `dot` in `mathv.ts`.
|
||||
`vec`/`add`/`sub`/`scale` are already provided in `given.ts`.
|
||||
|
||||
```sh
|
||||
bun test workshop/steps/02-vectors-length
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
// Finished in step 01 — provided so you only implement the new ideas here.
|
||||
export type Vec = { x: number; y: number };
|
||||
export const vec = (x: number, y: number): Vec => ({ x, y });
|
||||
export const add = (a: Vec, b: Vec): Vec => ({ x: a.x + b.x, y: a.y + b.y });
|
||||
export const sub = (a: Vec, b: Vec): Vec => ({ x: a.x - b.x, y: a.y - b.y });
|
||||
export const scale = (a: Vec, s: number): Vec => ({ x: a.x * s, y: a.y * s });
|
||||
@@ -0,0 +1,27 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { vec } from "./given.ts";
|
||||
import { dot, length, normalize } from "./mathv.ts";
|
||||
|
||||
test("length uses Pythagoras", () => {
|
||||
expect(length(vec(3, 4))).toBe(5);
|
||||
expect(length(vec(0, 0))).toBe(0);
|
||||
});
|
||||
|
||||
test("normalize gives a unit vector in the same direction", () => {
|
||||
const n = normalize(vec(3, 4));
|
||||
expect(n.x).toBeCloseTo(0.6);
|
||||
expect(n.y).toBeCloseTo(0.8);
|
||||
expect(length(n)).toBeCloseTo(1);
|
||||
});
|
||||
|
||||
test("normalize of the zero vector is (0,0), not NaN", () => {
|
||||
const n = normalize(vec(0, 0));
|
||||
expect(n).toEqual({ x: 0, y: 0 });
|
||||
expect(Number.isNaN(n.x)).toBe(false);
|
||||
expect(Number.isNaN(n.y)).toBe(false);
|
||||
});
|
||||
|
||||
test("dot product", () => {
|
||||
expect(dot(vec(1, 0), vec(0, 1))).toBe(0); // perpendicular
|
||||
expect(dot(vec(2, 3), vec(4, 5))).toBe(23); // 8 + 15
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Vec } from "./given.ts";
|
||||
|
||||
/** The length (magnitude) of the arrow. */
|
||||
export function length(a: Vec): number {
|
||||
return Math.sqrt(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit-length vector pointing the same way as `a`.
|
||||
* MUST return (0, 0) when `a` is the zero vector — do not divide by zero.
|
||||
*/
|
||||
export function normalize(a: Vec): Vec {
|
||||
const len = length(a);
|
||||
const x = a.x === 0 ? 0 : a.x / len;
|
||||
const y = a.y === 0 ? 0 : a.y / len;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
/** The dot product a·b. */
|
||||
export function dot(a: Vec, b: Vec): number {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
Reference in New Issue
Block a user