Skip to content
Merged
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
20 changes: 18 additions & 2 deletions crates/rustmotion/src/engine/render/background.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,12 @@ fn draw_bg_pixel_grid(
let paint = &mut paints[idx];

if cfg.motion == PixelGridMotion::Twinkle {
// Each cell on its own phase, or they blink in unison.
// Full 0 → 1 → 0, not a partial dip: a cell that only fades to
// 10 % still reads as a permanent dot, so the lattice looks
// fixed and merely dimmer. Each cell gets its own phase from
// its own hash, or the whole field blinks in unison.
let phase = cell_hash(col, row, cfg.seed, 1) * std::f32::consts::TAU;
let a = 0.55 + 0.45 * (t * 1.6 + phase).sin();
let a = 0.5 + 0.5 * (t * 1.6 + phase).sin();
paint.set_alpha_f(paint.alpha_f() * a);
}

Expand Down Expand Up @@ -1205,6 +1208,19 @@ mod pixel_grid_tests {
assert_eq!(tile_spacing(&BackgroundPreset::PixelGrid(c)), 40.0);
}

/// `twinkle` has to reach both ends. A cell that only dips to 10 % still
/// reads as a permanent dot: the field looks fixed, just dimmer.
#[test]
fn twinkle_spans_the_whole_opacity_range() {
let phase = 0.0f32;
let alpha = |t: f32| 0.5 + 0.5 * (t * 1.6 + phase).sin();
let samples: Vec<f32> = (0..400).map(|i| alpha(i as f32 * 0.01)).collect();
let lo = samples.iter().cloned().fold(f32::MAX, f32::min);
let hi = samples.iter().cloned().fold(f32::MIN, f32::max);
assert!(lo < 0.01, "must fade all the way out, floor was {lo}");
assert!(hi > 0.99, "must come all the way back, ceiling was {hi}");
}

/// Degenerate configs must be inert, not panic: an empty palette has
/// nothing to draw with, and a zero size no area to draw.
#[test]
Expand Down
Loading