diff --git a/crates/path/src/ops.rs b/crates/path/src/ops.rs index 3ad94944d..ba5349312 100644 --- a/crates/path/src/ops.rs +++ b/crates/path/src/ops.rs @@ -564,7 +564,8 @@ fn noise(seed: u64, idx: u64, t: f64) -> f64 { let i = t.floor(); let f = t - i; let i = i as i64; - let p = |k: i64| hash(seed, idx, i + k); + // `i` saturates at the i64 ends for an absurd `t`, so the neighbours wrap rather than overflow. + let p = |k: i64| hash(seed, idx, i.wrapping_add(k)); // Catmull-Rom through four lattice values keeps the motion C1-continuous. let (p0, p1, p2, p3) = (p(-1), p(0), p(1), p(2)); let v = 0.5 * (2.0 * p1 + (-p0 + p2) * f + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * f * f + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * f * f * f); diff --git a/crates/path/src/ops/tests.rs b/crates/path/src/ops/tests.rs index 89c009bc4..1ebdcea5b 100644 --- a/crates/path/src/ops/tests.rs +++ b/crates/path/src/ops/tests.rs @@ -292,6 +292,18 @@ fn wiggle_is_deterministic_and_time_varying() { assert_eq!(vertex_count(&a[0]), 4 * 11); } +#[test] +fn wiggle_survives_an_absurd_speed_or_time() { + // `t` far past i64 saturates the lattice index at i64::MAX, and the neighbours' `+ 1`, `+ 2` used to overflow. + let s = [sq(0.0, 0.0, 100.0)]; + for v in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN, f64::MAX, f64::MIN, 1e300, 9.3e18] { + let by_speed = wiggle(&s, &WiggleParams { speed: v, ..Default::default() }, 1.0); + let by_time = wiggle(&s, &WiggleParams::default(), v); + let by_phase = wiggle(&s, &WiggleParams { phase_deg: v, ..Default::default() }, 0.0); + assert_eq!((by_speed.len(), by_time.len(), by_phase.len()), (1, 1, 1), "wiggle with {v}"); + } +} + #[test] fn wiggle_displacement_is_bounded() { let mut l = BezPath::new();