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