time for a snapshot
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# Step 09 — Slide (this is what `dot` was for)
|
||||
|
||||
Back in step 02 you implemented `dot` and I said "you'll see later." This is
|
||||
later. Sliding is the difference between a game that feels good and one where you
|
||||
stick to every wall like glue.
|
||||
|
||||
## The problem
|
||||
|
||||
You're moving with velocity `v` and you hit a wall whose outward normal is `n`.
|
||||
If you just *stop* (velocity → 0), the player jams against the wall — press
|
||||
into a wall diagonally and all motion dies, even the part that was parallel to
|
||||
the wall and perfectly fine. What we actually want: **cancel only the part of `v`
|
||||
that pushes *into* the wall, and keep the part that runs *along* it.** That's a
|
||||
slide.
|
||||
|
||||
## The math (projection)
|
||||
|
||||
Any velocity `v` can be split into two pieces relative to the wall:
|
||||
|
||||
- the part **along the normal** (into/out of the wall) — this is what the wall forbids,
|
||||
- the part **along the wall surface** (perpendicular to the normal) — this is fine.
|
||||
|
||||
Because `n` is a **unit vector**, the amount of `v` pointing along `n` is exactly
|
||||
`dot(v, n)`. That single number is "how much of `v` goes straight into the wall."
|
||||
The vector piece pointing into the wall is `n * dot(v, n)`. Subtract it off:
|
||||
|
||||
```
|
||||
vSlide = v - n * dot(v, n)
|
||||
```
|
||||
|
||||
What's left has **zero** component along the normal — it lies flat against the
|
||||
wall. (That's the geometric meaning of `dot`: it measures how much two vectors
|
||||
share a direction. Subtract the shared-with-the-normal part, and nothing pointing
|
||||
into the wall survives.)
|
||||
|
||||
### Feel it with numbers
|
||||
|
||||
Move `v = (10, 5)` into a wall whose normal is `n = (-1, 0)` (a vertical wall on
|
||||
your right, pushing you left):
|
||||
|
||||
- `dot(v, n) = 10*(-1) + 5*0 = -10`
|
||||
- `n * dot = (-1,0) * -10 = (10, 0)`
|
||||
- `vSlide = (10,5) - (10,0) = (0, 5)`
|
||||
|
||||
The rightward motion (10) is gone — that was driving you into the wall — but the
|
||||
downward motion (5) survives untouched. You slide down the wall. Exactly right.
|
||||
|
||||
This is the very last line of your engine's `moveAndSlide`:
|
||||
`velocity -= normal * (velocity · normal)`.
|
||||
|
||||
## Task
|
||||
|
||||
Implement `slide(v, normal)` in `slide.ts` using `dot`, `scale`, and `sub` (all
|
||||
in `given.ts`). Return the velocity with its into-the-wall component removed.
|
||||
|
||||
```sh
|
||||
bun test workshop/steps/09-slide
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
// Finished in earlier steps.
|
||||
export type Vec = { x: number; y: number };
|
||||
export const vec = (x: number, y: number): Vec => ({ x, 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 });
|
||||
export const dot = (a: Vec, b: Vec): number => a.x * b.x + a.y * b.y;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { dot, vec } from "./given.ts";
|
||||
import { slide } from "./slide.ts";
|
||||
|
||||
test("vertical wall kills horizontal motion, keeps vertical", () => {
|
||||
// moving (10,5) into a wall with normal (-1,0)
|
||||
const s = slide(vec(10, 5), vec(-1, 0));
|
||||
expect(s.x).toBeCloseTo(0);
|
||||
expect(s.y).toBeCloseTo(5);
|
||||
});
|
||||
|
||||
test("horizontal wall kills vertical motion, keeps horizontal", () => {
|
||||
const s = slide(vec(10, 5), vec(0, -1));
|
||||
expect(s.x).toBeCloseTo(10);
|
||||
expect(s.y).toBeCloseTo(0);
|
||||
});
|
||||
|
||||
test("result always runs along the wall (perpendicular to the normal)", () => {
|
||||
// a diagonal (unit) normal: 0.36 + 0.64 = 1
|
||||
const normal = vec(-0.6, -0.8);
|
||||
const s = slide(vec(10, 0), normal);
|
||||
// the slid velocity has no component into the wall -> dot with normal is 0
|
||||
expect(dot(s, normal)).toBeCloseTo(0);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { dot, scale, sub, type Vec } from "./given.ts";
|
||||
|
||||
/**
|
||||
* Remove the component of `v` that points into a wall with the given (unit)
|
||||
* normal, leaving only the motion that runs along the wall.
|
||||
*
|
||||
* vSlide = v - normal * dot(v, normal)
|
||||
*/
|
||||
export function slide(v: Vec, normal: Vec): Vec {
|
||||
return sub(v, scale(normal, dot(v, normal)));
|
||||
}
|
||||
Reference in New Issue
Block a user