1use super::{Easing, FiniteAnimation, Interpolate, Signal};
4use crate::{Duration, Path2D, Point, Time, ValidationError, Vector2};
5use std::sync::Arc;
6
7const MAX_MAGNITUDE: f64 = 1.0e290;
9
10const MAX_SECONDS: f64 = 2.0e10;
12
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15#[cfg_attr(
16 feature = "serde",
17 derive(serde::Serialize, serde::Deserialize),
18 serde(rename_all = "snake_case")
19)]
20#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21pub enum Waveform {
22 #[default]
24 Sine,
25 Triangle,
27 Square,
29 Sawtooth,
31}
32
33#[derive(Clone, Debug)]
35pub struct Oscillator {
36 waveform: Waveform,
37 frequency: f64,
38 amplitude: f64,
39 phase: f64,
40 offset: f64,
41}
42
43impl Oscillator {
44 pub fn try_new(waveform: Waveform, frequency_hertz: f64) -> Result<Self, ValidationError> {
48 Self::builder(waveform, frequency_hertz).build()
49 }
50
51 pub fn builder(waveform: Waveform, frequency_hertz: f64) -> OscillatorBuilder {
53 OscillatorBuilder {
54 waveform,
55 frequency: frequency_hertz,
56 amplitude: 1.0,
57 phase: 0.0,
58 offset: 0.0,
59 }
60 }
61}
62
63#[derive(Clone, Debug)]
65pub struct OscillatorBuilder {
66 waveform: Waveform,
67 frequency: f64,
68 amplitude: f64,
69 phase: f64,
70 offset: f64,
71}
72
73impl OscillatorBuilder {
74 pub fn amplitude(mut self, amplitude: f64) -> Self {
76 self.amplitude = amplitude;
77 self
78 }
79
80 pub fn phase(mut self, turns: f64) -> Self {
82 self.phase = turns;
83 self
84 }
85
86 pub fn offset(mut self, offset: f64) -> Self {
88 self.offset = offset;
89 self
90 }
91
92 pub fn build(self) -> Result<Oscillator, ValidationError> {
96 let error = |field, message| ValidationError::invalid("oscillator", field, message);
97 if !(self.frequency.is_finite() && (0.0..=MAX_MAGNITUDE).contains(&self.frequency)) {
98 return Err(error(
99 "frequency",
100 "frequency must be finite, nonnegative and bounded",
101 ));
102 }
103 if !(self.phase.is_finite() && self.phase.abs() <= MAX_MAGNITUDE) {
104 return Err(error("phase", "phase must be finite and bounded"));
105 }
106 if !(self.amplitude.is_finite()
107 && self.offset.is_finite()
108 && (self.amplitude.abs() + self.offset.abs()).is_finite())
109 {
110 return Err(error(
111 "amplitude",
112 "amplitude and offset must be finite and keep the output finite",
113 ));
114 }
115 Ok(Oscillator {
116 waveform: self.waveform,
117 frequency: self.frequency,
118 amplitude: self.amplitude,
119 phase: self.phase,
120 offset: self.offset,
121 })
122 }
123}
124
125impl Signal<f64> for Oscillator {
126 fn sample(&self, time: Time) -> f64 {
127 let cycles = time.as_seconds() * self.frequency + self.phase;
128 debug_assert!(
129 cycles.is_finite(),
130 "bounded parameters keep the phase finite"
131 );
132 let turns = cycles - libm::floor(cycles);
133 let value = match self.waveform {
134 Waveform::Sine => libm::sin(turns * std::f64::consts::TAU),
135 Waveform::Triangle if turns < 0.25 => turns * 4.0,
136 Waveform::Triangle if turns < 0.75 => 2.0 - turns * 4.0,
137 Waveform::Triangle => turns * 4.0 - 4.0,
138 Waveform::Square => {
139 if turns < 0.5 {
140 1.0
141 } else {
142 -1.0
143 }
144 }
145 Waveform::Sawtooth => turns * 2.0 - 1.0,
146 };
147 let output = value * self.amplitude + self.offset;
148 debug_assert!(
149 output.is_finite(),
150 "validated range keeps the output finite"
151 );
152 output
153 }
154}
155
156pub fn value_noise(position: f64, seed: u32) -> (f64, f64) {
161 try_value_noise(position, seed).unwrap_or_else(|error| panic!("{}", error.message))
162}
163
164pub fn try_value_noise(position: f64, seed: u32) -> Result<(f64, f64), ValidationError> {
166 if !position.is_finite() {
167 return Err(ValidationError::invalid(
168 "noise",
169 "position",
170 "noise position must be finite",
171 ));
172 }
173 let (value, gradient) = crate::noise::value2_d([position, 0.0], seed);
174 Ok((value, gradient[0]))
175}
176
177#[derive(Clone, Debug)]
179pub struct Noise {
180 seed: u64,
181 frequency: f64,
182 octaves: u32,
183 lacunarity: f64,
184 gain: f64,
185}
186
187impl Noise {
188 pub fn new(seed: u64) -> Self {
190 Self::builder(seed)
191 .build()
192 .unwrap_or_else(|error| unreachable!("default noise parameters are valid: {error}"))
193 }
194
195 pub fn builder(seed: u64) -> NoiseBuilder {
197 NoiseBuilder {
198 seed,
199 frequency: 1.0,
200 octaves: 1,
201 lacunarity: 2.0,
202 gain: 0.5,
203 }
204 }
205
206 pub fn vector(self) -> impl Signal<Vector2> {
208 let mut vertical = self.clone();
209 vertical.seed ^= 0x9e3779b97f4a7c15;
210 move |time| Vector2::new(self.sample(time), vertical.sample(time))
211 }
212}
213
214#[derive(Clone, Debug)]
216pub struct NoiseBuilder {
217 seed: u64,
218 frequency: f64,
219 octaves: u32,
220 lacunarity: f64,
221 gain: f64,
222}
223
224impl NoiseBuilder {
225 pub fn frequency(mut self, hertz: f64) -> Self {
227 self.frequency = hertz;
228 self
229 }
230
231 pub fn octaves(mut self, octaves: u32) -> Self {
233 self.octaves = octaves;
234 self
235 }
236
237 pub fn lacunarity(mut self, lacunarity: f64) -> Self {
239 self.lacunarity = lacunarity;
240 self
241 }
242
243 pub fn gain(mut self, gain: f64) -> Self {
245 self.gain = gain;
246 self
247 }
248
249 pub fn build(self) -> Result<Noise, ValidationError> {
253 let error = |field, message| ValidationError::invalid("noise", field, message);
254 if !(self.frequency.is_finite() && self.frequency >= 0.0) {
255 return Err(error(
256 "frequency",
257 "frequency must be finite and nonnegative",
258 ));
259 }
260 if !(1..=32).contains(&self.octaves) {
261 return Err(error("octaves", "octaves must be from 1 through 32"));
262 }
263 if !(self.lacunarity.is_finite() && self.lacunarity > 0.0) {
264 return Err(error(
265 "lacunarity",
266 "lacunarity must be finite and positive",
267 ));
268 }
269 if !(self.gain.is_finite() && (0.0..=1.0).contains(&self.gain)) {
270 return Err(error("gain", "gain must be finite and in 0..=1"));
271 }
272 let reach = MAX_SECONDS * self.frequency * self.lacunarity.powi(self.octaves as i32 - 1);
273 if !reach.is_finite() {
274 return Err(error(
275 "frequency",
276 "frequency and lacunarity must keep octave positions finite",
277 ));
278 }
279 Ok(Noise {
280 seed: self.seed,
281 frequency: self.frequency,
282 octaves: self.octaves,
283 lacunarity: self.lacunarity,
284 gain: self.gain,
285 })
286 }
287}
288
289impl Signal<f64> for Noise {
290 fn sample(&self, time: Time) -> f64 {
291 let mut position = time.as_seconds() * self.frequency;
292 let mut amplitude = 1.0;
293 let mut value = 0.0;
294 let mut weight = 0.0;
295 for octave in 0..self.octaves {
296 let seed = (self.seed ^ (self.seed >> 32)) as u32;
297 value += value_noise(position, seed.wrapping_add(octave.wrapping_mul(0x9e3779b9))).0
298 * amplitude;
299 weight += amplitude;
300 position *= self.lacunarity;
301 amplitude *= self.gain;
302 }
303 value / weight
304 }
305}
306
307#[derive(Clone, Copy, Debug, PartialEq)]
309pub struct PathSample {
310 pub position: Point,
312 pub tangent: Vector2,
314 pub angle: f64,
316}
317
318#[derive(Clone, Debug)]
320pub struct FollowPath {
321 path: Arc<Path2D>,
322 duration: Duration,
323 easing: Easing,
324}
325
326impl FollowPath {
327 pub fn new(path: impl Into<Arc<Path2D>>, duration: Duration) -> Self {
329 Self {
330 path: path.into(),
331 duration,
332 easing: Easing::Linear,
333 }
334 }
335
336 pub fn easing(mut self, easing: Easing) -> Self {
338 self.easing = easing;
339 self
340 }
341}
342
343impl Signal<PathSample> for FollowPath {
344 fn sample(&self, time: Time) -> PathSample {
345 let progress = if self.duration == Duration::ZERO {
346 if time < Time::ZERO { 0.0 } else { 1.0 }
347 } else {
348 (time.ticks() as f64 / self.duration.ticks() as f64).clamp(0.0, 1.0)
349 };
350 let (position, tangent) = self
351 .path
352 .point_at(self.easing.apply(progress).clamp(0.0, 1.0));
353 PathSample {
354 position,
355 tangent,
356 angle: libm::atan2(tangent.y, tangent.x),
357 }
358 }
359}
360
361impl FiniteAnimation<PathSample> for FollowPath {
362 fn duration(&self) -> Duration {
363 self.duration
364 }
365}
366
367#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
369pub enum Extrapolate {
370 #[default]
372 Clamp,
373 Extend,
375 Wrap,
377}
378
379fn mapping_error(field: &'static str, message: &'static str) -> ValidationError {
380 ValidationError::invalid("mapping", field, message)
381}
382
383fn progress(value: f64, from: [f64; 2], extrapolate: Extrapolate) -> Result<f64, ValidationError> {
384 if !(value.is_finite() && from.iter().all(|value| value.is_finite()) && from[0] != from[1]) {
385 return Err(mapping_error(
386 "from",
387 "mapping inputs must be finite with distinct endpoints",
388 ));
389 }
390 let scale = value
391 .abs()
392 .max(from[0].abs())
393 .max(from[1].abs())
394 .max(f64::MIN_POSITIVE);
395 let span = from[1] - from[0];
396 let distance = value - from[0];
397 let amount = if span.is_finite() && distance.is_finite() {
398 distance / span
399 } else {
400 (value / scale - from[0] / scale) / (from[1] / scale - from[0] / scale)
401 };
402 Ok(match extrapolate {
403 Extrapolate::Clamp => amount.clamp(0.0, 1.0),
404 Extrapolate::Extend => amount,
405 Extrapolate::Wrap => amount - libm::floor(amount),
406 })
407}
408
409pub fn remap(value: f64, from: [f64; 2], to: [f64; 2], extrapolate: Extrapolate) -> f64 {
414 try_remap(value, from, to, extrapolate).unwrap_or_else(|error| panic!("{}", error.message))
415}
416
417pub fn try_remap(
419 value: f64,
420 from: [f64; 2],
421 to: [f64; 2],
422 extrapolate: Extrapolate,
423) -> Result<f64, ValidationError> {
424 if !to.iter().all(|value| value.is_finite()) {
425 return Err(mapping_error("to", "mapping outputs must be finite"));
426 }
427 let output = to[0].interpolate(&to[1], progress(value, from, extrapolate)?);
428 if !output.is_finite() {
429 return Err(mapping_error("output", "mapping output overflows"));
430 }
431 Ok(output)
432}
433
434pub fn piecewise<Value: Interpolate>(
439 value: f64,
440 inputs: &[f64],
441 outputs: &[Value],
442 easing: Easing,
443 extrapolate: Extrapolate,
444) -> Value {
445 try_piecewise(value, inputs, outputs, easing, extrapolate)
446 .unwrap_or_else(|error| panic!("{}", error.message))
447}
448
449pub fn try_piecewise<Value: Interpolate>(
451 value: f64,
452 inputs: &[f64],
453 outputs: &[Value],
454 easing: Easing,
455 extrapolate: Extrapolate,
456) -> Result<Value, ValidationError> {
457 if inputs.len() < 2 || inputs.len() != outputs.len() {
458 return Err(mapping_error(
459 "inputs",
460 "piecewise needs at least two knots and one output per knot",
461 ));
462 }
463 if !(inputs.iter().all(|value| value.is_finite())
464 && inputs.windows(2).all(|pair| pair[0] < pair[1]))
465 {
466 return Err(mapping_error(
467 "inputs",
468 "piecewise knots must be finite and strictly increasing",
469 ));
470 }
471 let mapped = try_remap(
472 value,
473 [inputs[0], inputs[inputs.len() - 1]],
474 [inputs[0], inputs[inputs.len() - 1]],
475 extrapolate,
476 )?;
477 let index = inputs
478 .partition_point(|input| *input <= mapped)
479 .saturating_sub(1)
480 .min(inputs.len() - 2);
481 let amount = progress(
482 mapped,
483 [inputs[index], inputs[index + 1]],
484 Extrapolate::Extend,
485 )?;
486 let amount = if !(0.0..=1.0).contains(&amount) && extrapolate == Extrapolate::Extend {
487 amount
488 } else {
489 easing.try_apply(amount)?
490 };
491 Ok(outputs[index].interpolate(&outputs[index + 1], amount))
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497 use crate::{Shape, TimeLiteral};
498
499 #[test]
500 fn oscillators_are_periodic_seek_independent_and_infinite() {
501 for waveform in [
502 Waveform::Sine,
503 Waveform::Triangle,
504 Waveform::Square,
505 Waveform::Sawtooth,
506 ] {
507 let value = Oscillator::builder(waveform, 2.0)
508 .amplitude(3.0)
509 .offset(7.0)
510 .build()
511 .unwrap();
512 assert_eq!(
513 value.sample(125.milliseconds().after_origin()),
514 value.sample(625.milliseconds().after_origin())
515 );
516 assert_eq!(
517 value.sample(Time::ZERO - 375.milliseconds()),
518 value.sample(125.milliseconds().after_origin())
519 );
520 }
521 assert_eq!(
522 Oscillator::builder(Waveform::Triangle, 1.0)
523 .build()
524 .unwrap()
525 .sample(250.milliseconds().after_origin()),
526 1.0
527 );
528 }
529
530 #[test]
531 fn noise_and_vector_replay_share_the_particle_noise_primitive() {
532 let noise = Noise::new(17);
533 for time in [
534 500.milliseconds().after_origin(),
535 Time::ZERO - 200.milliseconds(),
536 3.seconds().after_origin(),
537 500.milliseconds().after_origin(),
538 ] {
539 assert_eq!(noise.sample(time), value_noise(time.as_seconds(), 17).0);
540 assert!(
541 (-1.0..=1.0).contains(&Noise::builder(17).octaves(5).build().unwrap().sample(time))
542 );
543 }
544 let vector = noise.vector();
545 assert_eq!(
546 vector.sample(500.milliseconds().after_origin()),
547 vector.sample(500.milliseconds().after_origin())
548 );
549 assert_ne!(
550 vector.sample(500.milliseconds().after_origin()).x,
551 vector.sample(500.milliseconds().after_origin()).y
552 );
553 }
554
555 #[test]
556 fn path_motion_uses_arc_length_and_has_exact_endpoints() {
557 let path = crate::Line::new(Point::ORIGIN, Point::new(10.0, 0.0))
558 .unwrap()
559 .path()
560 .unwrap();
561 let motion = FollowPath::new(path, 2.seconds());
562 assert_eq!(motion.duration(), 2.seconds());
563 assert_eq!(
564 motion.sample(1.seconds().after_origin()).position,
565 Point::new(5.0, 0.0)
566 );
567 assert_eq!(
568 motion.sample(Time::ZERO - 1.seconds()).position,
569 Point::ORIGIN
570 );
571 assert_eq!(
572 motion.sample(3.seconds().after_origin()).position,
573 Point::new(10.0, 0.0)
574 );
575 assert_eq!(motion.sample(1.seconds().after_origin()).angle, 0.0);
576 }
577
578 #[test]
579 fn mappings_cover_clamp_wrap_extend_reversed_ranges_and_invalid_knots() {
580 assert_eq!(
581 remap(3.0, [0.0, 2.0], [0.0, 10.0], Extrapolate::Extend),
582 15.0
583 );
584 assert_eq!(remap(-1.0, [0.0, 2.0], [0.0, 10.0], Extrapolate::Wrap), 5.0);
585 assert_eq!(remap(3.0, [2.0, 0.0], [0.0, 10.0], Extrapolate::Clamp), 0.0);
586 assert_eq!(
587 piecewise(
588 1.5,
589 &[0.0, 1.0, 2.0],
590 &[0.0, 10.0, 30.0],
591 Easing::Linear,
592 Extrapolate::Clamp
593 ),
594 20.0
595 );
596 assert_eq!(
597 piecewise(
598 3.0,
599 &[0.0, 1.0, 2.0],
600 &[0.0, 10.0, 30.0],
601 Easing::Linear,
602 Extrapolate::Extend
603 ),
604 50.0
605 );
606 assert!(
607 try_piecewise(
608 0.0,
609 &[0.0, 0.0],
610 &[0.0, 1.0],
611 Easing::Linear,
612 Extrapolate::Clamp
613 )
614 .is_err()
615 );
616 assert!(try_remap(0.0, [1.0, 1.0], [0.0, 1.0], Extrapolate::Clamp).is_err());
617 assert!(
618 try_remap(
619 f64::MAX,
620 [0.0, 1.0],
621 [-f64::MAX, f64::MAX],
622 Extrapolate::Extend
623 )
624 .is_err()
625 );
626 }
627
628 #[test]
629 fn builders_report_invalid_and_overflowing_parameters() {
630 let oscillator = |frequency| Oscillator::builder(Waveform::Sine, frequency);
631 assert!(oscillator(f64::NAN).build().is_err());
632 assert!(oscillator(-1.0).build().is_err());
633 assert!(oscillator(f64::MAX).build().is_err());
634 assert!(oscillator(1.0).phase(f64::INFINITY).build().is_err());
635 assert!(
636 oscillator(1.0)
637 .amplitude(f64::MAX)
638 .offset(f64::MAX)
639 .build()
640 .is_err()
641 );
642 let extreme = oscillator(1.0e290).build().unwrap();
643 assert!(extreme.sample(Time::from_ticks(i64::MAX)).is_finite());
644 let noise = Noise::builder(1);
645 assert!(noise.clone().frequency(-1.0).build().is_err());
646 assert!(noise.clone().octaves(0).build().is_err());
647 assert!(noise.clone().octaves(33).build().is_err());
648 assert!(noise.clone().lacunarity(0.0).build().is_err());
649 assert!(noise.clone().gain(1.5).build().is_err());
650 assert!(noise.clone().frequency(1.0e300).build().is_err());
651 assert!(noise.octaves(32).lacunarity(1.0e10).build().is_err());
652 assert!(try_value_noise(f64::NAN, 0).is_err());
653 }
654}