1use glam::Vec3;
12
13#[derive(Debug, Clone)]
19pub struct VerletPoint {
20 pub position: Vec3,
21 pub old_position: Vec3,
22 pub acceleration: Vec3,
23 pub pinned: bool,
24 pub mass: f32,
25}
26
27impl VerletPoint {
28 pub fn new(position: Vec3, mass: f32) -> Self {
29 Self {
30 position,
31 old_position: position,
32 acceleration: Vec3::ZERO,
33 pinned: false,
34 mass,
35 }
36 }
37
38 pub fn integrate(&mut self, dt: f32) {
40 if self.pinned {
41 return;
42 }
43 let velocity = self.position - self.old_position;
44 self.old_position = self.position;
45 self.position += velocity * 0.999 + self.acceleration * dt * dt;
48 self.acceleration = Vec3::ZERO;
49 }
50
51 pub fn apply_force(&mut self, force: Vec3) {
52 if !self.pinned && self.mass > 0.0 {
53 self.acceleration += force / self.mass;
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
64pub struct DistanceConstraint {
65 pub a: usize,
66 pub b: usize,
67 pub rest_length: f32,
68 pub active: bool,
69}
70
71impl DistanceConstraint {
72 pub fn new(a: usize, b: usize, rest_length: f32) -> Self {
73 Self {
74 a,
75 b,
76 rest_length,
77 active: true,
78 }
79 }
80
81 pub fn satisfy(&self, points: &mut [VerletPoint]) {
83 if !self.active {
84 return;
85 }
86 let pa = points[self.a].position;
87 let pb = points[self.b].position;
88 let delta = pb - pa;
89 let dist = delta.length();
90 if dist < 1e-8 {
91 return;
92 }
93 let diff = (dist - self.rest_length) / dist;
94
95 let pinned_a = points[self.a].pinned;
96 let pinned_b = points[self.b].pinned;
97
98 if pinned_a && pinned_b {
99 return;
100 } else if pinned_a {
101 points[self.b].position -= delta * diff;
102 } else if pinned_b {
103 points[self.a].position += delta * diff;
104 } else {
105 let half = delta * diff * 0.5;
106 points[self.a].position += half;
107 points[self.b].position -= half;
108 }
109 }
110}
111
112#[derive(Debug, Clone)]
119pub struct ClothStrip {
120 pub points: Vec<VerletPoint>,
121 pub structural_constraints: Vec<DistanceConstraint>,
122 pub bend_constraints: Vec<DistanceConstraint>,
123 pub width: usize,
124 pub height: usize,
125 pub spacing: f32,
126 pub lifetime: f32,
128 pub age: f32,
129}
130
131impl ClothStrip {
132 pub fn new(width_points: usize, height_points: usize, spacing: f32, anchor_pos: Vec3) -> Self {
135 let mut points = Vec::with_capacity(width_points * height_points);
136 for row in 0..height_points {
137 for col in 0..width_points {
138 let pos = anchor_pos
139 + Vec3::new(col as f32 * spacing, -(row as f32) * spacing, 0.0);
140 points.push(VerletPoint::new(pos, 1.0));
141 }
142 }
143
144 let idx = |r: usize, c: usize| r * width_points + c;
145
146 let mut structural = Vec::new();
148 for r in 0..height_points {
149 for c in 0..width_points {
150 if c + 1 < width_points {
151 structural.push(DistanceConstraint::new(idx(r, c), idx(r, c + 1), spacing));
152 }
153 if r + 1 < height_points {
154 structural.push(DistanceConstraint::new(idx(r, c), idx(r + 1, c), spacing));
155 }
156 if c + 1 < width_points && r + 1 < height_points {
158 let diag = spacing * std::f32::consts::SQRT_2;
159 structural
160 .push(DistanceConstraint::new(idx(r, c), idx(r + 1, c + 1), diag));
161 structural
162 .push(DistanceConstraint::new(idx(r, c + 1), idx(r + 1, c), diag));
163 }
164 }
165 }
166
167 let mut bend = Vec::new();
169 for r in 0..height_points {
170 for c in 0..width_points {
171 if c + 2 < width_points {
172 bend.push(DistanceConstraint::new(
173 idx(r, c),
174 idx(r, c + 2),
175 spacing * 2.0,
176 ));
177 }
178 if r + 2 < height_points {
179 bend.push(DistanceConstraint::new(
180 idx(r, c),
181 idx(r + 2, c),
182 spacing * 2.0,
183 ));
184 }
185 }
186 }
187
188 Self {
189 points,
190 structural_constraints: structural,
191 bend_constraints: bend,
192 width: width_points,
193 height: height_points,
194 spacing,
195 lifetime: 0.0,
196 age: 0.0,
197 }
198 }
199
200 pub fn step(&mut self, dt: f32, iterations: usize) {
202 self.age += dt;
203 for p in &mut self.points {
205 p.integrate(dt);
206 }
207 for _ in 0..iterations {
209 for c in &self.structural_constraints {
210 c.satisfy(&mut self.points);
211 }
212 for c in &self.bend_constraints {
213 c.satisfy(&mut self.points);
214 }
215 }
216 }
217
218 pub fn apply_force(&mut self, force: Vec3) {
220 for p in &mut self.points {
221 p.apply_force(force);
222 }
223 }
224
225 pub fn apply_wind(&mut self, direction: Vec3, strength: f32, turbulence: f32) {
227 for (i, p) in self.points.iter_mut().enumerate() {
228 let t = (i as f32 * 0.37).sin() * turbulence;
230 let wind = direction * (strength + t);
231 p.apply_force(wind);
232 }
233 }
234
235 pub fn pin_point(&mut self, index: usize) {
237 if let Some(p) = self.points.get_mut(index) {
238 p.pinned = true;
239 }
240 }
241
242 pub fn unpin_point(&mut self, index: usize) {
244 if let Some(p) = self.points.get_mut(index) {
245 p.pinned = false;
246 }
247 }
248
249 pub fn tear_at(&mut self, index: usize) {
251 for c in &mut self.structural_constraints {
252 if c.a == index || c.b == index {
253 c.active = false;
254 }
255 }
256 for c in &mut self.bend_constraints {
257 if c.a == index || c.b == index {
258 c.active = false;
259 }
260 }
261 }
262
263 pub fn get_render_data(&self) -> Vec<[f32; 3]> {
265 self.points
266 .iter()
267 .map(|p| [p.position.x, p.position.y, p.position.z])
268 .collect()
269 }
270
271 pub fn set_point_position(&mut self, index: usize, pos: Vec3) {
273 if let Some(p) = self.points.get_mut(index) {
274 p.position = pos;
275 p.old_position = pos;
276 }
277 }
278
279 pub fn is_expired(&self) -> bool {
281 self.lifetime > 0.0 && self.age >= self.lifetime
282 }
283
284 pub fn active_constraint_count(&self) -> usize {
286 self.structural_constraints.iter().filter(|c| c.active).count()
287 + self.bend_constraints.iter().filter(|c| c.active).count()
288 }
289}
290
291#[derive(Debug, Clone)]
297pub struct RopeChain {
298 pub points: Vec<VerletPoint>,
299 pub constraints: Vec<DistanceConstraint>,
300 pub lifetime: f32,
302 pub age: f32,
303}
304
305impl RopeChain {
306 pub fn new(start: Vec3, end: Vec3, segments: usize) -> Self {
308 let seg_count = segments.max(1);
309 let mut points = Vec::with_capacity(seg_count + 1);
310 for i in 0..=seg_count {
311 let t = i as f32 / seg_count as f32;
312 let pos = start.lerp(end, t);
313 points.push(VerletPoint::new(pos, 1.0));
314 }
315
316 let seg_length = (end - start).length() / seg_count as f32;
317 let mut constraints = Vec::with_capacity(seg_count);
318 for i in 0..seg_count {
319 constraints.push(DistanceConstraint::new(i, i + 1, seg_length));
320 }
321
322 Self {
323 points,
324 constraints,
325 lifetime: 0.0,
326 age: 0.0,
327 }
328 }
329
330 pub fn step(&mut self, dt: f32) {
332 self.age += dt;
333 self.apply_gravity();
334 for p in &mut self.points {
335 p.integrate(dt);
336 }
337 for _ in 0..8 {
339 for c in &self.constraints {
340 c.satisfy(&mut self.points);
341 }
342 }
343 }
344
345 pub fn attach_start(&mut self, pos: Vec3) {
347 if let Some(p) = self.points.first_mut() {
348 p.pinned = true;
349 p.position = pos;
350 p.old_position = pos;
351 }
352 }
353
354 pub fn attach_end(&mut self, pos: Vec3) {
356 if let Some(p) = self.points.last_mut() {
357 p.pinned = true;
358 p.position = pos;
359 p.old_position = pos;
360 }
361 }
362
363 pub fn apply_gravity(&mut self) {
365 let gravity = Vec3::new(0.0, -9.81, 0.0);
366 for p in &mut self.points {
367 p.apply_force(gravity * p.mass);
368 }
369 }
370
371 pub fn sever_at(&mut self, segment_index: usize) -> Option<RopeChain> {
375 if segment_index >= self.constraints.len() || self.constraints.is_empty() {
376 return None;
377 }
378 self.constraints[segment_index].active = false;
380
381 let split_point = segment_index + 1;
383 if split_point >= self.points.len() {
384 return None;
385 }
386
387 let new_points: Vec<VerletPoint> = self.points[split_point..].to_vec();
388 if new_points.len() < 2 {
389 return None;
390 }
391
392 let mut new_constraints = Vec::new();
393 for i in 0..new_points.len() - 1 {
394 let rest = new_points[i]
395 .position
396 .distance(new_points[i + 1].position)
397 .max(0.01);
398 new_constraints.push(DistanceConstraint::new(i, i + 1, rest));
399 }
400
401 self.points.truncate(split_point + 1);
403 self.constraints.truncate(segment_index);
404
405 Some(RopeChain {
406 points: new_points,
407 constraints: new_constraints,
408 lifetime: self.lifetime,
409 age: self.age,
410 })
411 }
412
413 pub fn get_points(&self) -> Vec<Vec3> {
415 self.points.iter().map(|p| p.position).collect()
416 }
417
418 pub fn is_expired(&self) -> bool {
420 self.lifetime > 0.0 && self.age >= self.lifetime
421 }
422
423 pub fn current_length(&self) -> f32 {
425 let mut total = 0.0;
426 for i in 0..self.points.len().saturating_sub(1) {
427 total += self.points[i].position.distance(self.points[i + 1].position);
428 }
429 total
430 }
431
432 pub fn apply_force(&mut self, force: Vec3) {
434 for p in &mut self.points {
435 p.apply_force(force);
436 }
437 }
438}
439
440#[derive(Debug, Clone)]
447pub struct SoftBodyBlob {
448 pub points: Vec<VerletPoint>,
449 pub constraints: Vec<DistanceConstraint>,
450 pub center_index: usize,
451 pub perimeter_count: usize,
452 pub spring_stiffness: f32,
453 pub shape_a: Vec<Vec3>,
455 pub shape_b: Vec<Vec3>,
456 pub morph_frequency: f32,
457 pub morph_time: f32,
458 pub morphing: bool,
459 pub lifetime: f32,
461 pub age: f32,
462}
463
464impl SoftBodyBlob {
465 pub fn new(center: Vec3, radius: f32, resolution: usize) -> Self {
468 let n = resolution.max(4);
469 let tau = std::f32::consts::TAU;
470
471 let mut points = Vec::with_capacity(n + 1);
472 for i in 0..n {
474 let angle = i as f32 / n as f32 * tau;
475 let pos = center + Vec3::new(angle.cos() * radius, angle.sin() * radius, 0.0);
476 points.push(VerletPoint::new(pos, 1.0));
477 }
478 points.push(VerletPoint::new(center, 2.0));
480 let center_idx = n;
481
482 let mut constraints = Vec::new();
483 let default_stiffness_rest = radius; let arc_len = tau * radius / n as f32;
487 for i in 0..n {
488 let j = (i + 1) % n;
489 constraints.push(DistanceConstraint::new(i, j, arc_len));
490 }
491
492 for i in 0..n {
494 constraints.push(DistanceConstraint::new(i, center_idx, default_stiffness_rest));
495 }
496
497 for i in 0..n {
499 let j = (i + 2) % n;
500 let dist = points[i].position.distance(points[j].position);
501 constraints.push(DistanceConstraint::new(i, j, dist));
502 }
503
504 let shape_a: Vec<Vec3> = points.iter().map(|p| p.position - center).collect();
506
507 Self {
508 points,
509 constraints,
510 center_index: center_idx,
511 perimeter_count: n,
512 spring_stiffness: 1.0,
513 shape_a: shape_a.clone(),
514 shape_b: shape_a,
515 morph_frequency: 0.0,
516 morph_time: 0.0,
517 morphing: false,
518 lifetime: 0.0,
519 age: 0.0,
520 }
521 }
522
523 pub fn step(&mut self, dt: f32) {
525 self.age += dt;
526 self.morph_time += dt;
527
528 let gravity = Vec3::new(0.0, -9.81, 0.0) * 0.5;
530 for p in &mut self.points {
531 p.apply_force(gravity * p.mass);
532 }
533
534 if self.morphing && self.morph_frequency > 0.0 {
536 let t = (self.morph_time * self.morph_frequency * std::f32::consts::TAU).sin() * 0.5
537 + 0.5;
538 let center_pos = self.points[self.center_index].position;
539 for i in 0..self.points.len() {
540 if i < self.shape_a.len() && i < self.shape_b.len() {
541 let target_offset = self.shape_a[i].lerp(self.shape_b[i], t);
542 let target = center_pos + target_offset;
543 let diff = target - self.points[i].position;
544 self.points[i].apply_force(diff * self.spring_stiffness * 50.0);
545 }
546 }
547 }
548
549 for p in &mut self.points {
550 p.integrate(dt);
551 }
552
553 let iters = 6;
555 for _ in 0..iters {
556 for c in &self.constraints {
557 c.satisfy(&mut self.points);
558 }
559 }
560 }
561
562 pub fn apply_hit(&mut self, direction: Vec3, force: f32) {
564 let dir = if direction.length_squared() > 1e-8 {
565 direction.normalize()
566 } else {
567 Vec3::X
568 };
569 let center_pos = self.points[self.center_index].position;
570 for p in &mut self.points {
571 if p.pinned {
572 continue;
573 }
574 let to_point = p.position - center_pos;
575 let alignment = to_point.normalize_or_zero().dot(dir).max(0.0);
577 p.apply_force(dir * force * (0.3 + alignment * 0.7));
578 }
579 }
580
581 pub fn get_hull(&self) -> Vec<Vec3> {
583 self.points[..self.perimeter_count]
584 .iter()
585 .map(|p| p.position)
586 .collect()
587 }
588
589 pub fn oscillate_between(&mut self, shape_a: Vec<Vec3>, shape_b: Vec<Vec3>, frequency: f32) {
592 self.shape_a = shape_a;
593 self.shape_b = shape_b;
594 self.morph_frequency = frequency;
595 self.morph_time = 0.0;
596 self.morphing = true;
597 }
598
599 pub fn set_stiffness(&mut self, stiffness: f32) {
601 self.spring_stiffness = stiffness.max(0.01);
602 }
603
604 pub fn is_expired(&self) -> bool {
606 self.lifetime > 0.0 && self.age >= self.lifetime
607 }
608
609 pub fn center_position(&self) -> Vec3 {
611 self.points[self.center_index].position
612 }
613}
614
615#[derive(Debug, Clone)]
626pub struct BossCape {
627 pub cloth: ClothStrip,
628 pub anchor_count: usize,
630 pub anchor_offsets: Vec<Vec3>,
632 pub boss_position: Vec3,
634 pub prev_boss_position: Vec3,
636}
637
638impl BossCape {
639 pub fn new(boss_pos: Vec3, width: usize, height: usize, spacing: f32) -> Self {
642 let mut cloth = ClothStrip::new(width, height, spacing, boss_pos);
643
644 let mut anchor_offsets = Vec::with_capacity(width);
646 for c in 0..width {
647 cloth.pin_point(c);
648 anchor_offsets.push(Vec3::new(c as f32 * spacing, 0.0, 0.0));
649 }
650
651 Self {
652 cloth,
653 anchor_count: width,
654 anchor_offsets,
655 boss_position: boss_pos,
656 prev_boss_position: boss_pos,
657 }
658 }
659
660 pub fn update(&mut self, new_boss_pos: Vec3, dt: f32) {
662 self.prev_boss_position = self.boss_position;
663 self.boss_position = new_boss_pos;
664
665 for (i, offset) in self.anchor_offsets.iter().enumerate() {
667 self.cloth
668 .set_point_position(i, self.boss_position + *offset);
669 }
670
671 let move_delta = self.boss_position - self.prev_boss_position;
673 if move_delta.length_squared() > 1e-6 {
674 let sway = -move_delta.normalize() * move_delta.length() * 20.0;
675 self.cloth.apply_force(sway);
676 }
677
678 self.cloth
680 .apply_force(Vec3::new(0.0, -9.81, 0.0));
681
682 self.cloth.step(dt, 5);
683 }
684
685 pub fn apply_vortex(&mut self, origin: Vec3, strength: f32, radius: f32) {
688 for p in &mut self.cloth.points {
689 if p.pinned {
690 continue;
691 }
692 let to_point = p.position - origin;
693 let dist = to_point.length();
694 if dist < radius && dist > 1e-4 {
695 let falloff = 1.0 - dist / radius;
696 let tangent = Vec3::new(-to_point.y, to_point.x, 0.0).normalize_or_zero();
698 p.apply_force(tangent * strength * falloff);
699 }
700 }
701 }
702
703 pub fn get_render_data(&self) -> Vec<[f32; 3]> {
705 self.cloth.get_render_data()
706 }
707}
708
709#[derive(Debug, Clone)]
716pub struct HydraTendril {
717 pub rope: RopeChain,
718 pub health: f32,
720 pub max_health: f32,
721 pub severed: bool,
723 pub endpoint_a: Vec3,
725 pub endpoint_b: Vec3,
727}
728
729impl HydraTendril {
730 pub fn new(pos_a: Vec3, pos_b: Vec3, segments: usize, health: f32) -> Self {
731 let mut rope = RopeChain::new(pos_a, pos_b, segments);
732 rope.attach_start(pos_a);
733 rope.attach_end(pos_b);
734
735 Self {
736 rope,
737 health,
738 max_health: health,
739 severed: false,
740 endpoint_a: pos_a,
741 endpoint_b: pos_b,
742 }
743 }
744
745 pub fn update(&mut self, pos_a: Vec3, pos_b: Vec3, dt: f32) {
747 if self.severed {
748 self.rope.step(dt);
749 return;
750 }
751
752 self.endpoint_a = pos_a;
753 self.endpoint_b = pos_b;
754 self.rope.attach_start(pos_a);
755 self.rope.attach_end(pos_b);
756 self.rope.step(dt);
757 }
758
759 pub fn damage(&mut self, amount: f32) -> bool {
761 if self.severed {
762 return true;
763 }
764 self.health = (self.health - amount).max(0.0);
765 if self.health <= 0.0 {
766 self.sever();
767 return true;
768 }
769 false
770 }
771
772 fn sever(&mut self) {
774 self.severed = true;
775 let mid = self.rope.constraints.len() / 2;
776 let _ = self.rope.sever_at(mid);
777 if let Some(p) = self.rope.points.first_mut() {
779 p.pinned = false;
780 }
781 if let Some(p) = self.rope.points.last_mut() {
782 p.pinned = false;
783 }
784 }
785
786 pub fn get_points(&self) -> Vec<Vec3> {
787 self.rope.get_points()
788 }
789
790 pub fn is_alive(&self) -> bool {
791 !self.severed
792 }
793}
794
795#[derive(Debug, Clone)]
801pub struct PlayerRobe {
802 pub strips: Vec<ClothStrip>,
803 pub attachment_offsets: Vec<Vec3>,
805 pub player_position: Vec3,
806 pub prev_player_position: Vec3,
807}
808
809impl PlayerRobe {
810 pub fn new(
813 player_pos: Vec3,
814 strip_count: usize,
815 strip_width: usize,
816 strip_height: usize,
817 spacing: f32,
818 ) -> Self {
819 let count = strip_count.clamp(1, 4);
820 let mut strips = Vec::with_capacity(count);
821 let mut offsets = Vec::with_capacity(count);
822
823 let spread = spacing * strip_width as f32;
825 for i in 0..count {
826 let x_off = (i as f32 - (count as f32 - 1.0) * 0.5) * spread;
827 let offset = Vec3::new(x_off, 0.0, 0.0);
828 let anchor = player_pos + offset;
829 let mut strip = ClothStrip::new(strip_width, strip_height, spacing, anchor);
830 for c in 0..strip_width {
832 strip.pin_point(c);
833 }
834 strips.push(strip);
835 offsets.push(offset);
836 }
837
838 Self {
839 strips,
840 attachment_offsets: offsets,
841 player_position: player_pos,
842 prev_player_position: player_pos,
843 }
844 }
845
846 pub fn update(&mut self, new_pos: Vec3, dt: f32) {
848 self.prev_player_position = self.player_position;
849 self.player_position = new_pos;
850
851 let move_delta = self.player_position - self.prev_player_position;
852
853 for (idx, strip) in self.strips.iter_mut().enumerate() {
854 let base = self.player_position + self.attachment_offsets[idx];
856 for c in 0..strip.width {
857 strip.set_point_position(c, base + Vec3::new(c as f32 * strip.spacing, 0.0, 0.0));
858 }
859
860 if move_delta.length_squared() > 1e-6 {
862 let sway = -move_delta.normalize() * move_delta.length() * 15.0;
863 strip.apply_force(sway);
864 }
865
866 strip.apply_force(Vec3::new(0.0, -5.0, 0.0));
867 strip.step(dt, 4);
868 }
869 }
870
871 pub fn get_render_data(&self) -> Vec<Vec<[f32; 3]>> {
873 self.strips.iter().map(|s| s.get_render_data()).collect()
874 }
875}
876
877#[derive(Debug, Clone)]
885pub struct NecroSoulChain {
886 pub rope: RopeChain,
887 pub drain_progress: f32,
889 pub drain_rate: f32,
891 pub max_distance: f32,
893 pub broken: bool,
895 pub particle_positions: Vec<f32>,
897 pub particle_speed: f32,
899}
900
901impl NecroSoulChain {
902 pub fn new(
903 necro_pos: Vec3,
904 enemy_pos: Vec3,
905 segments: usize,
906 max_distance: f32,
907 drain_rate: f32,
908 ) -> Self {
909 let mut rope = RopeChain::new(necro_pos, enemy_pos, segments);
910 rope.attach_start(necro_pos);
911
912 let particle_count = 5;
914 let particle_positions = (0..particle_count)
915 .map(|i| i as f32 / particle_count as f32)
916 .collect();
917
918 Self {
919 rope,
920 drain_progress: 0.0,
921 drain_rate,
922 max_distance,
923 broken: false,
924 particle_positions,
925 particle_speed: 1.5,
926 }
927 }
928
929 pub fn update(&mut self, necro_pos: Vec3, enemy_pos: Vec3, dt: f32) {
931 if self.broken {
932 return;
933 }
934
935 let dist = necro_pos.distance(enemy_pos);
937 if dist > self.max_distance {
938 self.broken = true;
939 return;
940 }
941
942 self.rope.attach_start(necro_pos);
943 if let Some(p) = self.rope.points.last_mut() {
945 let diff = enemy_pos - p.position;
946 p.apply_force(diff * 30.0);
947 }
948
949 self.rope.step(dt);
950
951 self.drain_progress = (self.drain_progress + self.drain_rate * dt).min(1.0);
953 if self.drain_progress >= 1.0 {
954 self.broken = true;
955 }
956
957 for pp in &mut self.particle_positions {
959 *pp -= self.particle_speed * dt;
960 if *pp < 0.0 {
961 *pp += 1.0; }
963 }
964 }
965
966 pub fn get_particle_world_positions(&self) -> Vec<Vec3> {
968 let points = self.rope.get_points();
969 if points.len() < 2 {
970 return Vec::new();
971 }
972 self.particle_positions
973 .iter()
974 .map(|t| {
975 let total = points.len() - 1;
976 let f = t * total as f32;
977 let idx = (f as usize).min(total - 1);
978 let frac = f - idx as f32;
979 points[idx].lerp(points[idx + 1], frac)
980 })
981 .collect()
982 }
983
984 pub fn is_active(&self) -> bool {
985 !self.broken
986 }
987
988 pub fn get_points(&self) -> Vec<Vec3> {
989 self.rope.get_points()
990 }
991}
992
993#[derive(Debug, Clone)]
1000pub struct SlimeEnemy {
1001 pub blob: SoftBodyBlob,
1002 pub hp: f32,
1003 pub max_hp: f32,
1004 pub base_stiffness: f32,
1006 pub min_stiffness: f32,
1008}
1009
1010impl SlimeEnemy {
1011 pub fn new(center: Vec3, radius: f32, resolution: usize, max_hp: f32) -> Self {
1012 let blob = SoftBodyBlob::new(center, radius, resolution);
1013 Self {
1014 blob,
1015 hp: max_hp,
1016 max_hp,
1017 base_stiffness: 1.0,
1018 min_stiffness: 0.1,
1019 }
1020 }
1021
1022 pub fn update(&mut self, dt: f32) {
1024 let hp_frac = (self.hp / self.max_hp).clamp(0.0, 1.0);
1026 let stiffness = self.min_stiffness + (self.base_stiffness - self.min_stiffness) * hp_frac;
1027 self.blob.set_stiffness(stiffness);
1028 self.blob.step(dt);
1029 }
1030
1031 pub fn take_hit(&mut self, direction: Vec3, force: f32, damage: f32) {
1033 self.hp = (self.hp - damage).max(0.0);
1034 self.blob.apply_hit(direction, force);
1035 }
1036
1037 pub fn is_dead(&self) -> bool {
1038 self.hp <= 0.0
1039 }
1040
1041 pub fn get_hull(&self) -> Vec<Vec3> {
1042 self.blob.get_hull()
1043 }
1044
1045 pub fn center_position(&self) -> Vec3 {
1046 self.blob.center_position()
1047 }
1048}
1049
1050#[derive(Debug, Clone)]
1058pub struct QuantumBlob {
1059 pub blob: SoftBodyBlob,
1060 pub eigenstate: u8,
1062 pub superposed: bool,
1064}
1065
1066impl QuantumBlob {
1067 pub fn new(
1069 center: Vec3,
1070 radius: f32,
1071 resolution: usize,
1072 shape_a: Vec<Vec3>,
1073 shape_b: Vec<Vec3>,
1074 frequency: f32,
1075 ) -> Self {
1076 let mut blob = SoftBodyBlob::new(center, radius, resolution);
1077 blob.oscillate_between(shape_a, shape_b, frequency);
1078 Self {
1079 blob,
1080 eigenstate: 0,
1081 superposed: true,
1082 }
1083 }
1084
1085 pub fn new_default(center: Vec3, radius: f32, resolution: usize, frequency: f32) -> Self {
1087 let n = resolution.max(4);
1088 let tau = std::f32::consts::TAU;
1089
1090 let mut shape_a = Vec::new();
1092 for i in 0..n {
1093 let angle = i as f32 / n as f32 * tau;
1094 shape_a.push(Vec3::new(angle.cos() * radius, angle.sin() * radius, 0.0));
1095 }
1096 shape_a.push(Vec3::ZERO); let mut shape_b = Vec::new();
1100 for i in 0..n {
1101 let angle = i as f32 / n as f32 * tau;
1102 shape_b.push(Vec3::new(
1103 angle.cos() * radius * 1.5,
1104 angle.sin() * radius * 0.6,
1105 0.0,
1106 ));
1107 }
1108 shape_b.push(Vec3::ZERO);
1109
1110 Self::new(center, radius, resolution, shape_a, shape_b, frequency)
1111 }
1112
1113 pub fn update(&mut self, dt: f32) {
1115 self.blob.step(dt);
1116 }
1117
1118 pub fn collapse(&mut self, state: u8) {
1120 self.superposed = false;
1121 self.eigenstate = state.min(1);
1122 self.blob.morphing = false;
1123 }
1124
1125 pub fn enter_superposition(&mut self, frequency: f32) {
1127 self.superposed = true;
1128 self.blob.morph_frequency = frequency;
1129 self.blob.morphing = true;
1130 self.blob.morph_time = 0.0;
1131 }
1132
1133 pub fn get_hull(&self) -> Vec<Vec3> {
1134 self.blob.get_hull()
1135 }
1136
1137 pub fn center_position(&self) -> Vec3 {
1138 self.blob.center_position()
1139 }
1140}
1141
1142#[derive(Debug, Clone)]
1149pub struct WeaponTrail {
1150 pub rope: RopeChain,
1151 pub compressed: bool,
1153 pub compress_timer: f32,
1155 pub compress_duration: f32,
1157 pub tip_position: Vec3,
1159}
1160
1161impl WeaponTrail {
1162 pub fn new(start: Vec3, segments: usize, segment_length: f32) -> Self {
1163 let end = start + Vec3::new(segment_length * segments as f32, 0.0, 0.0);
1164 let rope = RopeChain::new(start, end, segments);
1165 Self {
1166 rope,
1167 compressed: false,
1168 compress_timer: 0.0,
1169 compress_duration: 0.2,
1170 tip_position: end,
1171 }
1172 }
1173
1174 pub fn update(&mut self, weapon_tip: Vec3, dt: f32) {
1176 self.tip_position = weapon_tip;
1177 self.rope.attach_start(weapon_tip);
1179
1180 if self.compressed {
1182 self.compress_timer += dt;
1183 if self.compress_timer >= self.compress_duration {
1184 self.compressed = false;
1185 self.compress_timer = 0.0;
1186 }
1187 }
1188
1189 if self.compressed {
1191 let factor = 1.0 - (self.compress_timer / self.compress_duration) * 0.7;
1192 for c in &mut self.rope.constraints {
1193 c.rest_length *= factor;
1195 }
1196 self.rope.step(dt);
1197 let inv_factor = 1.0 / factor;
1199 for c in &mut self.rope.constraints {
1200 c.rest_length *= inv_factor;
1201 }
1202 } else {
1203 self.rope.step(dt);
1204 }
1205 }
1206
1207 pub fn on_impact(&mut self) {
1209 self.compressed = true;
1210 self.compress_timer = 0.0;
1211 }
1212
1213 pub fn get_points(&self) -> Vec<Vec3> {
1214 self.rope.get_points()
1215 }
1216}
1217
1218#[derive(Debug, Clone)]
1225pub struct PendulumTrap {
1226 pub rope: RopeChain,
1227 pub damage: f32,
1229 pub weight_radius: f32,
1231 pub weight_mass: f32,
1233}
1234
1235impl PendulumTrap {
1236 pub fn new(pivot: Vec3, length: f32, segments: usize, damage: f32, weight_radius: f32) -> Self {
1239 let bottom = pivot + Vec3::new(0.0, -length, 0.0);
1240 let mut rope = RopeChain::new(pivot, bottom, segments);
1241 rope.attach_start(pivot);
1242
1243 let weight_mass = 5.0;
1245 if let Some(p) = rope.points.last_mut() {
1246 p.mass = weight_mass;
1247 }
1248
1249 Self {
1250 rope,
1251 damage,
1252 weight_radius,
1253 weight_mass,
1254 }
1255 }
1256
1257 pub fn push(&mut self, force: Vec3) {
1259 if let Some(p) = self.rope.points.last_mut() {
1260 p.apply_force(force);
1261 }
1262 }
1263
1264 pub fn update(&mut self, dt: f32) {
1266 self.rope.step(dt);
1267 }
1268
1269 pub fn weight_position(&self) -> Vec3 {
1271 self.rope
1272 .points
1273 .last()
1274 .map(|p| p.position)
1275 .unwrap_or(Vec3::ZERO)
1276 }
1277
1278 pub fn check_collision(&self, point: Vec3) -> bool {
1280 self.weight_position().distance(point) < self.weight_radius
1281 }
1282
1283 pub fn get_points(&self) -> Vec<Vec3> {
1284 self.rope.get_points()
1285 }
1286}
1287
1288#[derive(Debug, Clone)]
1295pub struct TreasureChestLid {
1296 pub rope: RopeChain,
1297 pub is_open: bool,
1299 pub angle: f32,
1301 pub target_angle: f32,
1303 pub hinge_pos: Vec3,
1305 pub lid_length: f32,
1307}
1308
1309impl TreasureChestLid {
1310 pub fn new(hinge_pos: Vec3, lid_length: f32) -> Self {
1311 let end = hinge_pos + Vec3::new(lid_length, 0.0, 0.0);
1312 let mut rope = RopeChain::new(hinge_pos, end, 2);
1313 rope.attach_start(hinge_pos);
1314
1315 Self {
1316 rope,
1317 is_open: false,
1318 angle: 0.0,
1319 target_angle: 0.0,
1320 hinge_pos,
1321 lid_length,
1322 }
1323 }
1324
1325 pub fn open(&mut self) {
1327 self.is_open = true;
1328 self.target_angle = std::f32::consts::FRAC_PI_2;
1329 }
1330
1331 pub fn close(&mut self) {
1333 self.is_open = false;
1334 self.target_angle = 0.0;
1335 }
1336
1337 pub fn toggle(&mut self) {
1339 if self.is_open {
1340 self.close();
1341 } else {
1342 self.open();
1343 }
1344 }
1345
1346 pub fn update(&mut self, dt: f32) {
1349 let diff = self.target_angle - self.angle;
1351 self.angle += diff * dt * 5.0;
1352
1353 let end_pos = self.hinge_pos
1355 + Vec3::new(
1356 self.angle.cos() * self.lid_length,
1357 self.angle.sin() * self.lid_length,
1358 0.0,
1359 );
1360
1361 if let Some(p) = self.rope.points.last_mut() {
1363 let diff_vec = end_pos - p.position;
1364 p.apply_force(diff_vec * 100.0);
1365 }
1366
1367 self.rope.step(dt);
1368 }
1369
1370 pub fn tip_position(&self) -> Vec3 {
1372 self.rope
1373 .points
1374 .last()
1375 .map(|p| p.position)
1376 .unwrap_or(self.hinge_pos)
1377 }
1378
1379 pub fn get_points(&self) -> Vec<Vec3> {
1380 self.rope.get_points()
1381 }
1382}
1383
1384pub const MAX_CLOTH: usize = 20;
1390pub const MAX_ROPES: usize = 30;
1391pub const MAX_SOFTBODIES: usize = 15;
1392
1393#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1395pub struct ClothId(pub u32);
1396
1397#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1398pub struct RopeId(pub u32);
1399
1400#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1401pub struct SoftBodyId(pub u32);
1402
1403#[derive(Debug, Clone)]
1406pub struct ClothRopeManager {
1407 pub cloths: Vec<(ClothId, ClothStrip)>,
1408 pub ropes: Vec<(RopeId, RopeChain)>,
1409 pub soft_bodies: Vec<(SoftBodyId, SoftBodyBlob)>,
1410 next_cloth_id: u32,
1411 next_rope_id: u32,
1412 next_soft_body_id: u32,
1413}
1414
1415impl ClothRopeManager {
1416 pub fn new() -> Self {
1417 Self {
1418 cloths: Vec::new(),
1419 ropes: Vec::new(),
1420 soft_bodies: Vec::new(),
1421 next_cloth_id: 0,
1422 next_rope_id: 0,
1423 next_soft_body_id: 0,
1424 }
1425 }
1426
1427 pub fn add_cloth(&mut self, cloth: ClothStrip) -> Option<ClothId> {
1431 if self.cloths.len() >= MAX_CLOTH {
1432 return None;
1433 }
1434 let id = ClothId(self.next_cloth_id);
1435 self.next_cloth_id += 1;
1436 self.cloths.push((id, cloth));
1437 Some(id)
1438 }
1439
1440 pub fn add_rope(&mut self, rope: RopeChain) -> Option<RopeId> {
1442 if self.ropes.len() >= MAX_ROPES {
1443 return None;
1444 }
1445 let id = RopeId(self.next_rope_id);
1446 self.next_rope_id += 1;
1447 self.ropes.push((id, rope));
1448 Some(id)
1449 }
1450
1451 pub fn add_soft_body(&mut self, blob: SoftBodyBlob) -> Option<SoftBodyId> {
1453 if self.soft_bodies.len() >= MAX_SOFTBODIES {
1454 return None;
1455 }
1456 let id = SoftBodyId(self.next_soft_body_id);
1457 self.next_soft_body_id += 1;
1458 self.soft_bodies.push((id, blob));
1459 Some(id)
1460 }
1461
1462 pub fn remove_cloth(&mut self, id: ClothId) {
1465 self.cloths.retain(|(cid, _)| *cid != id);
1466 }
1467
1468 pub fn remove_rope(&mut self, id: RopeId) {
1469 self.ropes.retain(|(rid, _)| *rid != id);
1470 }
1471
1472 pub fn remove_soft_body(&mut self, id: SoftBodyId) {
1473 self.soft_bodies.retain(|(sid, _)| *sid != id);
1474 }
1475
1476 pub fn get_cloth_mut(&mut self, id: ClothId) -> Option<&mut ClothStrip> {
1479 self.cloths
1480 .iter_mut()
1481 .find(|(cid, _)| *cid == id)
1482 .map(|(_, c)| c)
1483 }
1484
1485 pub fn get_rope_mut(&mut self, id: RopeId) -> Option<&mut RopeChain> {
1486 self.ropes
1487 .iter_mut()
1488 .find(|(rid, _)| *rid == id)
1489 .map(|(_, r)| r)
1490 }
1491
1492 pub fn get_soft_body_mut(&mut self, id: SoftBodyId) -> Option<&mut SoftBodyBlob> {
1493 self.soft_bodies
1494 .iter_mut()
1495 .find(|(sid, _)| *sid == id)
1496 .map(|(_, s)| s)
1497 }
1498
1499 pub fn step_all(&mut self, dt: f32) {
1504 for (_, cloth) in &mut self.cloths {
1505 cloth.apply_force(Vec3::new(0.0, -9.81, 0.0));
1506 cloth.step(dt, 4);
1507 }
1508 for (_, rope) in &mut self.ropes {
1509 rope.step(dt);
1510 }
1511 for (_, blob) in &mut self.soft_bodies {
1512 blob.step(dt);
1513 }
1514 self.remove_expired();
1515 }
1516
1517 fn remove_expired(&mut self) {
1519 self.cloths.retain(|(_, c)| !c.is_expired());
1520 self.ropes.retain(|(_, r)| !r.is_expired());
1521 self.soft_bodies.retain(|(_, s)| !s.is_expired());
1522 }
1523
1524 pub fn cloth_render_data(&self) -> Vec<(ClothId, Vec<[f32; 3]>)> {
1528 self.cloths
1529 .iter()
1530 .map(|(id, c)| (*id, c.get_render_data()))
1531 .collect()
1532 }
1533
1534 pub fn rope_render_data(&self) -> Vec<(RopeId, Vec<Vec3>)> {
1536 self.ropes
1537 .iter()
1538 .map(|(id, r)| (*id, r.get_points()))
1539 .collect()
1540 }
1541
1542 pub fn soft_body_render_data(&self) -> Vec<(SoftBodyId, Vec<Vec3>)> {
1544 self.soft_bodies
1545 .iter()
1546 .map(|(id, s)| (*id, s.get_hull()))
1547 .collect()
1548 }
1549
1550 pub fn cloth_count(&self) -> usize {
1553 self.cloths.len()
1554 }
1555
1556 pub fn rope_count(&self) -> usize {
1557 self.ropes.len()
1558 }
1559
1560 pub fn soft_body_count(&self) -> usize {
1561 self.soft_bodies.len()
1562 }
1563
1564 pub fn total_count(&self) -> usize {
1565 self.cloth_count() + self.rope_count() + self.soft_body_count()
1566 }
1567
1568 pub fn total_point_count(&self) -> usize {
1570 let c: usize = self.cloths.iter().map(|(_, cl)| cl.points.len()).sum();
1571 let r: usize = self.ropes.iter().map(|(_, rp)| rp.points.len()).sum();
1572 let s: usize = self
1573 .soft_bodies
1574 .iter()
1575 .map(|(_, sb)| sb.points.len())
1576 .sum();
1577 c + r + s
1578 }
1579}
1580
1581impl Default for ClothRopeManager {
1582 fn default() -> Self {
1583 Self::new()
1584 }
1585}
1586
1587#[cfg(test)]
1592mod tests {
1593 use super::*;
1594
1595 #[test]
1596 fn test_verlet_point_integration() {
1597 let mut p = VerletPoint::new(Vec3::ZERO, 1.0);
1598 p.apply_force(Vec3::new(0.0, -9.81, 0.0));
1599 p.integrate(0.016);
1600 assert!(p.position.y < 0.0);
1602 }
1603
1604 #[test]
1605 fn test_verlet_pinned_no_move() {
1606 let mut p = VerletPoint::new(Vec3::new(1.0, 2.0, 3.0), 1.0);
1607 p.pinned = true;
1608 p.apply_force(Vec3::new(100.0, 0.0, 0.0));
1609 p.integrate(0.016);
1610 assert!((p.position.x - 1.0).abs() < 1e-6);
1611 }
1612
1613 #[test]
1614 fn test_distance_constraint() {
1615 let mut points = vec![
1616 VerletPoint::new(Vec3::ZERO, 1.0),
1617 VerletPoint::new(Vec3::new(3.0, 0.0, 0.0), 1.0),
1618 ];
1619 let c = DistanceConstraint::new(0, 1, 1.0);
1620 for _ in 0..50 {
1621 c.satisfy(&mut points);
1622 }
1623 let dist = points[0].position.distance(points[1].position);
1624 assert!((dist - 1.0).abs() < 0.01);
1625 }
1626
1627 #[test]
1628 fn test_cloth_creation() {
1629 let cloth = ClothStrip::new(4, 4, 0.5, Vec3::ZERO);
1630 assert_eq!(cloth.points.len(), 16);
1631 assert!(!cloth.structural_constraints.is_empty());
1632 assert!(!cloth.bend_constraints.is_empty());
1633 }
1634
1635 #[test]
1636 fn test_cloth_pin_unpin() {
1637 let mut cloth = ClothStrip::new(4, 4, 0.5, Vec3::ZERO);
1638 cloth.pin_point(0);
1639 assert!(cloth.points[0].pinned);
1640 cloth.unpin_point(0);
1641 assert!(!cloth.points[0].pinned);
1642 }
1643
1644 #[test]
1645 fn test_cloth_tear() {
1646 let mut cloth = ClothStrip::new(4, 4, 0.5, Vec3::ZERO);
1647 let before = cloth.active_constraint_count();
1648 cloth.tear_at(5); let after = cloth.active_constraint_count();
1650 assert!(after < before);
1651 }
1652
1653 #[test]
1654 fn test_cloth_step() {
1655 let dt = 0.016;
1661 let g = Vec3::new(0.0, -9.81, 0.0);
1662 let mut free = ClothStrip::new(4, 4, 0.5, Vec3::ZERO);
1663 let y0 = free.points[12].position.y;
1664 free.apply_force(g);
1665 free.step(dt, 4);
1666 let fall = y0 - free.points[12].position.y;
1667 assert!((fall - 9.81 * dt * dt).abs() < 1e-4, "fell {fall}");
1668
1669 let mut cloth = ClothStrip::new(4, 4, 0.5, Vec3::ZERO);
1670 for i in 0..4 {
1671 cloth.pin_point(i);
1672 }
1673 for _ in 0..60 {
1674 cloth.apply_force(g);
1675 cloth.step(dt, 4);
1676 }
1677 assert_eq!(cloth.points[0].position, Vec3::ZERO);
1678 assert!(cloth.points[12].position.y <= -1.45, "y = {}", cloth.points[12].position.y);
1680 }
1681
1682 #[test]
1683 fn test_cloth_render_data() {
1684 let cloth = ClothStrip::new(3, 3, 1.0, Vec3::ZERO);
1685 let data = cloth.get_render_data();
1686 assert_eq!(data.len(), 9);
1687 }
1688
1689 #[test]
1690 fn test_cloth_wind() {
1691 let mut cloth = ClothStrip::new(3, 3, 1.0, Vec3::ZERO);
1692 cloth.pin_point(0);
1693 cloth.pin_point(1);
1694 cloth.pin_point(2);
1695 cloth.apply_wind(Vec3::X, 10.0, 2.0);
1696 cloth.step(0.016, 4);
1697 assert!(cloth.points[6].position.x > 0.0);
1699 }
1700
1701 #[test]
1702 fn test_cloth_lifetime() {
1703 let mut cloth = ClothStrip::new(2, 2, 1.0, Vec3::ZERO);
1704 cloth.lifetime = 1.0;
1705 assert!(!cloth.is_expired());
1706 cloth.step(0.5, 1);
1707 assert!(!cloth.is_expired());
1708 cloth.step(0.6, 1);
1709 assert!(cloth.is_expired());
1710 }
1711
1712 #[test]
1713 fn test_rope_creation() {
1714 let rope = RopeChain::new(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), 10);
1715 assert_eq!(rope.points.len(), 11);
1716 assert_eq!(rope.constraints.len(), 10);
1717 }
1718
1719 #[test]
1720 fn test_rope_step_gravity() {
1721 let mut rope = RopeChain::new(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), 5);
1722 rope.attach_start(Vec3::ZERO);
1723 let y0 = rope.points[3].position.y;
1724 for _ in 0..20 {
1725 rope.step(0.016);
1726 }
1727 assert!(rope.points[3].position.y < y0);
1728 }
1729
1730 #[test]
1731 fn test_rope_sever() {
1732 let mut rope = RopeChain::new(Vec3::ZERO, Vec3::new(10.0, 0.0, 0.0), 8);
1733 let result = rope.sever_at(4);
1734 assert!(result.is_some());
1735 let new_rope = result.unwrap();
1736 assert!(!new_rope.points.is_empty());
1737 assert!(rope.points.len() <= 6);
1738 }
1739
1740 #[test]
1741 fn test_rope_attach() {
1742 let mut rope = RopeChain::new(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), 3);
1743 rope.attach_start(Vec3::new(1.0, 1.0, 0.0));
1744 assert!(rope.points[0].pinned);
1745 assert!((rope.points[0].position.x - 1.0).abs() < 1e-6);
1746 }
1747
1748 #[test]
1749 fn test_rope_length() {
1750 let rope = RopeChain::new(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), 5);
1751 let len = rope.current_length();
1752 assert!((len - 5.0).abs() < 0.01);
1753 }
1754
1755 #[test]
1756 fn test_rope_lifetime() {
1757 let mut rope = RopeChain::new(Vec3::ZERO, Vec3::X, 3);
1758 rope.lifetime = 0.5;
1759 rope.step(0.3);
1760 assert!(!rope.is_expired());
1761 rope.step(0.3);
1762 assert!(rope.is_expired());
1763 }
1764
1765 #[test]
1766 fn test_soft_body_blob_creation() {
1767 let blob = SoftBodyBlob::new(Vec3::ZERO, 1.0, 8);
1768 assert_eq!(blob.points.len(), 9); assert_eq!(blob.perimeter_count, 8);
1770 assert_eq!(blob.center_index, 8);
1771 }
1772
1773 #[test]
1774 fn test_blob_hit_deformation() {
1775 let mut blob = SoftBodyBlob::new(Vec3::ZERO, 1.0, 8);
1776 let positions_before: Vec<Vec3> = blob.points.iter().map(|p| p.position).collect();
1777 blob.apply_hit(Vec3::X, 50.0);
1778 blob.step(0.016);
1779 let moved = blob
1781 .points
1782 .iter()
1783 .zip(positions_before.iter())
1784 .any(|(p, b)| p.position.distance(*b) > 0.001);
1785 assert!(moved);
1786 }
1787
1788 #[test]
1789 fn test_blob_hull() {
1790 let blob = SoftBodyBlob::new(Vec3::ZERO, 1.0, 6);
1791 let hull = blob.get_hull();
1792 assert_eq!(hull.len(), 6);
1793 }
1794
1795 #[test]
1796 fn test_blob_oscillation() {
1797 let mut blob = SoftBodyBlob::new(Vec3::ZERO, 1.0, 6);
1798 let shape_a = vec![Vec3::X; 7];
1799 let shape_b = vec![Vec3::Y; 7];
1800 blob.oscillate_between(shape_a, shape_b, 2.0);
1801 assert!(blob.morphing);
1802 assert!((blob.morph_frequency - 2.0).abs() < 1e-6);
1803 }
1804
1805 #[test]
1806 fn test_blob_stiffness() {
1807 let mut blob = SoftBodyBlob::new(Vec3::ZERO, 1.0, 6);
1808 blob.set_stiffness(0.5);
1809 assert!((blob.spring_stiffness - 0.5).abs() < 1e-6);
1810 blob.set_stiffness(-1.0);
1811 assert!(blob.spring_stiffness >= 0.01);
1812 }
1813
1814 #[test]
1815 fn test_boss_cape_creation() {
1816 let cape = BossCape::new(Vec3::ZERO, 5, 8, 0.3);
1817 assert_eq!(cape.anchor_count, 5);
1818 assert_eq!(cape.cloth.width, 5);
1819 assert_eq!(cape.cloth.height, 8);
1820 for i in 0..5 {
1822 assert!(cape.cloth.points[i].pinned);
1823 }
1824 }
1825
1826 #[test]
1827 fn test_boss_cape_update() {
1828 let mut cape = BossCape::new(Vec3::ZERO, 4, 6, 0.5);
1829 cape.update(Vec3::new(1.0, 0.0, 0.0), 0.016);
1830 assert!((cape.cloth.points[0].position.x - 1.0).abs() < 1e-4);
1832 }
1833
1834 #[test]
1835 fn test_boss_cape_vortex() {
1836 let mut cape = BossCape::new(Vec3::ZERO, 4, 6, 0.5);
1837 cape.apply_vortex(Vec3::ZERO, 100.0, 10.0);
1838 cape.update(Vec3::ZERO, 0.016);
1839 let moved = cape.cloth.points[8..].iter().any(|p| p.position.length() > 0.01);
1841 assert!(moved);
1842 }
1843
1844 #[test]
1845 fn test_hydra_tendril_creation() {
1846 let tendril = HydraTendril::new(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), 6, 100.0);
1847 assert!(!tendril.severed);
1848 assert!((tendril.health - 100.0).abs() < 1e-6);
1849 }
1850
1851 #[test]
1852 fn test_hydra_tendril_damage_and_sever() {
1853 let mut tendril =
1854 HydraTendril::new(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), 6, 50.0);
1855 assert!(!tendril.damage(30.0));
1856 assert!(tendril.is_alive());
1857 assert!(tendril.damage(25.0));
1858 assert!(!tendril.is_alive());
1859 }
1860
1861 #[test]
1862 fn test_hydra_tendril_update() {
1863 let mut tendril =
1864 HydraTendril::new(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), 4, 100.0);
1865 tendril.update(Vec3::new(0.0, 1.0, 0.0), Vec3::new(5.0, 1.0, 0.0), 0.016);
1866 assert!((tendril.rope.points[0].position.y - 1.0).abs() < 1e-4);
1868 }
1869
1870 #[test]
1871 fn test_player_robe_creation() {
1872 let robe = PlayerRobe::new(Vec3::ZERO, 3, 3, 5, 0.2);
1873 assert_eq!(robe.strips.len(), 3);
1874 for strip in &robe.strips {
1876 for c in 0..3 {
1877 assert!(strip.points[c].pinned);
1878 }
1879 }
1880 }
1881
1882 #[test]
1883 fn test_player_robe_update() {
1884 let mut robe = PlayerRobe::new(Vec3::ZERO, 2, 2, 4, 0.3);
1885 robe.update(Vec3::new(2.0, 0.0, 0.0), 0.016);
1886 let data = robe.get_render_data();
1888 assert_eq!(data.len(), 2);
1889 }
1890
1891 #[test]
1892 fn test_necro_soul_chain() {
1893 let mut chain = NecroSoulChain::new(
1894 Vec3::ZERO,
1895 Vec3::new(5.0, 0.0, 0.0),
1896 6,
1897 20.0,
1898 0.5,
1899 );
1900 assert!(chain.is_active());
1901 chain.update(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), 0.016);
1902 assert!(chain.is_active());
1903 }
1904
1905 #[test]
1906 fn test_necro_soul_chain_break_distance() {
1907 let mut chain = NecroSoulChain::new(
1908 Vec3::ZERO,
1909 Vec3::new(5.0, 0.0, 0.0),
1910 4,
1911 10.0,
1912 0.1,
1913 );
1914 chain.update(Vec3::ZERO, Vec3::new(100.0, 0.0, 0.0), 0.016);
1916 assert!(!chain.is_active());
1917 }
1918
1919 #[test]
1920 fn test_necro_soul_chain_drain() {
1921 let mut chain = NecroSoulChain::new(
1922 Vec3::ZERO,
1923 Vec3::new(3.0, 0.0, 0.0),
1924 4,
1925 100.0,
1926 10.0, );
1928 for _ in 0..10 {
1929 chain.update(Vec3::ZERO, Vec3::new(3.0, 0.0, 0.0), 0.016);
1930 }
1931 assert!(!chain.is_active());
1934 }
1935
1936 #[test]
1937 fn test_necro_particles() {
1938 let chain = NecroSoulChain::new(
1939 Vec3::ZERO,
1940 Vec3::new(5.0, 0.0, 0.0),
1941 4,
1942 20.0,
1943 0.5,
1944 );
1945 let particles = chain.get_particle_world_positions();
1946 assert_eq!(particles.len(), 5);
1947 }
1948
1949 #[test]
1950 fn test_slime_enemy_creation() {
1951 let slime = SlimeEnemy::new(Vec3::ZERO, 1.0, 8, 100.0);
1952 assert!((slime.hp - 100.0).abs() < 1e-6);
1953 assert!(!slime.is_dead());
1954 }
1955
1956 #[test]
1957 fn test_slime_take_hit() {
1958 let mut slime = SlimeEnemy::new(Vec3::ZERO, 1.0, 8, 100.0);
1959 slime.take_hit(Vec3::X, 20.0, 60.0);
1960 assert!((slime.hp - 40.0).abs() < 1e-6);
1961 slime.take_hit(Vec3::X, 20.0, 50.0);
1962 assert!(slime.is_dead());
1963 }
1964
1965 #[test]
1966 fn test_slime_stiffness_scales() {
1967 let mut slime = SlimeEnemy::new(Vec3::ZERO, 1.0, 8, 100.0);
1968 slime.update(0.016);
1969 let stiff_full = slime.blob.spring_stiffness;
1970 slime.hp = 10.0;
1971 slime.update(0.016);
1972 let stiff_low = slime.blob.spring_stiffness;
1973 assert!(stiff_low < stiff_full);
1974 }
1975
1976 #[test]
1977 fn test_quantum_blob_default() {
1978 let qb = QuantumBlob::new_default(Vec3::ZERO, 1.0, 8, 2.0);
1979 assert!(qb.superposed);
1980 assert!(qb.blob.morphing);
1981 }
1982
1983 #[test]
1984 fn test_quantum_blob_collapse() {
1985 let mut qb = QuantumBlob::new_default(Vec3::ZERO, 1.0, 8, 2.0);
1986 qb.collapse(0);
1987 assert!(!qb.superposed);
1988 assert!(!qb.blob.morphing);
1989 assert_eq!(qb.eigenstate, 0);
1990 }
1991
1992 #[test]
1993 fn test_quantum_blob_superposition() {
1994 let mut qb = QuantumBlob::new_default(Vec3::ZERO, 1.0, 8, 2.0);
1995 qb.collapse(1);
1996 qb.enter_superposition(3.0);
1997 assert!(qb.superposed);
1998 assert!(qb.blob.morphing);
1999 assert!((qb.blob.morph_frequency - 3.0).abs() < 1e-6);
2000 }
2001
2002 #[test]
2003 fn test_weapon_trail_creation() {
2004 let trail = WeaponTrail::new(Vec3::ZERO, 8, 0.1);
2005 assert_eq!(trail.rope.points.len(), 9);
2006 assert!(!trail.compressed);
2007 }
2008
2009 #[test]
2010 fn test_weapon_trail_impact() {
2011 let mut trail = WeaponTrail::new(Vec3::ZERO, 5, 0.2);
2012 trail.on_impact();
2013 assert!(trail.compressed);
2014 for _ in 0..20 {
2016 trail.update(Vec3::new(1.0, 0.0, 0.0), 0.016);
2017 }
2018 assert!(!trail.compressed);
2019 }
2020
2021 #[test]
2022 fn test_pendulum_trap_creation() {
2023 let trap = PendulumTrap::new(Vec3::new(0.0, 10.0, 0.0), 5.0, 6, 25.0, 0.5);
2024 assert!((trap.damage - 25.0).abs() < 1e-6);
2025 assert!(trap.rope.points[0].pinned);
2026 }
2027
2028 #[test]
2029 fn test_pendulum_swings() {
2030 let mut trap = PendulumTrap::new(Vec3::new(0.0, 10.0, 0.0), 5.0, 4, 10.0, 0.3);
2031 trap.push(Vec3::new(20.0, 0.0, 0.0));
2032 for _ in 0..50 {
2033 trap.update(0.016);
2034 }
2035 assert!(trap.weight_position().x.abs() > 0.01);
2037 }
2038
2039 #[test]
2040 fn test_pendulum_collision() {
2041 let trap = PendulumTrap::new(Vec3::new(0.0, 10.0, 0.0), 5.0, 4, 10.0, 1.0);
2042 let weight = trap.weight_position();
2043 assert!(trap.check_collision(weight));
2044 assert!(!trap.check_collision(Vec3::new(100.0, 100.0, 0.0)));
2045 }
2046
2047 #[test]
2048 fn test_treasure_chest_lid() {
2049 let mut lid = TreasureChestLid::new(Vec3::ZERO, 1.0);
2050 assert!(!lid.is_open);
2051 lid.open();
2052 assert!(lid.is_open);
2053 lid.close();
2054 assert!(!lid.is_open);
2055 }
2056
2057 #[test]
2058 fn test_treasure_chest_toggle() {
2059 let mut lid = TreasureChestLid::new(Vec3::ZERO, 1.0);
2060 lid.toggle();
2061 assert!(lid.is_open);
2062 lid.toggle();
2063 assert!(!lid.is_open);
2064 }
2065
2066 #[test]
2067 fn test_treasure_chest_update() {
2068 let mut lid = TreasureChestLid::new(Vec3::ZERO, 1.0);
2069 lid.open();
2070 for _ in 0..60 {
2071 lid.update(0.016);
2072 }
2073 assert!(lid.angle > 0.5);
2075 }
2076
2077 #[test]
2078 fn test_manager_creation() {
2079 let mgr = ClothRopeManager::new();
2080 assert_eq!(mgr.total_count(), 0);
2081 }
2082
2083 #[test]
2084 fn test_manager_add_cloth() {
2085 let mut mgr = ClothRopeManager::new();
2086 let cloth = ClothStrip::new(3, 3, 0.5, Vec3::ZERO);
2087 let id = mgr.add_cloth(cloth);
2088 assert!(id.is_some());
2089 assert_eq!(mgr.cloth_count(), 1);
2090 }
2091
2092 #[test]
2093 fn test_manager_add_rope() {
2094 let mut mgr = ClothRopeManager::new();
2095 let rope = RopeChain::new(Vec3::ZERO, Vec3::X * 5.0, 4);
2096 let id = mgr.add_rope(rope);
2097 assert!(id.is_some());
2098 assert_eq!(mgr.rope_count(), 1);
2099 }
2100
2101 #[test]
2102 fn test_manager_add_soft_body() {
2103 let mut mgr = ClothRopeManager::new();
2104 let blob = SoftBodyBlob::new(Vec3::ZERO, 1.0, 6);
2105 let id = mgr.add_soft_body(blob);
2106 assert!(id.is_some());
2107 assert_eq!(mgr.soft_body_count(), 1);
2108 }
2109
2110 #[test]
2111 fn test_manager_limits() {
2112 let mut mgr = ClothRopeManager::new();
2113 for _ in 0..MAX_CLOTH {
2114 let cloth = ClothStrip::new(2, 2, 0.5, Vec3::ZERO);
2115 assert!(mgr.add_cloth(cloth).is_some());
2116 }
2117 let cloth = ClothStrip::new(2, 2, 0.5, Vec3::ZERO);
2118 assert!(mgr.add_cloth(cloth).is_none());
2119 }
2120
2121 #[test]
2122 fn test_manager_remove() {
2123 let mut mgr = ClothRopeManager::new();
2124 let cloth = ClothStrip::new(2, 2, 0.5, Vec3::ZERO);
2125 let id = mgr.add_cloth(cloth).unwrap();
2126 assert_eq!(mgr.cloth_count(), 1);
2127 mgr.remove_cloth(id);
2128 assert_eq!(mgr.cloth_count(), 0);
2129 }
2130
2131 #[test]
2132 fn test_manager_step_all() {
2133 let mut mgr = ClothRopeManager::new();
2134 let mut cloth = ClothStrip::new(3, 3, 0.5, Vec3::ZERO);
2135 cloth.pin_point(0);
2136 cloth.pin_point(1);
2137 cloth.pin_point(2);
2138 mgr.add_cloth(cloth);
2139
2140 let rope = RopeChain::new(Vec3::ZERO, Vec3::X * 3.0, 3);
2141 mgr.add_rope(rope);
2142
2143 let blob = SoftBodyBlob::new(Vec3::ZERO, 1.0, 6);
2144 mgr.add_soft_body(blob);
2145
2146 mgr.step_all(0.016);
2147 assert_eq!(mgr.total_count(), 3);
2148 }
2149
2150 #[test]
2151 fn test_manager_expiry() {
2152 let mut mgr = ClothRopeManager::new();
2153 let mut cloth = ClothStrip::new(2, 2, 0.5, Vec3::ZERO);
2154 cloth.lifetime = 0.01;
2155 mgr.add_cloth(cloth);
2156 assert_eq!(mgr.cloth_count(), 1);
2157 mgr.step_all(0.02);
2158 assert_eq!(mgr.cloth_count(), 0);
2159 }
2160
2161 #[test]
2162 fn test_manager_render_data() {
2163 let mut mgr = ClothRopeManager::new();
2164 let cloth = ClothStrip::new(2, 2, 0.5, Vec3::ZERO);
2165 mgr.add_cloth(cloth);
2166 let data = mgr.cloth_render_data();
2167 assert_eq!(data.len(), 1);
2168 assert_eq!(data[0].1.len(), 4);
2169 }
2170
2171 #[test]
2172 fn test_manager_point_count() {
2173 let mut mgr = ClothRopeManager::new();
2174 let cloth = ClothStrip::new(3, 3, 0.5, Vec3::ZERO);
2175 mgr.add_cloth(cloth);
2176 let rope = RopeChain::new(Vec3::ZERO, Vec3::X, 4);
2177 mgr.add_rope(rope);
2178 assert_eq!(mgr.total_point_count(), 9 + 5);
2179 }
2180
2181 #[test]
2182 fn test_manager_get_mut() {
2183 let mut mgr = ClothRopeManager::new();
2184 let cloth = ClothStrip::new(2, 2, 0.5, Vec3::ZERO);
2185 let id = mgr.add_cloth(cloth).unwrap();
2186 let c = mgr.get_cloth_mut(id);
2187 assert!(c.is_some());
2188 let c = c.unwrap();
2189 c.pin_point(0);
2190 assert!(mgr.get_cloth_mut(id).unwrap().points[0].pinned);
2191 }
2192}