Skip to main content

proof_engine/surfaces/
deformation.rs

1//! # Surface Deformation
2//!
3//! Time-varying deformation of surface meshes. Deformations are defined as vertex
4//! displacement operations that can be stacked, animated, and blended.
5//!
6//! ## Features
7//!
8//! - [`DeformationMode`] — built-in deformation types (breathe, wave, twist, melt, etc.)
9//! - [`DeformationStack`] — apply multiple deformations in sequence
10//! - [`MorphTarget`] — blend between two surface states
11//! - [`WaveSimulation`] — 2D wave equation simulation on a grid
12//! - [`KeyframeAnimator`] — animate deformation parameters over time
13
14use glam::Vec3;
15use std::f32::consts::{PI, TAU};
16
17// ─────────────────────────────────────────────────────────────────────────────
18// Deformation modes
19// ─────────────────────────────────────────────────────────────────────────────
20
21/// A deformation operation that displaces vertices over time.
22#[derive(Debug, Clone)]
23pub enum DeformationMode {
24    /// Uniform scale oscillation — surface "breathes" in and out.
25    Breathe {
26        amplitude: f32,
27        frequency: f32,
28    },
29    /// Sinusoidal wave propagation along a direction.
30    Wave {
31        direction: Vec3,
32        amplitude: f32,
33        wavelength: f32,
34        speed: f32,
35    },
36    /// Twist vertices around an axis.
37    Twist {
38        axis: Vec3,
39        strength: f32,
40        falloff_start: f32,
41        falloff_end: f32,
42    },
43    /// Vertices droop downward under simulated gravity.
44    Melt {
45        rate: f32,
46        gravity_dir: Vec3,
47        noise_scale: f32,
48    },
49    /// Vertices fly outward from center.
50    Explode {
51        strength: f32,
52        center: Vec3,
53        noise_scale: f32,
54    },
55    /// Concentric ripples from a point on the surface.
56    Ripple {
57        center: Vec3,
58        amplitude: f32,
59        wavelength: f32,
60        speed: f32,
61        decay: f32,
62    },
63    /// Fold the surface along a plane.
64    Fold {
65        plane_point: Vec3,
66        plane_normal: Vec3,
67        angle: f32,
68        sharpness: f32,
69    },
70    /// Break the surface into fragments that fly apart.
71    Shatter {
72        center: Vec3,
73        strength: f32,
74        fragment_seed: u32,
75        gravity: Vec3,
76    },
77    /// Displace along normals by a noise-like function.
78    NoiseDisplace {
79        amplitude: f32,
80        frequency: f32,
81        speed: f32,
82    },
83    /// Pinch vertices toward a line/axis.
84    Pinch {
85        axis_point: Vec3,
86        axis_dir: Vec3,
87        strength: f32,
88        radius: f32,
89    },
90    /// Inflate/deflate along normals.
91    Inflate {
92        amount: f32,
93    },
94    /// Taper: scale diminishes along an axis.
95    Taper {
96        axis: Vec3,
97        start: f32,
98        end: f32,
99        scale_at_end: f32,
100    },
101}
102
103/// A single deformation with its parameters and time influence.
104#[derive(Clone)]
105pub struct Deformation {
106    pub mode: DeformationMode,
107    /// Weight/intensity of this deformation (0 = off, 1 = full).
108    pub weight: f32,
109    /// Whether the deformation is active.
110    pub active: bool,
111    /// Falloff function: distance from center of effect -> weight multiplier.
112    pub falloff: FalloffFunction,
113    /// Center of the falloff region (in local space).
114    pub falloff_center: Vec3,
115    /// Radius of the falloff region.
116    pub falloff_radius: f32,
117}
118
119impl Deformation {
120    /// Create a new deformation at full weight with no falloff.
121    pub fn new(mode: DeformationMode) -> Self {
122        Self {
123            mode,
124            weight: 1.0,
125            active: true,
126            falloff: FalloffFunction::None,
127            falloff_center: Vec3::ZERO,
128            falloff_radius: f32::MAX,
129        }
130    }
131
132    /// Set the weight of this deformation.
133    pub fn with_weight(mut self, weight: f32) -> Self {
134        self.weight = weight;
135        self
136    }
137
138    /// Set a spherical falloff region.
139    pub fn with_falloff(mut self, center: Vec3, radius: f32, falloff: FalloffFunction) -> Self {
140        self.falloff_center = center;
141        self.falloff_radius = radius;
142        self.falloff = falloff;
143        self
144    }
145
146    /// Compute the falloff multiplier for a vertex at the given position.
147    fn compute_falloff(&self, position: Vec3) -> f32 {
148        if matches!(self.falloff, FalloffFunction::None) {
149            return 1.0;
150        }
151        let dist = (position - self.falloff_center).length();
152        if dist > self.falloff_radius {
153            return 0.0;
154        }
155        let t = dist / self.falloff_radius;
156        match self.falloff {
157            FalloffFunction::None => 1.0,
158            FalloffFunction::Linear => 1.0 - t,
159            FalloffFunction::Smooth => {
160                let s = 1.0 - t;
161                s * s * (3.0 - 2.0 * s)
162            }
163            FalloffFunction::Exponential { decay } => (-t * decay).exp(),
164            FalloffFunction::Gaussian { sigma } => (-(t * t) / (2.0 * sigma * sigma)).exp(),
165            FalloffFunction::InverseSquare => 1.0 / (1.0 + t * t),
166        }
167    }
168
169    /// Apply this deformation to a single vertex.
170    pub fn apply(&self, position: Vec3, normal: Vec3, time: f32) -> Vec3 {
171        if !self.active || self.weight < 1e-6 {
172            return position;
173        }
174
175        let falloff = self.compute_falloff(position) * self.weight;
176        if falloff < 1e-6 {
177            return position;
178        }
179
180        let displacement = match self.mode.clone() {
181            DeformationMode::Breathe { amplitude, frequency } => {
182                let scale = (time * frequency * TAU).sin() * amplitude;
183                normal * scale
184            }
185            DeformationMode::Wave { direction, amplitude, wavelength, speed } => {
186                let dir = direction.normalize_or_zero();
187                let phase = position.dot(dir) / wavelength - time * speed;
188                let disp = (phase * TAU).sin() * amplitude;
189                normal * disp
190            }
191            DeformationMode::Twist { axis, strength, falloff_start, falloff_end } => {
192                let axis_n = axis.normalize_or_zero();
193                let proj = axis_n * position.dot(axis_n);
194                let radial = position - proj;
195                let t_along = position.dot(axis_n);
196
197                let twist_amount = if falloff_end > falloff_start {
198                    let local_t = ((t_along - falloff_start) / (falloff_end - falloff_start))
199                        .clamp(0.0, 1.0);
200                    local_t * strength * time
201                } else {
202                    strength * time
203                };
204
205                let angle = twist_amount;
206                let cos_a = angle.cos();
207                let sin_a = angle.sin();
208
209                // Rodrigues rotation
210                let rotated = radial * cos_a
211                    + axis_n.cross(radial) * sin_a
212                    + axis_n * axis_n.dot(radial) * (1.0 - cos_a);
213
214                (proj + rotated) - position
215            }
216            DeformationMode::Melt { rate, gravity_dir, noise_scale } => {
217                let g = gravity_dir.normalize_or_zero();
218                let droop = rate * time * time * 0.5;
219                // Higher vertices melt faster
220                let height_factor = position.dot(-g).max(0.0);
221                let noise = simple_noise_3d(position * noise_scale) * 0.5 + 0.5;
222                g * droop * height_factor * noise
223            }
224            DeformationMode::Explode { strength, center, noise_scale } => {
225                let dir = (position - center).normalize_or_zero();
226                let dist = (position - center).length();
227                let noise = simple_noise_3d(position * noise_scale) * 0.5 + 0.5;
228                let t_exp = (time * strength).min(10.0);
229                dir * t_exp * (1.0 + noise * 0.5) * (1.0 + dist * 0.1)
230            }
231            DeformationMode::Ripple { center, amplitude, wavelength, speed, decay } => {
232                let dist = (position - center).length();
233                let wave = ((dist / wavelength - time * speed) * TAU).sin();
234                let attenuation = (-dist * decay).exp();
235                normal * wave * amplitude * attenuation
236            }
237            DeformationMode::Fold { plane_point, plane_normal, angle, sharpness } => {
238                let pn = plane_normal.normalize_or_zero();
239                let to_point = position - plane_point;
240                let signed_dist = to_point.dot(pn);
241
242                if signed_dist > 0.0 {
243                    // On the positive side of the fold plane
244                    let fold_factor = (signed_dist * sharpness).min(1.0);
245                    let fold_angle = angle * fold_factor * time.min(1.0);
246
247                    // Reflect around the plane normal
248                    let cos_a = fold_angle.cos();
249                    let sin_a = fold_angle.sin();
250                    let proj = pn * signed_dist;
251                    let in_plane = to_point - proj;
252
253                    let folded = in_plane + pn * (signed_dist * cos_a);
254                    let cross = pn.cross(in_plane.normalize_or_zero()) * signed_dist * sin_a;
255
256                    (folded + cross + plane_point) - position
257                } else {
258                    Vec3::ZERO
259                }
260            }
261            DeformationMode::Shatter { center, strength, fragment_seed, gravity } => {
262                let dir = (position - center).normalize_or_zero();
263                // Use position hash as fragment seed
264                let hash = hash_vec3(position, fragment_seed);
265                let frag_dir = Vec3::new(
266                    (hash & 0xFF) as f32 / 128.0 - 1.0,
267                    ((hash >> 8) & 0xFF) as f32 / 128.0 - 1.0,
268                    ((hash >> 16) & 0xFF) as f32 / 128.0 - 1.0,
269                ).normalize_or_zero();
270
271                let t = time * strength;
272                let explosion = (dir + frag_dir * 0.5) * t;
273                let grav = gravity * t * t * 0.5;
274                explosion + grav
275            }
276            DeformationMode::NoiseDisplace { amplitude, frequency, speed } => {
277                let sample_pos = position * frequency + Vec3::splat(time * speed);
278                let n = simple_noise_3d(sample_pos);
279                normal * n * amplitude
280            }
281            DeformationMode::Pinch { axis_point, axis_dir, strength, radius } => {
282                let axis = axis_dir.normalize_or_zero();
283                let to_point = position - axis_point;
284                let along = axis * to_point.dot(axis);
285                let radial = to_point - along;
286                let dist = radial.length();
287                if dist < radius && dist > 1e-6 {
288                    let t = 1.0 - dist / radius;
289                    let t = t * t; // quadratic falloff
290                    -radial.normalize_or_zero() * t * strength * time.min(1.0)
291                } else {
292                    Vec3::ZERO
293                }
294            }
295            DeformationMode::Inflate { amount } => {
296                normal * amount * time.min(1.0)
297            }
298            DeformationMode::Taper { axis, start, end, scale_at_end } => {
299                let axis_n = axis.normalize_or_zero();
300                let t_along = position.dot(axis_n);
301                let range = end - start;
302                if range.abs() < 1e-6 {
303                    return position;
304                }
305                let local_t = ((t_along - start) / range).clamp(0.0, 1.0);
306                let scale = 1.0 + (scale_at_end - 1.0) * local_t * time.min(1.0);
307                let proj = axis_n * t_along;
308                let radial = position - proj;
309                (proj + radial * scale) - position
310            }
311        };
312
313        position + displacement * falloff
314    }
315}
316
317/// Falloff function for controlling deformation influence.
318#[derive(Debug, Clone, Copy)]
319pub enum FalloffFunction {
320    /// No falloff — full effect everywhere.
321    None,
322    /// Linear falloff from center.
323    Linear,
324    /// Smooth (hermite) falloff.
325    Smooth,
326    /// Exponential decay.
327    Exponential { decay: f32 },
328    /// Gaussian falloff.
329    Gaussian { sigma: f32 },
330    /// Inverse square falloff.
331    InverseSquare,
332}
333
334// ─────────────────────────────────────────────────────────────────────────────
335// Deformation stack
336// ─────────────────────────────────────────────────────────────────────────────
337
338/// A stack of deformations applied sequentially to a mesh.
339pub struct DeformationStack {
340    pub deformations: Vec<Deformation>,
341    pub time: f32,
342    /// Master weight applied to all deformations.
343    pub master_weight: f32,
344}
345
346impl DeformationStack {
347    /// Create a new empty deformation stack.
348    pub fn new() -> Self {
349        Self {
350            deformations: Vec::new(),
351            time: 0.0,
352            master_weight: 1.0,
353        }
354    }
355
356    /// Add a deformation to the stack.
357    pub fn push(&mut self, deformation: Deformation) {
358        self.deformations.push(deformation);
359    }
360
361    /// Remove a deformation by index.
362    pub fn remove(&mut self, index: usize) {
363        if index < self.deformations.len() {
364            self.deformations.remove(index);
365        }
366    }
367
368    /// Clear all deformations.
369    pub fn clear(&mut self) {
370        self.deformations.clear();
371    }
372
373    /// Set the time for all deformations.
374    pub fn set_time(&mut self, time: f32) {
375        self.time = time;
376    }
377
378    /// Advance time by dt.
379    pub fn tick(&mut self, dt: f32) {
380        self.time += dt;
381    }
382
383    /// Apply all deformations to a vertex.
384    pub fn apply(&self, mut position: Vec3, normal: Vec3) -> Vec3 {
385        if self.master_weight < 1e-6 {
386            return position;
387        }
388        for deform in &self.deformations {
389            let deformed = deform.apply(position, normal, self.time);
390            position = position + (deformed - position) * self.master_weight;
391        }
392        position
393    }
394
395    /// Apply all deformations to a mesh (modifying positions in place).
396    pub fn apply_to_positions(&self, positions: &mut [Vec3], normals: &[Vec3]) {
397        if self.master_weight < 1e-6 || self.deformations.is_empty() {
398            return;
399        }
400        for (i, pos) in positions.iter_mut().enumerate() {
401            let normal = normals.get(i).copied().unwrap_or(Vec3::Y);
402            *pos = self.apply(*pos, normal);
403        }
404    }
405
406    /// Apply all deformations and return displaced positions (non-mutating).
407    pub fn compute_displaced(&self, positions: &[Vec3], normals: &[Vec3]) -> Vec<Vec3> {
408        positions.iter().enumerate().map(|(i, &pos)| {
409            let normal = normals.get(i).copied().unwrap_or(Vec3::Y);
410            self.apply(pos, normal)
411        }).collect()
412    }
413
414    /// Get the number of active deformations.
415    pub fn active_count(&self) -> usize {
416        self.deformations.iter().filter(|d| d.active).count()
417    }
418
419    /// Set the weight of a deformation by index.
420    pub fn set_weight(&mut self, index: usize, weight: f32) {
421        if let Some(d) = self.deformations.get_mut(index) {
422            d.weight = weight;
423        }
424    }
425
426    /// Enable/disable a deformation by index.
427    pub fn set_active(&mut self, index: usize, active: bool) {
428        if let Some(d) = self.deformations.get_mut(index) {
429            d.active = active;
430        }
431    }
432}
433
434impl Default for DeformationStack {
435    fn default() -> Self { Self::new() }
436}
437
438// ─────────────────────────────────────────────────────────────────────────────
439// Morph targets
440// ─────────────────────────────────────────────────────────────────────────────
441
442/// Blend between two sets of vertex positions.
443pub struct MorphTarget {
444    /// Base (rest) positions.
445    pub base_positions: Vec<Vec3>,
446    /// Target positions to morph toward.
447    pub target_positions: Vec<Vec3>,
448    /// Base normals.
449    pub base_normals: Vec<Vec3>,
450    /// Target normals.
451    pub target_normals: Vec<Vec3>,
452    /// Current blend factor (0 = base, 1 = target).
453    pub blend: f32,
454    /// Animation speed (blend units per second).
455    pub speed: f32,
456    /// Whether to ping-pong between states.
457    pub ping_pong: bool,
458    /// Internal direction for ping-pong.
459    direction: f32,
460}
461
462impl MorphTarget {
463    /// Create a morph target from base and target position arrays.
464    pub fn new(base_positions: Vec<Vec3>, target_positions: Vec<Vec3>) -> Self {
465        let len = base_positions.len();
466        Self {
467            base_positions,
468            target_positions,
469            base_normals: vec![Vec3::Y; len],
470            target_normals: vec![Vec3::Y; len],
471            blend: 0.0,
472            speed: 1.0,
473            ping_pong: false,
474            direction: 1.0,
475        }
476    }
477
478    /// Create with normals.
479    pub fn with_normals(
480        mut self,
481        base_normals: Vec<Vec3>,
482        target_normals: Vec<Vec3>,
483    ) -> Self {
484        self.base_normals = base_normals;
485        self.target_normals = target_normals;
486        self
487    }
488
489    /// Set the blend speed.
490    pub fn with_speed(mut self, speed: f32) -> Self {
491        self.speed = speed;
492        self
493    }
494
495    /// Enable ping-pong animation.
496    pub fn with_ping_pong(mut self, ping_pong: bool) -> Self {
497        self.ping_pong = ping_pong;
498        self
499    }
500
501    /// Update the morph blend over time.
502    pub fn tick(&mut self, dt: f32) {
503        self.blend += self.direction * self.speed * dt;
504
505        if self.ping_pong {
506            if self.blend >= 1.0 {
507                self.blend = 1.0;
508                self.direction = -1.0;
509            } else if self.blend <= 0.0 {
510                self.blend = 0.0;
511                self.direction = 1.0;
512            }
513        } else {
514            self.blend = self.blend.clamp(0.0, 1.0);
515        }
516    }
517
518    /// Set the blend factor directly.
519    pub fn set_blend(&mut self, blend: f32) {
520        self.blend = blend.clamp(0.0, 1.0);
521    }
522
523    /// Compute the current blended positions.
524    pub fn compute_positions(&self) -> Vec<Vec3> {
525        let t = self.blend;
526        let len = self.base_positions.len().min(self.target_positions.len());
527        (0..len).map(|i| {
528            self.base_positions[i] * (1.0 - t) + self.target_positions[i] * t
529        }).collect()
530    }
531
532    /// Compute the current blended normals.
533    pub fn compute_normals(&self) -> Vec<Vec3> {
534        let t = self.blend;
535        let len = self.base_normals.len().min(self.target_normals.len());
536        (0..len).map(|i| {
537            (self.base_normals[i] * (1.0 - t) + self.target_normals[i] * t).normalize_or_zero()
538        }).collect()
539    }
540
541    /// Get the vertex count.
542    pub fn vertex_count(&self) -> usize {
543        self.base_positions.len()
544    }
545
546    /// Check if the morph is complete (blend at 0 or 1).
547    pub fn is_complete(&self) -> bool {
548        self.blend <= 0.0 || self.blend >= 1.0
549    }
550}
551
552/// Multi-target morph: blend between N different pose states.
553pub struct MultiMorphTarget {
554    /// Base positions.
555    pub base_positions: Vec<Vec3>,
556    /// Multiple target position sets.
557    pub targets: Vec<Vec<Vec3>>,
558    /// Weight for each target (should sum to <= 1.0).
559    pub weights: Vec<f32>,
560}
561
562impl MultiMorphTarget {
563    pub fn new(base_positions: Vec<Vec3>) -> Self {
564        Self {
565            base_positions,
566            targets: Vec::new(),
567            weights: Vec::new(),
568        }
569    }
570
571    /// Add a morph target.
572    pub fn add_target(&mut self, target: Vec<Vec3>) {
573        self.targets.push(target);
574        self.weights.push(0.0);
575    }
576
577    /// Set the weight for a target.
578    pub fn set_weight(&mut self, index: usize, weight: f32) {
579        if let Some(w) = self.weights.get_mut(index) {
580            *w = weight.clamp(0.0, 1.0);
581        }
582    }
583
584    /// Compute the blended positions.
585    pub fn compute_positions(&self) -> Vec<Vec3> {
586        let len = self.base_positions.len();
587        let mut result = self.base_positions.clone();
588
589        for (target_idx, target) in self.targets.iter().enumerate() {
590            let w = self.weights.get(target_idx).copied().unwrap_or(0.0);
591            if w < 1e-6 { continue; }
592            for i in 0..len.min(target.len()) {
593                let delta = target[i] - self.base_positions[i];
594                result[i] += delta * w;
595            }
596        }
597
598        result
599    }
600}
601
602// ─────────────────────────────────────────────────────────────────────────────
603// Wave equation simulation
604// ─────────────────────────────────────────────────────────────────────────────
605
606/// 2D wave equation simulation on a grid.
607///
608/// Simulates wave propagation using the discretized wave equation:
609/// u(t+1) = 2*u(t) - u(t-1) + c^2 * dt^2 * laplacian(u)
610pub struct WaveSimulation {
611    /// Grid width.
612    pub width: usize,
613    /// Grid height.
614    pub height: usize,
615    /// Current displacement values.
616    pub current: Vec<f32>,
617    /// Previous frame displacement values.
618    pub previous: Vec<f32>,
619    /// Wave propagation speed.
620    pub speed: f32,
621    /// Damping factor (0 = no damping, higher = more damping).
622    pub damping: f32,
623    /// Boundary condition: if true, edges are fixed at zero.
624    pub fixed_boundaries: bool,
625}
626
627impl WaveSimulation {
628    /// Create a new wave simulation grid.
629    pub fn new(width: usize, height: usize, speed: f32) -> Self {
630        let size = width * height;
631        Self {
632            width,
633            height,
634            current: vec![0.0; size],
635            previous: vec![0.0; size],
636            speed,
637            damping: 0.01,
638            fixed_boundaries: true,
639        }
640    }
641
642    /// Set damping factor.
643    pub fn with_damping(mut self, damping: f32) -> Self {
644        self.damping = damping;
645        self
646    }
647
648    /// Set boundary condition.
649    pub fn with_fixed_boundaries(mut self, fixed: bool) -> Self {
650        self.fixed_boundaries = fixed;
651        self
652    }
653
654    /// Reset the simulation to zero.
655    pub fn reset(&mut self) {
656        self.current.fill(0.0);
657        self.previous.fill(0.0);
658    }
659
660    /// Add a displacement at a grid point.
661    pub fn displace(&mut self, x: usize, y: usize, amount: f32) {
662        if x < self.width && y < self.height {
663            self.current[y * self.width + x] += amount;
664        }
665    }
666
667    /// Add a circular impulse at a position.
668    pub fn impulse(&mut self, cx: f32, cy: f32, radius: f32, amplitude: f32) {
669        let r2 = radius * radius;
670        let min_x = ((cx - radius).floor() as i32).max(0) as usize;
671        let max_x = ((cx + radius).ceil() as i32).min(self.width as i32 - 1) as usize;
672        let min_y = ((cy - radius).floor() as i32).max(0) as usize;
673        let max_y = ((cy + radius).ceil() as i32).min(self.height as i32 - 1) as usize;
674
675        for y in min_y..=max_y {
676            for x in min_x..=max_x {
677                let dx = x as f32 - cx;
678                let dy = y as f32 - cy;
679                let d2 = dx * dx + dy * dy;
680                if d2 < r2 {
681                    let falloff = 1.0 - d2 / r2;
682                    let falloff = falloff * falloff;
683                    self.current[y * self.width + x] += amplitude * falloff;
684                }
685            }
686        }
687    }
688
689    /// Add a sinusoidal source at a point (continuous oscillation).
690    pub fn oscillator(&mut self, x: usize, y: usize, time: f32, frequency: f32, amplitude: f32) {
691        if x < self.width && y < self.height {
692            self.current[y * self.width + x] = (time * frequency * TAU).sin() * amplitude;
693        }
694    }
695
696    /// Step the simulation by dt.
697    pub fn step(&mut self, dt: f32) {
698        let c2 = self.speed * self.speed * dt * dt;
699        let damp = 1.0 - self.damping;
700
701        let mut next = vec![0.0f32; self.width * self.height];
702
703        let w = self.width;
704        let h = self.height;
705
706        for y in 1..h - 1 {
707            for x in 1..w - 1 {
708                let idx = y * w + x;
709                let laplacian = self.current[idx - 1]
710                    + self.current[idx + 1]
711                    + self.current[idx - w]
712                    + self.current[idx + w]
713                    - 4.0 * self.current[idx];
714
715                next[idx] = (2.0 * self.current[idx] - self.previous[idx]
716                    + c2 * laplacian) * damp;
717            }
718        }
719
720        if !self.fixed_boundaries {
721            // Open boundaries: copy from adjacent interior cells
722            for x in 0..w {
723                next[x] = next[w + x];                     // top row
724                next[(h - 1) * w + x] = next[(h - 2) * w + x]; // bottom row
725            }
726            for y in 0..h {
727                next[y * w] = next[y * w + 1];             // left column
728                next[y * w + w - 1] = next[y * w + w - 2]; // right column
729            }
730        }
731
732        self.previous = std::mem::replace(&mut self.current, next);
733    }
734
735    /// Get the displacement at a grid point.
736    pub fn get(&self, x: usize, y: usize) -> f32 {
737        if x < self.width && y < self.height {
738            self.current[y * self.width + x]
739        } else {
740            0.0
741        }
742    }
743
744    /// Sample displacement at a fractional position using bilinear interpolation.
745    pub fn sample(&self, x: f32, y: f32) -> f32 {
746        let fx = x.clamp(0.0, (self.width - 1) as f32);
747        let fy = y.clamp(0.0, (self.height - 1) as f32);
748        let ix = fx as usize;
749        let iy = fy as usize;
750        let sx = fx - ix as f32;
751        let sy = fy - iy as f32;
752
753        let ix1 = (ix + 1).min(self.width - 1);
754        let iy1 = (iy + 1).min(self.height - 1);
755
756        let v00 = self.current[iy * self.width + ix];
757        let v10 = self.current[iy * self.width + ix1];
758        let v01 = self.current[iy1 * self.width + ix];
759        let v11 = self.current[iy1 * self.width + ix1];
760
761        let top = v00 * (1.0 - sx) + v10 * sx;
762        let bottom = v01 * (1.0 - sx) + v11 * sx;
763        top * (1.0 - sy) + bottom * sy
764    }
765
766    /// Compute the normal at a grid point (for rendering the wave as a surface).
767    pub fn normal_at(&self, x: usize, y: usize, scale: f32) -> Vec3 {
768        let h_left = if x > 0 { self.get(x - 1, y) } else { self.get(x, y) };
769        let h_right = if x + 1 < self.width { self.get(x + 1, y) } else { self.get(x, y) };
770        let h_down = if y > 0 { self.get(x, y - 1) } else { self.get(x, y) };
771        let h_up = if y + 1 < self.height { self.get(x, y + 1) } else { self.get(x, y) };
772
773        let dx = (h_right - h_left) * scale;
774        let dy = (h_up - h_down) * scale;
775
776        Vec3::new(-dx, 2.0, -dy).normalize()
777    }
778
779    /// Get the total energy in the system.
780    pub fn total_energy(&self) -> f32 {
781        self.current.iter().map(|&v| v * v).sum::<f32>()
782    }
783
784    /// Convert the wave simulation to a displacement array for a surface mesh.
785    /// Maps grid coordinates to vertex displacement amounts.
786    pub fn to_displacements(&self) -> Vec<f32> {
787        self.current.clone()
788    }
789
790    /// Apply the wave simulation as displacement to a flat grid of positions.
791    pub fn apply_to_grid(&self, positions: &mut [Vec3], grid_width: usize, grid_height: usize) {
792        let scale_x = (self.width - 1) as f32 / (grid_width - 1).max(1) as f32;
793        let scale_y = (self.height - 1) as f32 / (grid_height - 1).max(1) as f32;
794
795        for gy in 0..grid_height {
796            for gx in 0..grid_width {
797                let idx = gy * grid_width + gx;
798                if idx < positions.len() {
799                    let wx = gx as f32 * scale_x;
800                    let wy = gy as f32 * scale_y;
801                    positions[idx].y += self.sample(wx, wy);
802                }
803            }
804        }
805    }
806}
807
808// ─────────────────────────────────────────────────────────────────────────────
809// Keyframe animation of deformation parameters
810// ─────────────────────────────────────────────────────────────────────────────
811
812/// A keyframe value at a specific time.
813#[derive(Debug, Clone, Copy)]
814pub struct DeformKeyframe {
815    pub time: f32,
816    pub value: f32,
817}
818
819impl DeformKeyframe {
820    pub fn new(time: f32, value: f32) -> Self { Self { time, value } }
821}
822
823/// Interpolation mode for keyframes.
824#[derive(Debug, Clone, Copy)]
825pub enum KeyframeInterp {
826    /// Linear interpolation.
827    Linear,
828    /// Smooth step interpolation.
829    Smooth,
830    /// Constant (step function).
831    Step,
832    /// Cubic Hermite interpolation.
833    Cubic,
834}
835
836/// Animates a scalar value over time using keyframes.
837pub struct KeyframeAnimator {
838    pub keyframes: Vec<DeformKeyframe>,
839    pub interpolation: KeyframeInterp,
840    /// Whether the animation loops.
841    pub looping: bool,
842    /// Current time.
843    pub time: f32,
844}
845
846impl KeyframeAnimator {
847    /// Create a new keyframe animator.
848    pub fn new() -> Self {
849        Self {
850            keyframes: Vec::new(),
851            interpolation: KeyframeInterp::Linear,
852            looping: false,
853            time: 0.0,
854        }
855    }
856
857    /// Add a keyframe.
858    pub fn add_key(&mut self, time: f32, value: f32) {
859        self.keyframes.push(DeformKeyframe::new(time, value));
860        self.keyframes.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap_or(std::cmp::Ordering::Equal));
861    }
862
863    /// Set interpolation mode.
864    pub fn with_interpolation(mut self, interp: KeyframeInterp) -> Self {
865        self.interpolation = interp;
866        self
867    }
868
869    /// Enable looping.
870    pub fn with_looping(mut self, looping: bool) -> Self {
871        self.looping = looping;
872        self
873    }
874
875    /// Advance time by dt.
876    pub fn tick(&mut self, dt: f32) {
877        self.time += dt;
878
879        if self.looping && self.keyframes.len() >= 2 {
880            let duration = self.keyframes.last().unwrap().time - self.keyframes.first().unwrap().time;
881            if duration > 0.0 {
882                let start = self.keyframes.first().unwrap().time;
883                self.time = start + ((self.time - start) % duration);
884            }
885        }
886    }
887
888    /// Evaluate the animated value at the current time.
889    pub fn evaluate(&self) -> f32 {
890        self.evaluate_at(self.time)
891    }
892
893    /// Evaluate the animated value at a specific time.
894    pub fn evaluate_at(&self, time: f32) -> f32 {
895        if self.keyframes.is_empty() {
896            return 0.0;
897        }
898        if self.keyframes.len() == 1 {
899            return self.keyframes[0].value;
900        }
901
902        // Find the surrounding keyframes
903        if time <= self.keyframes.first().unwrap().time {
904            return self.keyframes.first().unwrap().value;
905        }
906        if time >= self.keyframes.last().unwrap().time {
907            return self.keyframes.last().unwrap().value;
908        }
909
910        let mut idx = 0;
911        for (i, kf) in self.keyframes.iter().enumerate() {
912            if kf.time > time {
913                idx = i;
914                break;
915            }
916        }
917        if idx == 0 { idx = 1; }
918
919        let kf0 = &self.keyframes[idx - 1];
920        let kf1 = &self.keyframes[idx];
921        let t = (time - kf0.time) / (kf1.time - kf0.time);
922
923        match self.interpolation {
924            KeyframeInterp::Linear => {
925                kf0.value + (kf1.value - kf0.value) * t
926            }
927            KeyframeInterp::Smooth => {
928                let s = t * t * (3.0 - 2.0 * t);
929                kf0.value + (kf1.value - kf0.value) * s
930            }
931            KeyframeInterp::Step => {
932                if t < 0.5 { kf0.value } else { kf1.value }
933            }
934            KeyframeInterp::Cubic => {
935                // Catmull-Rom with clamped endpoints
936                let v0 = if idx >= 2 { self.keyframes[idx - 2].value } else { kf0.value };
937                let v1 = kf0.value;
938                let v2 = kf1.value;
939                let v3 = if idx + 1 < self.keyframes.len() {
940                    self.keyframes[idx + 1].value
941                } else {
942                    kf1.value
943                };
944                catmull_rom(v0, v1, v2, v3, t)
945            }
946        }
947    }
948
949    /// Get the total duration of the animation.
950    pub fn duration(&self) -> f32 {
951        if self.keyframes.len() < 2 {
952            return 0.0;
953        }
954        self.keyframes.last().unwrap().time - self.keyframes.first().unwrap().time
955    }
956
957    /// Check if the animation is finished (time past last keyframe).
958    pub fn is_finished(&self) -> bool {
959        if self.looping { return false; }
960        if self.keyframes.is_empty() { return true; }
961        self.time >= self.keyframes.last().unwrap().time
962    }
963
964    /// Reset animation time to the start.
965    pub fn reset(&mut self) {
966        self.time = if self.keyframes.is_empty() {
967            0.0
968        } else {
969            self.keyframes.first().unwrap().time
970        };
971    }
972}
973
974impl Default for KeyframeAnimator {
975    fn default() -> Self { Self::new() }
976}
977
978/// Animates a Vec3 value over time using keyframes.
979pub struct Vec3KeyframeAnimator {
980    pub x: KeyframeAnimator,
981    pub y: KeyframeAnimator,
982    pub z: KeyframeAnimator,
983}
984
985impl Vec3KeyframeAnimator {
986    pub fn new() -> Self {
987        Self {
988            x: KeyframeAnimator::new(),
989            y: KeyframeAnimator::new(),
990            z: KeyframeAnimator::new(),
991        }
992    }
993
994    pub fn add_key(&mut self, time: f32, value: Vec3) {
995        self.x.add_key(time, value.x);
996        self.y.add_key(time, value.y);
997        self.z.add_key(time, value.z);
998    }
999
1000    pub fn tick(&mut self, dt: f32) {
1001        self.x.tick(dt);
1002        self.y.tick(dt);
1003        self.z.tick(dt);
1004    }
1005
1006    pub fn evaluate(&self) -> Vec3 {
1007        Vec3::new(self.x.evaluate(), self.y.evaluate(), self.z.evaluate())
1008    }
1009
1010    pub fn set_time(&mut self, time: f32) {
1011        self.x.time = time;
1012        self.y.time = time;
1013        self.z.time = time;
1014    }
1015}
1016
1017impl Default for Vec3KeyframeAnimator {
1018    fn default() -> Self { Self::new() }
1019}
1020
1021// ─────────────────────────────────────────────────────────────────────────────
1022// Utility functions
1023// ─────────────────────────────────────────────────────────────────────────────
1024
1025/// Simple 3D value noise for deformation effects.
1026fn simple_noise_3d(p: Vec3) -> f32 {
1027    // Sine-based pseudo-noise (deterministic, no lookup table needed)
1028    let n = p.x * 127.1 + p.y * 311.7 + p.z * 74.7;
1029    (n.sin() * 43758.5453).fract() * 2.0 - 1.0
1030}
1031
1032/// Hash a Vec3 position to produce a pseudo-random u32.
1033fn hash_vec3(p: Vec3, seed: u32) -> u32 {
1034    let x = (p.x * 73.0 + 37.0) as u32;
1035    let y = (p.y * 157.0 + 59.0) as u32;
1036    let z = (p.z * 241.0 + 83.0) as u32;
1037    let mut h = seed;
1038    h = h.wrapping_mul(31).wrapping_add(x);
1039    h = h.wrapping_mul(31).wrapping_add(y);
1040    h = h.wrapping_mul(31).wrapping_add(z);
1041    h ^= h >> 16;
1042    h = h.wrapping_mul(0x45d9f3b);
1043    h ^= h >> 16;
1044    h
1045}
1046
1047/// Catmull-Rom spline interpolation between v1 and v2.
1048fn catmull_rom(v0: f32, v1: f32, v2: f32, v3: f32, t: f32) -> f32 {
1049    let t2 = t * t;
1050    let t3 = t2 * t;
1051    0.5 * ((2.0 * v1)
1052        + (-v0 + v2) * t
1053        + (2.0 * v0 - 5.0 * v1 + 4.0 * v2 - v3) * t2
1054        + (-v0 + 3.0 * v1 - 3.0 * v2 + v3) * t3)
1055}
1056
1057// ─────────────────────────────────────────────────────────────────────────────
1058// Preset deformations
1059// ─────────────────────────────────────────────────────────────────────────────
1060
1061/// Factory methods for common deformation presets.
1062pub struct DeformationPresets;
1063
1064impl DeformationPresets {
1065    /// Gentle breathing animation.
1066    pub fn gentle_breathe() -> Deformation {
1067        Deformation::new(DeformationMode::Breathe {
1068            amplitude: 0.05,
1069            frequency: 0.5,
1070        })
1071    }
1072
1073    /// Ocean-like wave.
1074    pub fn ocean_wave() -> Deformation {
1075        Deformation::new(DeformationMode::Wave {
1076            direction: Vec3::X,
1077            amplitude: 0.3,
1078            wavelength: 5.0,
1079            speed: 1.0,
1080        })
1081    }
1082
1083    /// Slow twist around Y axis.
1084    pub fn slow_twist() -> Deformation {
1085        Deformation::new(DeformationMode::Twist {
1086            axis: Vec3::Y,
1087            strength: 0.5,
1088            falloff_start: -1.0,
1089            falloff_end: 1.0,
1090        })
1091    }
1092
1093    /// Dramatic melt effect.
1094    pub fn melt_down() -> Deformation {
1095        Deformation::new(DeformationMode::Melt {
1096            rate: 1.0,
1097            gravity_dir: Vec3::NEG_Y,
1098            noise_scale: 2.0,
1099        })
1100    }
1101
1102    /// Explosion from center.
1103    pub fn explosion() -> Deformation {
1104        Deformation::new(DeformationMode::Explode {
1105            strength: 2.0,
1106            center: Vec3::ZERO,
1107            noise_scale: 1.0,
1108        })
1109    }
1110
1111    /// Water drop ripple.
1112    pub fn water_ripple(center: Vec3) -> Deformation {
1113        Deformation::new(DeformationMode::Ripple {
1114            center,
1115            amplitude: 0.1,
1116            wavelength: 0.5,
1117            speed: 3.0,
1118            decay: 0.5,
1119        })
1120    }
1121
1122    /// Paper fold.
1123    pub fn paper_fold() -> Deformation {
1124        Deformation::new(DeformationMode::Fold {
1125            plane_point: Vec3::ZERO,
1126            plane_normal: Vec3::X,
1127            angle: PI * 0.5,
1128            sharpness: 5.0,
1129        })
1130    }
1131
1132    /// Glass shatter.
1133    pub fn shatter() -> Deformation {
1134        Deformation::new(DeformationMode::Shatter {
1135            center: Vec3::ZERO,
1136            strength: 3.0,
1137            fragment_seed: 42,
1138            gravity: Vec3::new(0.0, -9.8, 0.0),
1139        })
1140    }
1141
1142    /// Noise displacement (organic wobble).
1143    pub fn organic_wobble() -> Deformation {
1144        Deformation::new(DeformationMode::NoiseDisplace {
1145            amplitude: 0.1,
1146            frequency: 3.0,
1147            speed: 1.0,
1148        })
1149    }
1150
1151    /// Inflate along normals.
1152    pub fn inflate(amount: f32) -> Deformation {
1153        Deformation::new(DeformationMode::Inflate { amount })
1154    }
1155}
1156
1157// ─────────────────────────────────────────────────────────────────────────────
1158// Tests
1159// ─────────────────────────────────────────────────────────────────────────────
1160
1161#[cfg(test)]
1162mod tests {
1163    use super::*;
1164
1165    #[test]
1166    fn breathe_deformation() {
1167        let d = Deformation::new(DeformationMode::Breathe {
1168            amplitude: 1.0,
1169            frequency: 1.0,
1170        });
1171        let pos = Vec3::new(1.0, 0.0, 0.0);
1172        let normal = Vec3::new(1.0, 0.0, 0.0);
1173
1174        let at_zero = d.apply(pos, normal, 0.0);
1175        let at_quarter = d.apply(pos, normal, 0.25);
1176        // At time 0, sin(0) = 0 so no displacement
1177        assert!((at_zero - pos).length() < 1e-3);
1178        // At time 0.25 (TAU * 0.25 = PI/2), sin(PI/2) = 1
1179        assert!((at_quarter - pos).length() > 0.5);
1180    }
1181
1182    #[test]
1183    fn deformation_stack() {
1184        let mut stack = DeformationStack::new();
1185        stack.push(Deformation::new(DeformationMode::Inflate { amount: 1.0 }));
1186        stack.set_time(1.0);
1187
1188        let pos = Vec3::new(1.0, 0.0, 0.0);
1189        let normal = Vec3::new(1.0, 0.0, 0.0);
1190        let result = stack.apply(pos, normal);
1191
1192        assert!((result.x - 2.0).abs() < 1e-3);
1193    }
1194
1195    #[test]
1196    fn morph_target_blend() {
1197        let base = vec![Vec3::ZERO; 4];
1198        let target = vec![Vec3::ONE; 4];
1199        let mut morph = MorphTarget::new(base, target);
1200
1201        morph.set_blend(0.5);
1202        let positions = morph.compute_positions();
1203        assert!((positions[0] - Vec3::splat(0.5)).length() < 1e-5);
1204    }
1205
1206    #[test]
1207    fn morph_target_pingpong() {
1208        let base = vec![Vec3::ZERO; 1];
1209        let target = vec![Vec3::ONE; 1];
1210        let mut morph = MorphTarget::new(base, target)
1211            .with_speed(2.0)
1212            .with_ping_pong(true);
1213
1214        for _ in 0..20 {
1215            morph.tick(0.1);
1216        }
1217        // After 2 seconds at speed 2, should have completed one full cycle
1218        assert!(morph.blend >= 0.0 && morph.blend <= 1.0);
1219    }
1220
1221    #[test]
1222    fn wave_simulation_basic() {
1223        let mut wave = WaveSimulation::new(32, 32, 1.0);
1224        wave.impulse(16.0, 16.0, 3.0, 1.0);
1225        assert!(wave.total_energy() > 0.0);
1226
1227        for _ in 0..10 {
1228            wave.step(0.016);
1229        }
1230        // Energy should still be positive (waves propagating)
1231        assert!(wave.total_energy() > 0.0);
1232    }
1233
1234    #[test]
1235    fn wave_simulation_damping() {
1236        let mut wave = WaveSimulation::new(16, 16, 1.0).with_damping(0.1);
1237        wave.impulse(8.0, 8.0, 2.0, 1.0);
1238        let initial_energy = wave.total_energy();
1239
1240        for _ in 0..100 {
1241            wave.step(0.016);
1242        }
1243        // Energy should decrease due to damping
1244        assert!(wave.total_energy() < initial_energy);
1245    }
1246
1247    #[test]
1248    fn keyframe_animator() {
1249        let mut anim = KeyframeAnimator::new();
1250        anim.add_key(0.0, 0.0);
1251        anim.add_key(1.0, 10.0);
1252        anim.add_key(2.0, 5.0);
1253
1254        anim.time = 0.5;
1255        assert!((anim.evaluate() - 5.0).abs() < 1e-3);
1256
1257        anim.time = 1.0;
1258        assert!((anim.evaluate() - 10.0).abs() < 1e-3);
1259    }
1260
1261    #[test]
1262    fn keyframe_looping() {
1263        let mut anim = KeyframeAnimator::new().with_looping(true);
1264        anim.add_key(0.0, 0.0);
1265        anim.add_key(1.0, 1.0);
1266
1267        anim.time = 0.0;
1268        anim.tick(1.5);
1269        // Should have looped
1270        assert!(anim.time >= 0.0 && anim.time <= 1.0);
1271    }
1272
1273    #[test]
1274    fn falloff_functions() {
1275        let d = Deformation::new(DeformationMode::Inflate { amount: 1.0 })
1276            .with_falloff(Vec3::ZERO, 10.0, FalloffFunction::Linear);
1277
1278        // At center, full effect
1279        let center_falloff = d.compute_falloff(Vec3::ZERO);
1280        assert!((center_falloff - 1.0).abs() < 1e-5);
1281
1282        // At edge, zero effect
1283        let edge_falloff = d.compute_falloff(Vec3::new(10.0, 0.0, 0.0));
1284        assert!(edge_falloff.abs() < 1e-5);
1285
1286        // Outside radius, zero
1287        let outside = d.compute_falloff(Vec3::new(15.0, 0.0, 0.0));
1288        assert!(outside.abs() < 1e-5);
1289    }
1290
1291    #[test]
1292    fn deformation_presets() {
1293        // Just verify they construct without panic
1294        let _ = DeformationPresets::gentle_breathe();
1295        let _ = DeformationPresets::ocean_wave();
1296        let _ = DeformationPresets::slow_twist();
1297        let _ = DeformationPresets::melt_down();
1298        let _ = DeformationPresets::explosion();
1299        let _ = DeformationPresets::water_ripple(Vec3::ZERO);
1300        let _ = DeformationPresets::paper_fold();
1301        let _ = DeformationPresets::shatter();
1302        let _ = DeformationPresets::organic_wobble();
1303        let _ = DeformationPresets::inflate(1.0);
1304    }
1305
1306    #[test]
1307    fn multi_morph() {
1308        let base = vec![Vec3::ZERO; 4];
1309        let mut mm = MultiMorphTarget::new(base);
1310        mm.add_target(vec![Vec3::X; 4]);
1311        mm.add_target(vec![Vec3::Y; 4]);
1312
1313        mm.set_weight(0, 0.5);
1314        mm.set_weight(1, 0.3);
1315
1316        let result = mm.compute_positions();
1317        assert!((result[0].x - 0.5).abs() < 1e-5);
1318        assert!((result[0].y - 0.3).abs() < 1e-5);
1319    }
1320}