1use glam::{Vec2, Vec3, Vec4};
8use crate::math::MathFunction;
9
10#[derive(Debug, Clone)]
16pub enum CurveType {
17 Bezier { degree: u32 },
19 Lissajous { a: f32, b: f32, delta: f32 },
21 Parametric { x_fn: MathFunction, y_fn: MathFunction },
23 Circle { radius: f32, distortion: Option<MathFunction> },
25 Spiral { rate: f32, decay: f32 },
27 Rose { k: f32, amplitude: f32 },
29 Hypotrochoid { big_r: f32, small_r: f32, d: f32 },
31 Superellipse { a: f32, b: f32, n: f32 },
33 Catenary { a: f32, span: f32 },
35}
36
37#[derive(Debug, Clone)]
43pub struct EntityCurve {
44 pub curve_type: CurveType,
46 pub control_points: Vec<Vec2>,
50 pub base_points: Vec<Vec2>,
52 pub color: Vec4,
54 pub emission: f32,
56 pub thickness: f32,
58 pub stiffness: f32,
60 pub segment_count: u32,
62 pub alive: bool,
64 pub point_velocities: Vec<Vec2>,
66 pub closed: bool,
68 pub dash_pattern: Option<(f32, f32)>,
70 pub layer: u32,
72}
73
74impl EntityCurve {
75 pub fn new(curve_type: CurveType, control_points: Vec<Vec2>) -> Self {
77 let n = control_points.len();
78 Self {
79 base_points: control_points.clone(),
80 control_points,
81 curve_type,
82 color: Vec4::new(0.5, 0.7, 1.0, 0.9),
83 emission: 1.0,
84 thickness: 0.03,
85 stiffness: 1.0,
86 segment_count: 64,
87 alive: true,
88 point_velocities: vec![Vec2::ZERO; n],
89 closed: false,
90 dash_pattern: None,
91 layer: 0,
92 }
93 }
94
95 pub fn with_color(mut self, color: Vec4) -> Self { self.color = color; self }
96 pub fn with_emission(mut self, e: f32) -> Self { self.emission = e; self }
97 pub fn with_thickness(mut self, t: f32) -> Self { self.thickness = t; self }
98 pub fn with_stiffness(mut self, s: f32) -> Self { self.stiffness = s; self }
99 pub fn with_segments(mut self, n: u32) -> Self { self.segment_count = n; self }
100 pub fn with_closed(mut self, c: bool) -> Self { self.closed = c; self }
101 pub fn with_dash(mut self, on: f32, off: f32) -> Self { self.dash_pattern = Some((on, off)); self }
102 pub fn with_layer(mut self, l: u32) -> Self { self.layer = l; self }
103
104 pub fn apply_force(&mut self, force: Vec2) {
107 let inv_stiff = 1.0 / self.stiffness.max(0.01);
108 for vel in &mut self.point_velocities {
109 *vel += force * inv_stiff;
110 }
111 }
112
113 pub fn apply_hit_recoil(&mut self, direction: Vec2, magnitude: f32) {
116 let dir = direction.normalize_or_zero();
117 let inv_stiff = 1.0 / self.stiffness.max(0.01);
118 for (i, pt) in self.control_points.iter().enumerate() {
119 let facing = (pt.normalize_or_zero()).dot(dir);
120 let response = if facing > 0.0 {
121 dir * magnitude * (0.5 + facing * 0.5) * inv_stiff
122 } else {
123 dir * magnitude * 0.2 * inv_stiff
124 };
125 self.point_velocities[i] += response;
126 }
127 }
128
129 pub fn step_physics(&mut self, dt: f32, damping: f32) {
131 for i in 0..self.control_points.len() {
132 let to_base = self.base_points[i] - self.control_points[i];
134 let spring_force = to_base * self.stiffness * 5.0;
135 self.point_velocities[i] += spring_force * dt;
136
137 self.point_velocities[i] *= damping;
139
140 self.control_points[i] += self.point_velocities[i] * dt;
142 }
143 }
144
145 pub fn break_curve(&mut self) {
147 self.alive = false;
148 self.stiffness = 0.0;
149 }
150
151 pub fn kinetic_energy(&self) -> f32 {
153 self.point_velocities.iter().map(|v| v.length_squared()).sum::<f32>() * 0.5
154 }
155}
156
157#[derive(Debug, Clone)]
163pub struct CurveEntity {
164 pub curves: Vec<EntityCurve>,
166 pub position: Vec3,
168 pub center_of_mass: Vec2,
170 pub hp_ratio: f32,
172 pub breath_phase: f32,
174 pub breath_rate: f32,
176 pub breath_amplitude: f32,
178 pub force_response: Vec2,
180 pub name: String,
182 pub emission_mult: f32,
184 pub damping: f32,
186 pub alive: bool,
188 pub death_time: f32,
190 pub time: f32,
192 pub id: u32,
194}
195
196impl CurveEntity {
197 pub fn new(name: &str, position: Vec3) -> Self {
198 Self {
199 curves: Vec::new(),
200 position,
201 center_of_mass: Vec2::ZERO,
202 hp_ratio: 1.0,
203 breath_phase: 0.0,
204 breath_rate: 0.5,
205 breath_amplitude: 0.03,
206 force_response: Vec2::ZERO,
207 name: name.to_string(),
208 emission_mult: 1.0,
209 damping: 0.92,
210 alive: true,
211 death_time: 0.0,
212 time: 0.0,
213 id: 0,
214 }
215 }
216
217 pub fn add_curve(&mut self, curve: EntityCurve) {
219 self.curves.push(curve);
220 }
221
222 pub fn tick(&mut self, dt: f32) {
224 self.time += dt;
225 self.breath_phase += dt * self.breath_rate * std::f32::consts::TAU;
226
227 let breath_scale = 1.0 + self.breath_phase.sin() * self.breath_amplitude;
229 for curve in &mut self.curves {
230 for (i, pt) in curve.control_points.iter_mut().enumerate() {
231 let base = curve.base_points[i];
232 let breathed = base * breath_scale;
233 *pt += (breathed - *pt) * 0.1;
235 }
236 }
237
238 if self.hp_ratio < 1.0 {
240 let noise_amp = (1.0 - self.hp_ratio) * 0.15;
241 for curve in &mut self.curves {
242 for (i, pt) in curve.control_points.iter_mut().enumerate() {
243 let noise_x = simple_noise(self.time * 3.0 + i as f32 * 1.618) * noise_amp;
244 let noise_y = simple_noise(self.time * 2.7 + i as f32 * 2.718) * noise_amp;
245 pt.x += noise_x;
246 pt.y += noise_y;
247 }
248 }
249 }
250
251 for curve in &mut self.curves {
253 curve.step_physics(dt, self.damping);
254 }
255
256 self.update_center_of_mass();
258
259 self.force_response *= 0.9;
261
262 if !self.alive {
264 self.death_time += dt;
265 }
266 }
267
268 pub fn update_center_of_mass(&mut self) {
270 let mut sum = Vec2::ZERO;
271 let mut count = 0u32;
272 for curve in &self.curves {
273 for pt in &curve.control_points {
274 sum += *pt;
275 count += 1;
276 }
277 }
278 if count > 0 {
279 self.center_of_mass = sum / count as f32;
280 }
281 }
282
283 pub fn apply_hit(&mut self, direction: Vec2, damage: f32) {
285 self.hp_ratio = (self.hp_ratio - damage / 100.0).max(0.0);
286 self.force_response += direction * damage * 0.01;
287 for curve in &mut self.curves {
288 curve.apply_hit_recoil(direction, damage * 0.5);
289 }
290 if self.hp_ratio <= 0.0 {
291 self.alive = false;
292 }
293 }
294
295 pub fn set_hp(&mut self, hp_ratio: f32) {
297 self.hp_ratio = hp_ratio.clamp(0.0, 1.0);
298 self.emission_mult = 0.3 + hp_ratio * 0.7;
299 if hp_ratio <= 0.0 { self.alive = false; }
300 }
301
302 pub fn brace(&mut self, multiplier: f32) {
304 for curve in &mut self.curves {
305 curve.stiffness *= multiplier;
306 }
307 }
308
309 pub fn unbrace(&mut self, multiplier: f32) {
311 for curve in &mut self.curves {
312 curve.stiffness /= multiplier;
313 }
314 }
315
316 pub fn break_random_curve(&mut self, rng_seed: u32) {
318 let alive_curves: Vec<usize> = self.curves.iter().enumerate()
319 .filter(|(_, c)| c.alive)
320 .map(|(i, _)| i)
321 .collect();
322 if let Some(&idx) = alive_curves.get(rng_seed as usize % alive_curves.len().max(1)) {
323 self.curves[idx].break_curve();
324 }
325 }
326
327 pub fn die(&mut self) {
329 self.alive = false;
330 self.death_time = 0.0;
331 for curve in &mut self.curves {
332 curve.stiffness = 0.0;
333 for (i, vel) in curve.point_velocities.iter_mut().enumerate() {
335 let angle = (i as f32 * 2.399) + self.time; *vel += Vec2::new(angle.cos(), angle.sin()) * 2.0;
337 }
338 }
339 }
340
341 pub fn is_dissolved(&self) -> bool {
343 !self.alive && self.death_time > 3.0
344 }
345
346 pub fn bounding_box(&self) -> (Vec2, Vec2) {
348 let mut min = Vec2::splat(f32::MAX);
349 let mut max = Vec2::splat(f32::MIN);
350 for curve in &self.curves {
351 for pt in &curve.control_points {
352 min = min.min(*pt);
353 max = max.max(*pt);
354 }
355 }
356 (min, max)
357 }
358
359 pub fn alive_curve_count(&self) -> usize {
361 self.curves.iter().filter(|c| c.alive).count()
362 }
363
364 pub fn total_control_points(&self) -> usize {
366 self.curves.iter().map(|c| c.control_points.len()).sum()
367 }
368}
369
370fn simple_noise(x: f32) -> f32 {
375 let xi = x.floor() as i32;
376 let xf = x - x.floor();
377 let t = xf * xf * (3.0 - 2.0 * xf);
378 let a = hash_f(xi);
379 let b = hash_f(xi + 1);
380 a + (b - a) * t
381}
382
383fn hash_f(n: i32) -> f32 {
384 let n = (n as u32).wrapping_mul(0x9E3779B9);
385 let n = n ^ (n >> 16);
386 let n = n.wrapping_mul(0x85EBCA6B);
387 (n & 0x00FF_FFFF) as f32 / 0x0080_0000 as f32 - 1.0
388}
389
390#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn test_curve_entity_creation() {
400 let mut ent = CurveEntity::new("test", Vec3::ZERO);
401 let curve = EntityCurve::new(
402 CurveType::Circle { radius: 1.0, distortion: None },
403 vec![Vec2::ZERO],
404 );
405 ent.add_curve(curve);
406 assert_eq!(ent.curves.len(), 1);
407 assert!(ent.alive);
408 }
409
410 #[test]
411 fn test_hit_recoil() {
412 let mut ent = CurveEntity::new("test", Vec3::ZERO);
413 let curve = EntityCurve::new(
414 CurveType::Bezier { degree: 3 },
415 vec![Vec2::ZERO, Vec2::new(1.0, 0.0), Vec2::new(1.0, 1.0), Vec2::new(0.0, 1.0)],
416 );
417 ent.add_curve(curve);
418 ent.apply_hit(Vec2::new(1.0, 0.0), 30.0);
419 assert!(ent.hp_ratio < 1.0);
420 assert!(ent.curves[0].point_velocities.iter().any(|v| v.length() > 0.0));
422 }
423
424 #[test]
425 fn test_death_dissolution() {
426 let mut ent = CurveEntity::new("test", Vec3::ZERO);
427 ent.add_curve(EntityCurve::new(CurveType::Circle { radius: 1.0, distortion: None }, vec![Vec2::ZERO]));
428 ent.die();
429 assert!(!ent.alive);
430 for _ in 0..200 { ent.tick(1.0 / 60.0); }
431 assert!(ent.is_dissolved());
432 }
433
434 #[test]
435 fn test_bounding_box() {
436 let mut ent = CurveEntity::new("test", Vec3::ZERO);
437 ent.add_curve(EntityCurve::new(
438 CurveType::Bezier { degree: 2 },
439 vec![Vec2::new(-1.0, -1.0), Vec2::new(1.0, 1.0)],
440 ));
441 let (min, max) = ent.bounding_box();
442 assert!(min.x <= -1.0 && min.y <= -1.0);
443 assert!(max.x >= 1.0 && max.y >= 1.0);
444 }
445}