1use glam::Vec3;
7use std::f32::consts::{PI, TAU};
8use super::attractors;
9
10#[derive(Debug, Clone)]
15pub enum MathFunction {
16 Constant(f32),
19 Linear { slope: f32, offset: f32 },
21
22 Sine { amplitude: f32, frequency: f32, phase: f32 },
25 Cosine { amplitude: f32, frequency: f32, phase: f32 },
27 Triangle { amplitude: f32, frequency: f32, phase: f32 },
29 Square { amplitude: f32, frequency: f32, duty: f32 },
31
32 Lorenz { sigma: f32, rho: f32, beta: f32, scale: f32 },
35 Perlin { frequency: f32, octaves: u8, amplitude: f32 },
37 Simplex { frequency: f32, amplitude: f32 },
39
40 Exponential { start: f32, target: f32, rate: f32 },
43 LogisticMap { r: f32, x0: f32 },
45 Collatz { seed: u64, scale: f32 },
47
48 Orbit { center: Vec3, radius: f32, speed: f32, eccentricity: f32 },
51 Spiral { center: Vec3, radius_rate: f32, speed: f32 },
53 GoldenSpiral { center: Vec3, scale: f32, speed: f32 },
55 Lissajous { a: f32, b: f32, delta: f32, scale: f32 },
57
58 StrangeAttractor {
61 attractor_type: crate::math::attractors::AttractorType,
62 scale: f32,
63 strength: f32,
64 },
65
66 MandelbrotEscape { c_real: f32, c_imag: f32, scale: f32 },
69 JuliaSet { c_real: f32, c_imag: f32, scale: f32 },
71
72 SpringDamper { target: f32, stiffness: f32, damping: f32 },
75 CriticallyDamped { target: f32, speed: f32 },
77
78 HeartBeat { bpm: f32, intensity: f32 },
81 Breathing { rate: f32, depth: f32 },
83 Pendulum { length: f32, gravity: f32, damping: f32 },
85 Wave { wavelength: f32, speed: f32, amplitude: f32, decay: f32 },
87
88 Sawtooth { amplitude: f32, frequency: f32, phase: f32 },
91 Ramp { amplitude: f32, duration: f32 },
93
94 Sinc { frequency: f32, amplitude: f32 },
97 Gaussian { amplitude: f32, center: f32, width: f32 },
99 BeatFrequency { freq1: f32, freq2: f32, amplitude: f32 },
101 WavePacket { carrier_freq: f32, envelope_width: f32, amplitude: f32, center: f32 },
103 FourierSeries { fundamental: f32, coefficients: Vec<(f32, f32)> }, Sigmoid { steepness: f32, center: f32 },
109 Tanh { amplitude: f32, steepness: f32 },
111 SoftPlus { amplitude: f32, steepness: f32 },
113 Relu { slope: f32, offset: f32 },
115 PowerLaw { exponent: f32, scale: f32 },
117
118 VanDerPol { mu: f32, amplitude: f32 },
121 Duffing { alpha: f32, beta: f32, delta: f32, gamma: f32, omega: f32 },
123 TentMap { r: f32, x0: f32 },
125 HenonMap { a: f32, b: f32, x0: f32, y0: f32 },
127 Roessler { a: f32, b: f32, c: f32, scale: f32 },
129
130 DoublePendulum { l1: f32, l2: f32, m1: f32, m2: f32, theta1_0: f32, theta2_0: f32 },
133 Projectile { v0: f32, angle_deg: f32, gravity: f32 },
135 SimpleHarmonic { omega: f32, amplitude: f32, phase: f32, decay: f32 },
137 DampedSine { omega: f32, zeta: f32, amplitude: f32, phase: f32 },
139 Epicycle { r1: f32, r2: f32, omega1: f32, omega2: f32 },
141
142 FractionalBrownian { frequency: f32, octaves: u8, hurst: f32, amplitude: f32 },
145 DomainWarp { frequency: f32, warp_strength: f32, octaves: u8, amplitude: f32 },
147 Cellular { frequency: f32, amplitude: f32 },
149
150 Sum(Box<MathFunction>, Box<MathFunction>),
153 Product(Box<MathFunction>, Box<MathFunction>),
155 Chain(Vec<MathFunction>),
157 Modulate { carrier: Box<MathFunction>, modulator: Box<MathFunction> },
159 Clamp { inner: Box<MathFunction>, min: f32, max: f32 },
161 Abs(Box<MathFunction>),
163 Scale { inner: Box<MathFunction>, factor: f32 },
165 Offset { inner: Box<MathFunction>, offset: f32 },
167 Invert(Box<MathFunction>),
169 Normalize { inner: Box<MathFunction>, t_range: f32, steps: u32 },
172 Delay { inner: Box<MathFunction>, delay: f32 },
174 Mirror { inner: Box<MathFunction>, period: f32 },
176}
177
178impl MathFunction {
179 pub fn evaluate(&self, t: f32, input: f32) -> f32 {
181 match self {
182 MathFunction::Constant(v) => *v,
184 MathFunction::Linear { slope, offset } => slope * t + offset,
185
186 MathFunction::Sine { amplitude, frequency, phase } => {
188 amplitude * (t * frequency * TAU + phase).sin()
189 }
190 MathFunction::Cosine { amplitude, frequency, phase } => {
191 amplitude * (t * frequency * TAU + phase).cos()
192 }
193 MathFunction::Triangle { amplitude, frequency, phase } => {
194 let p = (t * frequency + phase / TAU).fract();
195 amplitude * (if p < 0.5 { 4.0 * p - 1.0 } else { 3.0 - 4.0 * p })
196 }
197 MathFunction::Square { amplitude, frequency, duty } => {
198 let p = (t * frequency).fract();
199 if p < *duty { *amplitude } else { -amplitude }
200 }
201
202 MathFunction::Lorenz { sigma: _, rho: _, beta: _, scale } => {
204 let init = glam::Vec3::new(
206 (t * 0.1).sin() * 10.0,
207 (t * 0.07).cos() * 10.0,
208 20.0 + (t * 0.05).sin() * 5.0,
209 );
210 let mut state = init;
211 for _ in 0..40 {
212 let (next, _) = attractors::step(attractors::AttractorType::Lorenz, state, 0.01);
213 state = next;
214 }
215 state.x * scale
216 }
217 MathFunction::Perlin { frequency, octaves, amplitude } => {
218 use crate::math::noise::fbm;
219 fbm(t * frequency, 0.0, *octaves, 0.5, 2.0) * amplitude
220 }
221 MathFunction::Simplex { frequency, amplitude } => {
222 use crate::math::noise::noise1;
223 noise1(t * frequency) * amplitude
224 }
225
226 MathFunction::Exponential { start, target, rate } => {
228 target + (start - target) * (-rate * t).exp()
229 }
230 MathFunction::LogisticMap { r, x0 } => {
231 let n = (t * 30.0) as u32;
233 let mut x = *x0;
234 for _ in 0..n.min(200) {
235 x = r * x * (1.0 - x);
236 }
237 x * 2.0 - 1.0 }
239 MathFunction::Collatz { seed, scale } => {
240 let n = (t * 10.0) as usize;
242 let seq = collatz_sequence(*seed, 200);
243 let v = seq.get(n % seq.len()).copied().unwrap_or(1) as f32;
244 let max = seq.iter().copied().fold(1u64, u64::max) as f32;
246 (v / max) * 2.0 * scale - scale
247 }
248
249 MathFunction::Orbit { radius, speed, eccentricity, .. } => {
251 let angle = t * speed;
252 let r = radius * (1.0 - eccentricity * angle.cos());
253 r * angle.cos()
254 }
255 MathFunction::Spiral { radius_rate, speed, .. } => {
256 let angle = t * speed;
257 let r = t * radius_rate;
258 r * angle.cos()
259 }
260 MathFunction::GoldenSpiral { scale, speed, .. } => {
261 let phi = 1.618_034_f32;
262 let angle = t * speed;
263 phi.powf(angle / TAU) * angle.cos() * scale
264 }
265 MathFunction::Lissajous { a, b, delta, scale } => {
266 (a * t).sin() * scale + (b * t + delta).sin() * scale * 0.5
267 }
268
269 MathFunction::StrangeAttractor { attractor_type, scale, strength } => {
271 let init = attractors::initial_state(*attractor_type);
272 let seed_t = (t * 0.05).sin() * 5.0;
273 let mut state = glam::Vec3::new(
274 init.x + seed_t,
275 init.y + (t * 0.03).cos() * 5.0,
276 init.z,
277 );
278 for _ in 0..20 {
279 let (next, _) = attractors::step(*attractor_type, state, 0.01);
280 state = next;
281 }
282 state.x * scale * strength
283 }
284
285 MathFunction::MandelbrotEscape { c_real, c_imag, scale } => {
287 let (mut zr, mut zi) = (input, input * 0.5);
288 let max_iter = 64;
289 let mut iter = 0;
290 while iter < max_iter && zr * zr + zi * zi < 4.0 {
291 let new_zr = zr * zr - zi * zi + c_real;
292 zi = 2.0 * zr * zi + c_imag;
293 zr = new_zr;
294 iter += 1;
295 }
296 (iter as f32 / max_iter as f32) * 2.0 * scale - scale
297 }
298 MathFunction::JuliaSet { c_real, c_imag, scale } => {
299 let (mut zr, mut zi) = ((t * 0.1).sin() * 2.0, (t * 0.07).cos() * 2.0);
300 let max_iter = 64;
301 let mut iter = 0;
302 while iter < max_iter && zr * zr + zi * zi < 4.0 {
303 let new_zr = zr * zr - zi * zi + c_real;
304 zi = 2.0 * zr * zi + c_imag;
305 zr = new_zr;
306 iter += 1;
307 }
308 (iter as f32 / max_iter as f32) * 2.0 * scale - scale
309 }
310
311 MathFunction::SpringDamper { target, stiffness, damping } => {
313 let omega = stiffness.sqrt();
315 let zeta = damping / (2.0 * omega);
316 if zeta < 1.0 {
317 let omega_d = omega * (1.0 - zeta * zeta).sqrt();
318 let decay = (-zeta * omega * t).exp();
319 target + (-target) * decay * ((omega_d * t).cos() + zeta / (1.0 - zeta * zeta).sqrt() * (omega_d * t).sin())
320 } else {
321 target + (-target) * (-omega * t).exp()
323 }
324 }
325 MathFunction::CriticallyDamped { target, speed } => {
326 target + (-target) * (1.0 + speed * t) * (-speed * t).exp()
327 }
328
329 MathFunction::HeartBeat { bpm, intensity } => {
331 let cycle = (t * bpm / 60.0).fract();
332 if cycle < 0.10 {
333 intensity * 0.3 * (cycle / 0.10 * PI).sin() } else if cycle < 0.18 {
335 0.0 } else if cycle < 0.28 {
337 intensity * 2.0 * (cycle - 0.18) / 0.10 } else if cycle < 0.33 {
339 intensity * 2.0 * (1.0 - (cycle - 0.28) / 0.05) } else if cycle < 0.50 {
341 intensity * 0.3 * ((cycle - 0.33) / 0.17 * PI).sin() } else {
343 0.0 }
345 }
346 MathFunction::Breathing { rate, depth } => {
347 let cycle = (t * rate).fract();
348 if cycle < 0.40 {
349 depth * (cycle / 0.40 * PI).sin() } else if cycle < 0.45 {
351 *depth } else if cycle < 0.85 {
353 depth * ((cycle - 0.45) / 0.40 * PI + PI).sin() + depth } else {
355 0.0 }
357 }
358 MathFunction::Pendulum { length, gravity, damping } => {
359 let omega = (gravity / length).sqrt();
360 let theta0 = PI / 6.0; let decay = (-damping * t).exp();
362 theta0 * decay * (omega * t).cos()
363 }
364 MathFunction::Wave { wavelength, speed, amplitude, decay } => {
365 let phase = t * speed / wavelength;
366 amplitude * phase.sin() * (-decay * t).exp()
367 }
368
369 MathFunction::Sawtooth { amplitude, frequency, phase } => {
371 let p = (t * frequency + phase / TAU).fract();
372 amplitude * (2.0 * p - 1.0)
373 }
374 MathFunction::Ramp { amplitude, duration } => {
375 let p = (t / duration.max(f32::EPSILON)).fract();
376 amplitude * p
377 }
378
379 MathFunction::Sinc { frequency, amplitude } => {
381 let x = t * frequency * PI;
382 let v = if x.abs() < f32::EPSILON { 1.0 } else { x.sin() / x };
383 amplitude * v
384 }
385 MathFunction::Gaussian { amplitude, center, width } => {
386 let x = (t - center) / width.max(f32::EPSILON);
387 amplitude * (-x * x).exp()
388 }
389 MathFunction::BeatFrequency { freq1, freq2, amplitude } => {
390 amplitude * (t * freq1 * TAU).sin() * (t * freq2 * TAU).sin()
391 }
392 MathFunction::WavePacket { carrier_freq, envelope_width, amplitude, center } => {
393 let carrier = (t * carrier_freq * TAU).sin();
394 let envelope = {
395 let x = (t - center) / envelope_width.max(f32::EPSILON);
396 (-x * x).exp()
397 };
398 amplitude * carrier * envelope
399 }
400 MathFunction::FourierSeries { fundamental, coefficients } => {
401 let mut v = 0.0_f32;
402 for (n, (sin_c, cos_c)) in coefficients.iter().enumerate() {
403 let harmonic = (n as f32 + 1.0) * fundamental * TAU * t;
404 v += sin_c * harmonic.sin() + cos_c * harmonic.cos();
405 }
406 v
407 }
408
409 MathFunction::Sigmoid { steepness, center } => {
411 1.0 / (1.0 + (-steepness * (t - center)).exp())
412 }
413 MathFunction::Tanh { amplitude, steepness } => {
414 amplitude * (steepness * t).tanh()
415 }
416 MathFunction::SoftPlus { amplitude, steepness } => {
417 let s = steepness.max(f32::EPSILON);
418 amplitude * (1.0 + (s * t).exp()).ln() / s
419 }
420 MathFunction::Relu { slope, offset } => {
421 (slope * t + offset).max(0.0)
422 }
423 MathFunction::PowerLaw { exponent, scale } => {
424 scale * t.abs().powf(*exponent) * t.signum()
425 }
426
427 MathFunction::VanDerPol { mu, amplitude } => {
429 let dt_inner = 0.02_f32;
431 let steps = (t / dt_inner) as u32;
432 let mut x = *amplitude;
433 let mut v = 0.0_f32;
434 for _ in 0..steps.min(5000) {
435 let a = mu * (1.0 - x * x) * v - x;
436 v += a * dt_inner;
437 x += v * dt_inner;
438 }
439 x.clamp(-amplitude * 3.0, amplitude * 3.0)
440 }
441 MathFunction::Duffing { alpha, beta, delta, gamma, omega } => {
442 let dt_inner = 0.005_f32;
444 let steps = (t / dt_inner) as u32;
445 let mut x = 0.5_f32;
446 let mut v = 0.0_f32;
447 let mut s = 0.0_f32;
448 for _ in 0..steps.min(10000) {
449 let a = -delta * v - alpha * x - beta * x * x * x + gamma * (omega * s).cos();
450 v += a * dt_inner;
451 x += v * dt_inner;
452 s += dt_inner;
453 }
454 x.clamp(-5.0, 5.0)
455 }
456 MathFunction::TentMap { r, x0 } => {
457 let n = (t * 40.0) as u32;
458 let mut x = *x0;
459 for _ in 0..n.min(500) {
460 x = if x < 0.5 { r * x } else { r * (1.0 - x) };
461 }
462 x * 2.0 - 1.0
463 }
464 MathFunction::HenonMap { a, b, x0, y0 } => {
465 let n = (t * 30.0) as u32;
466 let mut x = *x0;
467 let mut y = *y0;
468 for _ in 0..n.min(1000) {
469 let new_x = 1.0 - a * x * x + y;
470 y = b * x;
471 x = new_x;
472 }
473 x.clamp(-2.0, 2.0)
474 }
475 MathFunction::Roessler { a, b, c, scale } => {
476 let dt_inner = 0.01_f32;
477 let steps = (t / dt_inner) as u32;
478 let (mut rx, mut ry, mut rz) = (0.1_f32, 0.0_f32, 0.0_f32);
479 for _ in 0..steps.min(5000) {
480 let dx = -(ry + rz);
481 let dy = rx + a * ry;
482 let dz = b + rz * (rx - c);
483 rx += dx * dt_inner;
484 ry += dy * dt_inner;
485 rz += dz * dt_inner;
486 }
487 rx * scale
488 }
489
490 MathFunction::DoublePendulum { l1, l2, m1, m2, theta1_0, theta2_0 } => {
492 let g = 9.81_f32;
494 let dt_rk = 0.005_f32;
495 let steps = (t / dt_rk) as u32;
496 let mut th1 = *theta1_0;
497 let mut th2 = *theta2_0;
498 let mut w1 = 0.0_f32;
499 let mut w2 = 0.0_f32;
500
501 let dp_accel = |th1: f32, th2: f32, w1: f32, w2: f32| -> (f32, f32) {
502 let dth = th1 - th2;
503 let denom1 = (m1 + m2) * l1 - m2 * l1 * dth.cos() * dth.cos();
504 let denom2 = (l2 / l1) * denom1;
505 let a1 = (m2 * l1 * w1 * w1 * dth.sin() * dth.cos()
506 + m2 * g * th2.sin() * dth.cos()
507 + m2 * l2 * w2 * w2 * dth.sin()
508 - (m1 + m2) * g * th1.sin()) / denom1.max(f32::EPSILON);
509 let a2 = (-(m1 + m2) * (l1 * w1 * w1 * dth.sin()
510 + g * th1.sin() * dth.cos() - g * th2.sin())
511 - m2 * l2 * w2 * w2 * dth.sin() * dth.cos()) / denom2.max(f32::EPSILON);
512 (a1, a2)
513 };
514
515 for _ in 0..steps.min(8000) {
516 let (a1, a2) = dp_accel(th1, th2, w1, w2);
517 w1 += a1 * dt_rk;
519 w2 += a2 * dt_rk;
520 th1 += w1 * dt_rk;
521 th2 += w2 * dt_rk;
522 }
523 th1.clamp(-PI * 4.0, PI * 4.0)
524 }
525 MathFunction::Projectile { v0, angle_deg, gravity } => {
526 let angle = angle_deg.to_radians();
527 let vy = v0 * angle.sin();
528 vy * t - 0.5 * gravity * t * t
529 }
530 MathFunction::SimpleHarmonic { omega, amplitude, phase, decay } => {
531 let env = if *decay > f32::EPSILON { (-decay * t).exp() } else { 1.0 };
532 amplitude * env * (omega * t + phase).sin()
533 }
534 MathFunction::DampedSine { omega, zeta, amplitude, phase } => {
535 let omega_d = omega * (1.0 - zeta * zeta).abs().sqrt();
536 let env = (-zeta * omega * t).exp();
537 amplitude * env * (omega_d * t + phase).sin()
538 }
539 MathFunction::Epicycle { r1, r2, omega1, omega2 } => {
540 let x = r1 * (omega1 * t).cos() + r2 * (omega2 * t).cos();
541 x }
543
544 MathFunction::FractionalBrownian { frequency, octaves, hurst, amplitude } => {
546 use crate::math::noise::fbm;
547 let persistence = 2.0_f32.powf(-*hurst);
549 fbm(t * frequency, 0.0, *octaves, persistence, 2.0) * amplitude
550 }
551 MathFunction::DomainWarp { frequency, warp_strength, octaves, amplitude } => {
552 use crate::math::noise::{fbm, noise1};
553 let warp = noise1(t * frequency * 0.5) * warp_strength;
555 fbm((t + warp) * frequency, 0.0, *octaves, 0.5, 2.0) * amplitude
556 }
557 MathFunction::Cellular { frequency, amplitude } => {
558 let cell = (t * frequency).floor();
560 let frac = (t * frequency).fract();
561 let mut min_dist = f32::MAX;
562 for offset in [-1i32, 0, 1] {
563 let point_cell = cell + offset as f32;
564 let hash = (point_cell * 127.321 + 3481.12).sin().abs();
566 let point = hash; let dist = (frac - point - offset as f32).abs();
568 min_dist = min_dist.min(dist);
569 }
570 amplitude * min_dist * 2.0
571 }
572
573 MathFunction::Sum(a, b) => a.evaluate(t, input) + b.evaluate(t, input),
575 MathFunction::Product(a, b) => a.evaluate(t, input) * b.evaluate(t, input),
576 MathFunction::Chain(functions) => {
577 let mut value = input;
578 for f in functions {
579 value = f.evaluate(t, value);
580 }
581 value
582 }
583 MathFunction::Modulate { carrier, modulator } => {
584 carrier.evaluate(t, input) * modulator.evaluate(t, input)
585 }
586 MathFunction::Clamp { inner, min, max } => {
587 inner.evaluate(t, input).clamp(*min, *max)
588 }
589 MathFunction::Abs(inner) => inner.evaluate(t, input).abs(),
590 MathFunction::Scale { inner, factor } => inner.evaluate(t, input) * factor,
591 MathFunction::Offset { inner, offset } => inner.evaluate(t, input) + offset,
592 MathFunction::Invert(inner) => -inner.evaluate(t, input),
593 MathFunction::Normalize { inner, t_range, steps } => {
594 let n = (*steps).max(2) as usize;
596 let dt = t_range / (n - 1) as f32;
597 let mut min_v = f32::MAX;
598 let mut max_v = f32::MIN;
599 for i in 0..n {
600 let v = inner.evaluate(i as f32 * dt, 0.0);
601 if v < min_v { min_v = v; }
602 if v > max_v { max_v = v; }
603 }
604 let range = (max_v - min_v).max(f32::EPSILON);
605 let raw = inner.evaluate(t, input);
606 (raw - min_v) / range * 2.0 - 1.0
607 }
608 MathFunction::Delay { inner, delay } => {
609 inner.evaluate((t - delay).max(0.0), input)
610 }
611 MathFunction::Mirror { inner, period } => {
612 let p = period.max(f32::EPSILON);
613 let t2 = (t % (2.0 * p) + 2.0 * p) % (2.0 * p);
614 let t_m = if t2 < p { t2 } else { 2.0 * p - t2 };
615 inner.evaluate(t_m, input)
616 }
617 }
618 }
619
620 pub fn derivative(&self, t: f32, epsilon: f32) -> f32 {
626 let hi = self.evaluate(t + epsilon, 0.0);
627 let lo = self.evaluate(t - epsilon, 0.0);
628 (hi - lo) / (2.0 * epsilon.max(f32::EPSILON))
629 }
630
631 pub fn integrate(&self, from: f32, to: f32, steps: u32) -> f32 {
635 let n = ((steps + 1) & !1) as usize; let h = (to - from) / n as f32;
637 let mut sum = self.evaluate(from, 0.0) + self.evaluate(to, 0.0);
638 for i in 1..n {
639 let x = from + i as f32 * h;
640 let w = if i % 2 == 0 { 2.0 } else { 4.0 };
641 sum += w * self.evaluate(x, 0.0);
642 }
643 sum * h / 3.0
644 }
645
646 pub fn sample_range(&self, t_start: f32, t_end: f32, n: u32) -> Vec<f32> {
648 let count = n.max(2) as usize;
649 let dt = (t_end - t_start) / (count - 1) as f32;
650 (0..count).map(|i| self.evaluate(t_start + i as f32 * dt, 0.0)).collect()
651 }
652
653 pub fn find_range(&self, t_start: f32, t_end: f32, steps: u32) -> (f32, f32) {
655 let samples = self.sample_range(t_start, t_end, steps.max(2));
656 let min = samples.iter().cloned().fold(f32::MAX, f32::min);
657 let max = samples.iter().cloned().fold(f32::MIN, f32::max);
658 (min, max)
659 }
660
661 pub fn zero_crossings(&self, t_start: f32, t_end: f32, steps: u32) -> Vec<f32> {
663 let count = steps.max(2) as usize;
664 let dt = (t_end - t_start) / (count - 1) as f32;
665 let mut crossings = Vec::new();
666 let mut prev = self.evaluate(t_start, 0.0);
667 for i in 1..count {
668 let t = t_start + i as f32 * dt;
669 let v = self.evaluate(t, 0.0);
670 if (prev < 0.0) != (v < 0.0) {
671 let frac = -prev / (v - prev).max(f32::EPSILON);
673 crossings.push(t - dt + frac * dt);
674 }
675 prev = v;
676 }
677 crossings
678 }
679
680 pub fn evaluate_vec3(&self, t: f32) -> glam::Vec3 {
685 glam::Vec3::new(
686 self.evaluate(t, 0.0),
687 self.evaluate(t + 1.0, 0.0),
688 self.evaluate(t + 2.0, 0.0),
689 )
690 }
691
692 pub fn evaluate_vec3_phased(&self, t: f32, phase_x: f32, phase_y: f32, phase_z: f32) -> glam::Vec3 {
694 glam::Vec3::new(
695 self.evaluate(t + phase_x, 0.0),
696 self.evaluate(t + phase_y, 0.0),
697 self.evaluate(t + phase_z, 0.0),
698 )
699 }
700
701 pub fn add(self, other: MathFunction) -> MathFunction {
703 MathFunction::Sum(Box::new(self), Box::new(other))
704 }
705
706 pub fn mul(self, other: MathFunction) -> MathFunction {
708 MathFunction::Product(Box::new(self), Box::new(other))
709 }
710
711 pub fn scale(self, factor: f32) -> MathFunction {
713 MathFunction::Scale { inner: Box::new(self), factor }
714 }
715
716 pub fn offset(self, offset: f32) -> MathFunction {
718 MathFunction::Offset { inner: Box::new(self), offset }
719 }
720
721 pub fn clamp(self, min: f32, max: f32) -> MathFunction {
723 MathFunction::Clamp { inner: Box::new(self), min, max }
724 }
725
726 pub fn delay(self, seconds: f32) -> MathFunction {
728 MathFunction::Delay { inner: Box::new(self), delay: seconds }
729 }
730
731 pub fn invert(self) -> MathFunction {
733 MathFunction::Invert(Box::new(self))
734 }
735
736 pub fn modulate(self, modulator: MathFunction) -> MathFunction {
738 MathFunction::Modulate {
739 carrier: Box::new(self),
740 modulator: Box::new(modulator),
741 }
742 }
743}
744
745fn collatz_sequence(n: u64, max_steps: usize) -> Vec<u64> {
747 let mut seq = Vec::with_capacity(max_steps);
748 let mut x = n.max(1);
749 seq.push(x);
750 while x != 1 && seq.len() < max_steps {
751 x = if x % 2 == 0 { x / 2 } else { 3 * x + 1 };
752 seq.push(x);
753 }
754 seq
755}
756
757#[cfg(test)]
758mod tests {
759 use super::*;
760
761 #[test]
762 fn sine_at_zero() {
763 let f = MathFunction::Sine { amplitude: 1.0, frequency: 1.0, phase: 0.0 };
764 assert!((f.evaluate(0.0, 0.0)).abs() < 1e-5);
765 }
766
767 #[test]
768 fn constant_is_constant() {
769 let f = MathFunction::Constant(42.0);
770 assert_eq!(f.evaluate(0.0, 0.0), 42.0);
771 assert_eq!(f.evaluate(100.0, -999.0), 42.0);
772 }
773
774 #[test]
775 fn breathing_never_negative() {
776 let f = MathFunction::Breathing { rate: 0.25, depth: 1.0 };
777 for i in 0..100 {
778 let v = f.evaluate(i as f32 * 0.1, 0.0);
779 assert!(v >= -0.01, "breathing went negative at t={}", i as f32 * 0.1);
780 }
781 }
782
783 #[test]
784 fn chain_composes() {
785 let f = MathFunction::Chain(vec![
786 MathFunction::Constant(2.0),
787 MathFunction::Linear { slope: 1.0, offset: 0.0 }, ]);
789 let _ = f.evaluate(0.0, 0.0); }
797}