Skip to main content

proof_engine/curves/
dissolve.rs

1//! Death dissolution for curve entities.
2//!
3//! On death: stiffness drops to zero, control points scatter,
4//! curves degenerate beautifully, emission fades.
5
6use glam::Vec2;
7use super::entity_curves::{CurveEntity, CurveType};
8use std::f32::consts::TAU;
9
10/// Dissolution state tracker.
11#[derive(Debug, Clone)]
12pub struct DissolveState {
13    pub active: bool,
14    pub elapsed: f32,
15    pub duration: f32,
16    pub attractor_type: DissolveAttractor,
17}
18
19/// What attractor pulls the dissolving particles.
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum DissolveAttractor {
22    None,
23    Lorenz,
24    Rossler,
25    Scatter,
26    Spiral,
27}
28
29impl DissolveState {
30    pub fn new(attractor: DissolveAttractor) -> Self {
31        Self { active: true, elapsed: 0.0, duration: 3.0, attractor_type: attractor }
32    }
33
34    pub fn inactive() -> Self {
35        Self { active: false, elapsed: 0.0, duration: 3.0, attractor_type: DissolveAttractor::None }
36    }
37
38    /// Begin dissolution.
39    pub fn begin(&mut self, attractor: DissolveAttractor) {
40        self.active = true;
41        self.elapsed = 0.0;
42        self.attractor_type = attractor;
43    }
44
45    /// Update dissolution. Returns true when fully dissolved.
46    pub fn update(&mut self, entity: &mut CurveEntity, dt: f32) -> bool {
47        if !self.active { return false; }
48        self.elapsed += dt;
49
50        let t = (self.elapsed / self.duration).min(1.0);
51
52        // Fade emission
53        entity.emission_mult = (1.0 - t).max(0.0);
54
55        // Fade alpha on all curves
56        for curve in &mut entity.curves {
57            curve.color.w = (1.0 - t).max(0.0);
58        }
59
60        // Apply attractor forces to control points
61        for curve in &mut entity.curves {
62            for (i, vel) in curve.point_velocities.iter_mut().enumerate() {
63                let pt = curve.control_points[i];
64                let force = match self.attractor_type {
65                    DissolveAttractor::Lorenz => lorenz_2d(pt, self.elapsed),
66                    DissolveAttractor::Rossler => rossler_2d(pt, self.elapsed),
67                    DissolveAttractor::Spiral => {
68                        let angle = pt.y.atan2(pt.x) + 0.5;
69                        Vec2::new(angle.cos(), angle.sin()) * 0.5
70                    }
71                    DissolveAttractor::Scatter => Vec2::ZERO,
72                    DissolveAttractor::None => Vec2::ZERO,
73                };
74                *vel += force * dt;
75            }
76        }
77
78        // Push Lissajous parameters toward irrational ratios
79        for curve in &mut entity.curves {
80            if let CurveType::Lissajous { ref mut a, ref mut b, ref mut delta } = curve.curve_type {
81                *a += (std::f32::consts::E - *a) * dt * 0.3;
82                *b += (std::f32::consts::PI - *b) * dt * 0.3;
83                *delta += dt * 0.5;
84            }
85        }
86
87        self.elapsed >= self.duration
88    }
89
90    pub fn progress(&self) -> f32 { (self.elapsed / self.duration).min(1.0) }
91    pub fn is_done(&self) -> bool { self.elapsed >= self.duration }
92}
93
94fn lorenz_2d(pos: Vec2, t: f32) -> Vec2 {
95    let sigma = 10.0;
96    let rho = 28.0;
97    let x = pos.x * 0.1;
98    let y = pos.y * 0.1;
99    let dx = sigma * (y - x);
100    let dy = x * (rho - (t * 0.5).sin() * 5.0) - y;
101    Vec2::new(dx, dy) * 0.3
102}
103
104fn rossler_2d(pos: Vec2, t: f32) -> Vec2 {
105    let a = 0.2;
106    let x = pos.x * 0.1;
107    let y = pos.y * 0.1;
108    let z = (t * 0.3).sin() * 3.0;
109    let dx = -y - z;
110    let dy = x + a * y;
111    Vec2::new(dx, dy) * 0.4
112}