Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/rustmotion/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2357,7 +2357,7 @@ cells scattered by a hash of their coordinates. **Checkerboard**: two colours at
"size": 9, // cell edge, px
"spacing": 22, // lattice pitch, px — clamped to at least `size`
"density": 0.75, // 0..1 fraction of cells drawn
"density_ramp": "right", // none | left | right | top | bottom | radial
"density_ramp": "edges", // none | left | right | top | bottom | radial | edges
"radius": 1, // cell corner radius; 0 for hard pixels
"seed": 7, // stable scatter; same seed → same pattern
"motion": "none" // none | twinkle | sweep
Expand All @@ -2370,7 +2370,7 @@ cells scattered by a hash of their coordinates. **Checkerboard**: two colours at
| `size` | `10.0` | Cell edge in px. |
| `spacing` | `24.0` | Pitch, **clamped to `size`**: a smaller value would draw a solid sheet and lose the lattice. |
| `density` | `0.6` | Fraction of cells drawn. `1.0` fills every cell — required for a real checkerboard. |
| `density_ramp` | `"none"` | Where the field is densest. A ramp is what stops a scatter reading as noise. |
| `density_ramp` | `"none"` | Where the field is densest. A ramp is what stops a scatter reading as noise. `edges` is a vignette — heavy at the border, clear through the middle, so the texture stays off whatever sits in the centre; `radial` is its exact inverse. |
| `radius` | `0.0` | `0` keeps the pixels hard-edged; anti-aliasing turns on above `0`. |
| `seed` | `7` | Occupancy is a hash of `(col, row, seed)`, so the pattern holds still across frames and is identical between two renders. |
| `motion` | `"none"` | `twinkle` fades cells on their own phase; `sweep` runs a band of extra density across the field. Scaled by the background's `speed`. |
Expand Down
8 changes: 8 additions & 0 deletions crates/rustmotion-core/src/schema/background.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ pub enum PixelDensityRamp {
Bottom,
/// Dense at the centre, thinning outwards.
Radial,
/// Dense at the frame's edges, thinning toward the centre — a vignette.
///
/// Measured on the reference piece, in a band clear of its window, the
/// density runs 10.9 · 6.5 · 0.8 · 0.2 · 0.2 · 0.2 · 0.2 · 0.9 · 7.8 · 8.8 %
/// across the tenths of the frame: heavy at both edges, effectively empty
/// through the middle 60 %. That is what keeps the texture off whatever
/// sits in the centre, and `Radial` is its exact inverse.
Edges,
}

/// How a `pixel_grid` animates.
Expand Down
16 changes: 16 additions & 0 deletions crates/rustmotion/src/engine/render/background.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,11 @@ fn ramp_at(ramp: PixelDensityRamp, x: f32, y: f32, width: f32, height: f32) -> f
let (dx, dy) = (fx - 0.5, fy - 0.5);
(1.0 - (dx * dx + dy * dy).sqrt() * 2.0).clamp(0.0, 1.0)
}
// The inverse: a vignette that leaves the middle clear.
PixelDensityRamp::Edges => {
let (dx, dy) = (fx - 0.5, fy - 0.5);
((dx * dx + dy * dy).sqrt() * 2.0).clamp(0.0, 1.0)
}
}
}

Expand Down Expand Up @@ -1173,6 +1178,17 @@ mod pixel_grid_tests {
// Radial is full at the centre and gone at the corners.
assert!(ramp_at(PixelDensityRamp::Radial, 50.0, 50.0, w, h) > 0.99);
assert_eq!(ramp_at(PixelDensityRamp::Radial, 0.0, 0.0, w, h), 0.0);
// `Edges` is the vignette: clear in the middle, heavy at the border.
assert_eq!(ramp_at(PixelDensityRamp::Edges, 50.0, 50.0, w, h), 0.0);
assert!(ramp_at(PixelDensityRamp::Edges, 0.0, 50.0, w, h) > 0.99);
assert!(ramp_at(PixelDensityRamp::Edges, 100.0, 50.0, w, h) > 0.99);
// …and the exact inverse of `Radial`, which is the point of having both.
for (x, y) in [(0.0, 0.0), (25.0, 60.0), (100.0, 50.0)] {
let r = ramp_at(PixelDensityRamp::Radial, x, y, w, h);
let e = ramp_at(PixelDensityRamp::Edges, x, y, w, h);
assert!((r + e - 1.0).abs() < 1e-6, "at ({x},{y}): {r} + {e} != 1");
}

// `None` is uniform: the same everywhere, including the edges.
for x in [0.0, 50.0, 100.0] {
assert_eq!(ramp_at(PixelDensityRamp::None, x, 0.0, w, h), 1.0);
Expand Down
Loading