Skip to content

Commit 9653cf6

Browse files
committed
docs: update README.md with coordinate system explanation and best practices for P5.js animations
1 parent 81e6eb9 commit 9653cf6

File tree

1 file changed

+57
-4
lines changed

1 file changed

+57
-4
lines changed

src/animations/README.md

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,64 @@ p5Instance = new p5(sketch, containerElement);
540540
```
541541

542542
**Why:**
543+
543544
- Multiple P5.js sketches can coexist on one page
544545
- No global `setup()`/`draw()` conflicts
545546
- Easier to test and reason about
546547

547-
### 2. WEBGL Renderer for 3D Effects
548+
### 2. Coordinate System: Y-Axis Inversion (2D Mode)
549+
550+
In P5.js 2D mode, `(0, 0)` is the **top-left** corner of the canvas. The Y axis increases **downward**. This is the opposite of standard mathematical convention where Y increases upward, and it causes subtle bugs when reasoning about "height."
551+
552+
```text
553+
Screen space (P5.js 2D): Mathematical convention:
554+
555+
(0,0) ───────► +X +Y ▲
556+
│ │
557+
│ │
558+
▼ +Y (0,0) ───────► +X
559+
```
560+
561+
**The trap:** A wave crest (visually the highest point on screen) has a **lower Y value** than a trough (visually the lowest point). If your code checks `yPosition > threshold` expecting to find crests, it actually finds troughs.
562+
563+
```typescript
564+
// ❌ BUG: This finds troughs, not crests!
565+
// A crest at y=100 is visually ABOVE a trough at y=300,
566+
// but 300 > 100, so the trough passes the threshold check.
567+
function isCrest(yPosition: number, threshold: number): boolean {
568+
return yPosition > threshold;
569+
}
570+
571+
// ✅ OPTION A: Invert the comparison for screen-space values
572+
function isCrest(yPosition: number, threshold: number): boolean {
573+
return yPosition < threshold; // Lower Y = higher on screen = crest
574+
}
575+
576+
// ✅ OPTION B (preferred): Work in logical space, convert at render time
577+
// In logical space, positive = up (like math convention)
578+
function calculateWaveHeight(x: number, time: number, config: Config): number {
579+
// Returns positive values for crests, negative for troughs
580+
const noiseVal = noiseFunc(x * config.noiseScale, time * config.speed);
581+
return (noiseVal - 0.5) * 2 * config.amplitude; // Range: -amplitude to +amplitude
582+
}
583+
584+
// Only flip to screen space when drawing
585+
function logicalToScreenY(logicalY: number, baseline: number): number {
586+
return baseline - logicalY; // Subtract because screen Y is inverted
587+
}
588+
```
589+
590+
**Option B is preferred** because it keeps all calculation logic in intuitive mathematical space (positive = up, crests are positive, troughs are negative). The inversion happens once at the rendering boundary, not scattered through calculation functions. This also makes unit tests more intuitive: you can assert that crests produce positive values without thinking about screen coordinates.
591+
592+
**Checklist for new animations:**
593+
594+
- [ ] Calculation functions work in logical space (positive = up)
595+
- [ ] Screen-space conversion happens at render time, not in utility functions
596+
- [ ] Threshold comparisons make sense in the coordinate space being used
597+
- [ ] Test names and assertions use "crest" and "trough" to make intent clear
598+
- [ ] JSDoc on functions states which coordinate space the return value is in
599+
600+
### 3. WEBGL Renderer for 3D Effects
548601

549602
Use WEBGL mode for depth-stacked layers:
550603

@@ -566,7 +619,7 @@ for (let layer = 0; layer < layers; layer++) {
566619
- Requires manual camera positioning
567620
- `p.translate()` affects all subsequent draws (use `p.push()`/`p.pop()`)
568621

569-
### 3. Performance Optimization with Off-Screen Buffers
622+
### 4. Performance Optimization with Off-Screen Buffers
570623

571624
Cache static/repeated content in `p5.Graphics` buffers:
572625

@@ -596,7 +649,7 @@ p.draw = () => {
596649
- Recreate only on canvas resize (handle in `onResize`)
597650
- Same pattern for any static background or texture
598651

599-
### 4. Perlin Noise for Organic Motion
652+
### 5. Perlin Noise for Organic Motion
600653

601654
Use 3D Perlin noise with time as third dimension:
602655

@@ -627,7 +680,7 @@ export function calculateWavePoint(
627680
- Reduce `z` influence for smoother layer transitions
628681
- Center noise output around 0 for symmetric waves
629682

630-
### 5. Variable Naming to Avoid Shadowing
683+
### 6. Variable Naming to Avoid Shadowing
631684

632685
When using arrow functions inside loops, avoid shadowing outer variables:
633686

0 commit comments

Comments
 (0)