1use super::{Animation, Easing, Interpolate};
4use crate::{Duration, Path2D, Point, Time, Vector2};
5use std::sync::Arc;
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9pub enum Waveform {
10 #[default]
12 Sine,
13 Triangle,
15 Square,
17 Sawtooth,
19}
20
21#[derive(Clone, Debug)]
23pub struct Oscillator {
24 waveform: Waveform,
25 frequency: f64,
26 amplitude: f64,
27 phase: f64,
28 offset: f64,
29}
30
31impl Oscillator {
32 pub fn new(waveform: Waveform, frequency_hertz: f64) -> Self {
34 assert!(
35 frequency_hertz.is_finite() && frequency_hertz >= 0.0,
36 "Frequency must be finite and nonnegative"
37 );
38 Self {
39 waveform,
40 frequency: frequency_hertz,
41 amplitude: 1.0,
42 phase: 0.0,
43 offset: 0.0,
44 }
45 }
46
47 pub fn amplitude(mut self, amplitude: f64) -> Self {
49 assert!(amplitude.is_finite());
50 self.amplitude = amplitude;
51 self
52 }
53
54 pub fn phase(mut self, turns: f64) -> Self {
56 assert!(turns.is_finite());
57 self.phase = turns;
58 self
59 }
60
61 pub fn offset(mut self, offset: f64) -> Self {
63 assert!(offset.is_finite());
64 self.offset = offset;
65 self
66 }
67}
68
69impl Animation<f64> for Oscillator {
70 fn sample(&self, time: Time) -> f64 {
71 let cycles = time.as_seconds() * self.frequency + self.phase;
72 assert!(cycles.is_finite(), "Oscillator phase overflow");
73 let turns = cycles - libm::floor(cycles);
74 let value = match self.waveform {
75 Waveform::Sine => libm::sin(turns * std::f64::consts::TAU),
76 Waveform::Triangle if turns < 0.25 => turns * 4.0,
77 Waveform::Triangle if turns < 0.75 => 2.0 - turns * 4.0,
78 Waveform::Triangle => turns * 4.0 - 4.0,
79 Waveform::Square => {
80 if turns < 0.5 {
81 1.0
82 } else {
83 -1.0
84 }
85 }
86 Waveform::Sawtooth => turns * 2.0 - 1.0,
87 };
88 let output = value * self.amplitude + self.offset;
89 assert!(output.is_finite(), "Oscillator output overflow");
90 output
91 }
92}
93
94pub fn value_noise(position: f64, seed: u32) -> (f64, f64) {
96 assert!(position.is_finite(), "Noise position must be finite");
97 let (value, gradient) = crate::noise::value2_d([position, 0.0], seed);
98 (value, gradient[0])
99}
100
101#[derive(Clone, Debug)]
103pub struct Noise {
104 seed: u64,
105 frequency: f64,
106 octaves: u32,
107 lacunarity: f64,
108 gain: f64,
109}
110
111impl Noise {
112 pub fn new(seed: u64) -> Self {
114 Self {
115 seed,
116 frequency: 1.0,
117 octaves: 1,
118 lacunarity: 2.0,
119 gain: 0.5,
120 }
121 }
122
123 pub fn frequency(mut self, hertz: f64) -> Self {
125 assert!(hertz.is_finite() && hertz >= 0.0);
126 self.frequency = hertz;
127 self
128 }
129
130 pub fn octaves(mut self, octaves: u32) -> Self {
132 assert!((1..=32).contains(&octaves));
133 self.octaves = octaves;
134 self
135 }
136
137 pub fn lacunarity(mut self, lacunarity: f64) -> Self {
139 assert!(lacunarity.is_finite() && lacunarity > 0.0);
140 self.lacunarity = lacunarity;
141 self
142 }
143
144 pub fn gain(mut self, gain: f64) -> Self {
146 assert!(gain.is_finite() && (0.0..=1.0).contains(&gain));
147 self.gain = gain;
148 self
149 }
150
151 pub fn vector(self) -> impl Animation<Vector2> {
153 let mut vertical = self.clone();
154 vertical.seed ^= 0x9e3779b97f4a7c15;
155 move |time| Vector2::new(self.sample(time), vertical.sample(time))
156 }
157}
158
159impl Animation<f64> for Noise {
160 fn sample(&self, time: Time) -> f64 {
161 let mut position = time.as_seconds() * self.frequency;
162 let mut amplitude = 1.0;
163 let mut value = 0.0;
164 let mut weight = 0.0;
165 for octave in 0..self.octaves {
166 let seed = (self.seed ^ (self.seed >> 32)) as u32;
167 value += value_noise(position, seed.wrapping_add(octave.wrapping_mul(0x9e3779b9))).0
168 * amplitude;
169 weight += amplitude;
170 position *= self.lacunarity;
171 amplitude *= self.gain;
172 }
173 value / weight
174 }
175}
176
177#[derive(Clone, Copy, Debug, PartialEq)]
179pub struct PathSample {
180 pub position: Point,
182 pub tangent: Vector2,
184 pub angle: f64,
186}
187
188#[derive(Clone, Debug)]
190pub struct FollowPath {
191 path: Arc<Path2D>,
192 duration: Duration,
193 easing: Easing,
194}
195
196impl FollowPath {
197 pub fn new(path: impl Into<Arc<Path2D>>, duration: Duration) -> Self {
199 assert!(duration >= Time::ZERO);
200 Self {
201 path: path.into(),
202 duration,
203 easing: Easing::Linear,
204 }
205 }
206
207 pub fn easing(mut self, easing: Easing) -> Self {
209 easing.try_validate().expect("Valid path easing");
210 self.easing = easing;
211 self
212 }
213}
214
215impl Animation<PathSample> for FollowPath {
216 fn sample(&self, time: Time) -> PathSample {
217 let progress = if self.duration == Time::ZERO {
218 if time < Time::ZERO { 0.0 } else { 1.0 }
219 } else {
220 (time / self.duration).clamp(0.0, 1.0)
221 };
222 let (position, tangent) = self
223 .path
224 .point_at(self.easing.apply(progress).clamp(0.0, 1.0));
225 PathSample {
226 position,
227 tangent,
228 angle: libm::atan2(tangent.y, tangent.x),
229 }
230 }
231
232 fn duration(&self) -> Option<Duration> {
233 Some(self.duration)
234 }
235}
236
237#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
239pub enum Extrapolate {
240 #[default]
242 Clamp,
243 Extend,
245 Wrap,
247}
248
249fn progress(value: f64, from: [f64; 2], extrapolate: Extrapolate) -> f64 {
250 assert!(
251 value.is_finite() && from.iter().all(|value| value.is_finite()) && from[0] != from[1],
252 "Mapping inputs must be finite with distinct endpoints"
253 );
254 let scale = value
255 .abs()
256 .max(from[0].abs())
257 .max(from[1].abs())
258 .max(f64::MIN_POSITIVE);
259 let span = from[1] - from[0];
260 let distance = value - from[0];
261 let amount = if span.is_finite() && distance.is_finite() {
262 distance / span
263 } else {
264 (value / scale - from[0] / scale) / (from[1] / scale - from[0] / scale)
265 };
266 match extrapolate {
267 Extrapolate::Clamp => amount.clamp(0.0, 1.0),
268 Extrapolate::Extend => amount,
269 Extrapolate::Wrap => amount - libm::floor(amount),
270 }
271}
272
273pub fn remap(value: f64, from: [f64; 2], to: [f64; 2], extrapolate: Extrapolate) -> f64 {
275 assert!(to.iter().all(|value| value.is_finite()));
276 let output = to[0].interpolate(&to[1], progress(value, from, extrapolate));
277 assert!(output.is_finite(), "Mapping output overflow");
278 output
279}
280
281pub fn piecewise<Value: Interpolate>(
283 value: f64,
284 inputs: &[f64],
285 outputs: &[Value],
286 easing: Easing,
287 extrapolate: Extrapolate,
288) -> Value {
289 assert!(inputs.len() >= 2 && inputs.len() == outputs.len());
290 assert!(
291 inputs.iter().all(|value| value.is_finite())
292 && inputs.windows(2).all(|pair| pair[0] < pair[1])
293 );
294 let mapped = remap(
295 value,
296 [inputs[0], inputs[inputs.len() - 1]],
297 [inputs[0], inputs[inputs.len() - 1]],
298 extrapolate,
299 );
300 let index = inputs
301 .partition_point(|input| *input <= mapped)
302 .saturating_sub(1)
303 .min(inputs.len() - 2);
304 let amount = progress(
305 mapped,
306 [inputs[index], inputs[index + 1]],
307 Extrapolate::Extend,
308 );
309 let amount = if !(0.0..=1.0).contains(&amount) && extrapolate == Extrapolate::Extend {
310 amount
311 } else {
312 easing.apply(amount)
313 };
314 outputs[index].interpolate(&outputs[index + 1], amount)
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::{Shape, TimeLiteral};
321
322 #[test]
323 fn oscillators_are_periodic_seek_independent_and_infinite() {
324 for waveform in [
325 Waveform::Sine,
326 Waveform::Triangle,
327 Waveform::Square,
328 Waveform::Sawtooth,
329 ] {
330 let value = Oscillator::new(waveform, 2.0).amplitude(3.0).offset(7.0);
331 assert_eq!(value.duration(), None);
332 assert_eq!(
333 value.sample(125.milliseconds()),
334 value.sample(625.milliseconds())
335 );
336 assert_eq!(
337 value.sample((-375).milliseconds()),
338 value.sample(125.milliseconds())
339 );
340 }
341 assert_eq!(
342 Oscillator::new(Waveform::Triangle, 1.0).sample(250.milliseconds()),
343 1.0
344 );
345 }
346
347 #[test]
348 fn noise_and_vector_replay_share_the_particle_noise_primitive() {
349 let noise = Noise::new(17);
350 for time in [
351 500.milliseconds(),
352 (-200).milliseconds(),
353 3.seconds(),
354 500.milliseconds(),
355 ] {
356 assert_eq!(noise.sample(time), value_noise(time.as_seconds(), 17).0);
357 assert!((-1.0..=1.0).contains(&noise.clone().octaves(5).sample(time)));
358 }
359 let vector = noise.vector();
360 assert_eq!(
361 vector.sample(500.milliseconds()),
362 vector.sample(500.milliseconds())
363 );
364 assert_ne!(
365 vector.sample(500.milliseconds()).x,
366 vector.sample(500.milliseconds()).y
367 );
368 }
369
370 #[test]
371 fn path_motion_uses_arc_length_and_has_exact_endpoints() {
372 let path = crate::Line::new(Point::ORIGIN, Point::new(10.0, 0.0)).path();
373 let motion = FollowPath::new(path, 2.seconds());
374 assert_eq!(motion.duration(), Some(2.seconds()));
375 assert_eq!(motion.sample(1.seconds()).position, Point::new(5.0, 0.0));
376 assert_eq!(motion.sample((-1).seconds()).position, Point::ORIGIN);
377 assert_eq!(motion.sample(3.seconds()).position, Point::new(10.0, 0.0));
378 assert_eq!(motion.sample(1.seconds()).angle, 0.0);
379 }
380
381 #[test]
382 fn mappings_cover_clamp_wrap_extend_reversed_ranges_and_invalid_knots() {
383 assert_eq!(
384 remap(3.0, [0.0, 2.0], [0.0, 10.0], Extrapolate::Extend),
385 15.0
386 );
387 assert_eq!(remap(-1.0, [0.0, 2.0], [0.0, 10.0], Extrapolate::Wrap), 5.0);
388 assert_eq!(remap(3.0, [2.0, 0.0], [0.0, 10.0], Extrapolate::Clamp), 0.0);
389 assert_eq!(
390 piecewise(
391 1.5,
392 &[0.0, 1.0, 2.0],
393 &[0.0, 10.0, 30.0],
394 Easing::Linear,
395 Extrapolate::Clamp
396 ),
397 20.0
398 );
399 assert_eq!(
400 piecewise(
401 3.0,
402 &[0.0, 1.0, 2.0],
403 &[0.0, 10.0, 30.0],
404 Easing::Linear,
405 Extrapolate::Extend
406 ),
407 50.0
408 );
409 assert!(
410 std::panic::catch_unwind(|| piecewise(
411 0.0,
412 &[0.0, 0.0],
413 &[0.0, 1.0],
414 Easing::Linear,
415 Extrapolate::Clamp
416 ))
417 .is_err()
418 );
419 }
420}