1#[cfg(not(feature = "std"))]
22use alloc::{vec, vec::Vec};
23
24use core::ops::{Add, Div, Mul, Neg, Sub};
25
26use crate::math::Fix128;
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
37#[repr(C)]
38pub struct Vec2Fix {
39 pub x: Fix128,
41 pub y: Fix128,
43}
44
45impl Vec2Fix {
46 pub const ZERO: Self = Self {
48 x: Fix128::ZERO,
49 y: Fix128::ZERO,
50 };
51
52 pub const ONE: Self = Self {
54 x: Fix128::ONE,
55 y: Fix128::ONE,
56 };
57
58 pub const UNIT_X: Self = Self {
60 x: Fix128::ONE,
61 y: Fix128::ZERO,
62 };
63
64 pub const UNIT_Y: Self = Self {
66 x: Fix128::ZERO,
67 y: Fix128::ONE,
68 };
69
70 #[inline]
72 #[must_use]
73 pub const fn new(x: Fix128, y: Fix128) -> Self {
74 Self { x, y }
75 }
76
77 #[inline]
79 #[must_use]
80 pub const fn from_int(x: i64, y: i64) -> Self {
81 Self {
82 x: Fix128::from_int(x),
83 y: Fix128::from_int(y),
84 }
85 }
86
87 #[inline]
89 #[must_use]
90 pub fn length_squared(self) -> Fix128 {
91 self.x * self.x + self.y * self.y
92 }
93
94 #[inline]
96 #[must_use]
97 pub fn length(self) -> Fix128 {
98 self.length_squared().sqrt()
99 }
100
101 #[inline]
103 #[must_use]
104 pub fn normalize(self) -> Self {
105 let len = self.length();
106 if len.is_zero() {
107 Self::ZERO
108 } else {
109 self / len
110 }
111 }
112
113 #[inline]
115 #[must_use]
116 pub fn dot(self, rhs: Self) -> Fix128 {
117 self.x * rhs.x + self.y * rhs.y
118 }
119
120 #[inline]
125 #[must_use]
126 pub fn cross_scalar(self, rhs: Self) -> Fix128 {
127 self.x * rhs.y - self.y * rhs.x
128 }
129
130 #[must_use]
132 pub fn rotate(self, angle: Fix128) -> Self {
133 let (sin_a, cos_a) = angle.sin_cos();
134 Self {
135 x: self.x * cos_a - self.y * sin_a,
136 y: self.x * sin_a + self.y * cos_a,
137 }
138 }
139
140 #[inline]
142 #[must_use]
143 pub fn perpendicular(self) -> Self {
144 Self {
145 x: -self.y,
146 y: self.x,
147 }
148 }
149
150 #[inline]
152 #[must_use]
153 pub fn distance_to(self, other: Self) -> Fix128 {
154 (other - self).length()
155 }
156
157 #[inline]
159 #[must_use]
160 pub fn lerp(self, other: Self, t: Fix128) -> Self {
161 self + (other - self) * t
162 }
163
164 #[inline]
166 #[must_use]
167 pub fn scale(self, s: Fix128) -> Self {
168 Self {
169 x: self.x * s,
170 y: self.y * s,
171 }
172 }
173}
174
175impl Add for Vec2Fix {
176 type Output = Self;
177
178 #[inline]
179 fn add(self, rhs: Self) -> Self {
180 Self {
181 x: self.x + rhs.x,
182 y: self.y + rhs.y,
183 }
184 }
185}
186
187impl Sub for Vec2Fix {
188 type Output = Self;
189
190 #[inline]
191 fn sub(self, rhs: Self) -> Self {
192 Self {
193 x: self.x - rhs.x,
194 y: self.y - rhs.y,
195 }
196 }
197}
198
199impl Mul<Fix128> for Vec2Fix {
200 type Output = Self;
201
202 #[inline]
203 fn mul(self, rhs: Fix128) -> Self {
204 self.scale(rhs)
205 }
206}
207
208impl Div<Fix128> for Vec2Fix {
209 type Output = Self;
210
211 #[inline]
212 fn div(self, rhs: Fix128) -> Self {
213 Self {
214 x: self.x / rhs,
215 y: self.y / rhs,
216 }
217 }
218}
219
220impl Neg for Vec2Fix {
221 type Output = Self;
222
223 #[inline]
224 fn neg(self) -> Self {
225 Self {
226 x: -self.x,
227 y: -self.y,
228 }
229 }
230}
231
232#[derive(Clone, Debug)]
240pub enum Shape2D {
241 Circle {
243 radius: Fix128,
245 },
246 Polygon {
248 vertices: Vec<Vec2Fix>,
250 },
251 Capsule {
253 radius: Fix128,
255 half_length: Fix128,
257 },
258 Edge {
264 start: Vec2Fix,
266 end: Vec2Fix,
268 },
269}
270
271#[derive(Clone, Copy, Debug, PartialEq, Eq)]
277pub enum BodyType2D {
278 Dynamic,
280 Static,
282 Kinematic,
284}
285
286#[derive(Clone, Debug)]
292pub struct RigidBody2D {
293 pub position: Vec2Fix,
295 pub angle: Fix128,
297 pub velocity: Vec2Fix,
299 pub angular_velocity: Fix128,
301 pub inv_mass: Fix128,
303 pub inv_inertia: Fix128,
305 pub shape: Shape2D,
307 pub restitution: Fix128,
309 pub friction: Fix128,
311 pub body_type: BodyType2D,
313 pub prev_position: Vec2Fix,
315 pub prev_angle: Fix128,
317}
318
319impl RigidBody2D {
320 #[must_use]
325 pub fn new_dynamic(position: Vec2Fix, mass: Fix128, shape: Shape2D) -> Self {
326 let inv_mass = if mass.is_zero() {
327 Fix128::ZERO
328 } else {
329 Fix128::ONE / mass
330 };
331 let inertia = compute_inertia(&shape, mass);
332 let inv_inertia = if inertia.is_zero() {
333 Fix128::ZERO
334 } else {
335 Fix128::ONE / inertia
336 };
337 Self {
338 position,
339 angle: Fix128::ZERO,
340 velocity: Vec2Fix::ZERO,
341 angular_velocity: Fix128::ZERO,
342 inv_mass,
343 inv_inertia,
344 shape,
345 restitution: Fix128::from_ratio(5, 10),
346 friction: Fix128::from_ratio(3, 10),
347 body_type: BodyType2D::Dynamic,
348 prev_position: position,
349 prev_angle: Fix128::ZERO,
350 }
351 }
352
353 #[must_use]
355 pub fn new_static(position: Vec2Fix, shape: Shape2D) -> Self {
356 Self {
357 position,
358 angle: Fix128::ZERO,
359 velocity: Vec2Fix::ZERO,
360 angular_velocity: Fix128::ZERO,
361 inv_mass: Fix128::ZERO,
362 inv_inertia: Fix128::ZERO,
363 shape,
364 restitution: Fix128::from_ratio(5, 10),
365 friction: Fix128::from_ratio(5, 10),
366 body_type: BodyType2D::Static,
367 prev_position: position,
368 prev_angle: Fix128::ZERO,
369 }
370 }
371
372 #[must_use]
374 pub fn new_kinematic(position: Vec2Fix, shape: Shape2D) -> Self {
375 Self {
376 position,
377 angle: Fix128::ZERO,
378 velocity: Vec2Fix::ZERO,
379 angular_velocity: Fix128::ZERO,
380 inv_mass: Fix128::ZERO,
381 inv_inertia: Fix128::ZERO,
382 shape,
383 restitution: Fix128::from_ratio(5, 10),
384 friction: Fix128::from_ratio(3, 10),
385 body_type: BodyType2D::Kinematic,
386 prev_position: position,
387 prev_angle: Fix128::ZERO,
388 }
389 }
390
391 #[inline]
393 pub fn apply_impulse(&mut self, impulse: Vec2Fix) {
394 if self.body_type != BodyType2D::Dynamic {
395 return;
396 }
397 self.velocity = self.velocity + impulse * self.inv_mass;
398 }
399
400 pub fn apply_impulse_at_point(&mut self, impulse: Vec2Fix, world_point: Vec2Fix) {
403 if self.body_type != BodyType2D::Dynamic {
404 return;
405 }
406 self.velocity = self.velocity + impulse * self.inv_mass;
407 let r = world_point - self.position;
408 self.angular_velocity = self.angular_velocity + r.cross_scalar(impulse) * self.inv_inertia;
409 }
410
411 #[inline]
413 pub fn apply_force(&mut self, force: Vec2Fix, dt: Fix128) {
414 if self.body_type != BodyType2D::Dynamic {
415 return;
416 }
417 self.velocity = self.velocity + force * self.inv_mass * dt;
418 }
419
420 #[must_use]
422 pub fn world_point(&self, local: Vec2Fix) -> Vec2Fix {
423 self.position + local.rotate(self.angle)
424 }
425
426 #[inline]
428 #[must_use]
429 pub fn is_static_or_kinematic(&self) -> bool {
430 self.body_type != BodyType2D::Dynamic
431 }
432}
433
434fn compute_inertia(shape: &Shape2D, mass: Fix128) -> Fix128 {
436 match shape {
437 Shape2D::Circle { radius } => {
438 mass * *radius * *radius / Fix128::from_int(2)
440 }
441 Shape2D::Capsule {
442 radius,
443 half_length,
444 } => {
445 let r2 = *radius * *radius;
450 let h2 = *half_length * *half_length;
451 mass * (r2 / Fix128::from_int(2) + h2 / Fix128::from_int(3))
452 }
453 Shape2D::Polygon { vertices } => {
454 if vertices.len() < 3 {
456 return Fix128::ZERO;
457 }
458 let mut numerator = Fix128::ZERO;
459 let mut denominator = Fix128::ZERO;
460 let n = vertices.len();
461 for i in 0..n {
462 let a = vertices[i];
463 let b = vertices[(i + 1) % n];
464 let cross = a.cross_scalar(b).abs();
465 numerator = numerator + cross * (a.dot(a) + a.dot(b) + b.dot(b));
466 denominator = denominator + cross;
467 }
468 if denominator.is_zero() {
469 return Fix128::ZERO;
470 }
471 mass * numerator / (denominator * Fix128::from_int(6))
472 }
473 Shape2D::Edge { start, end } => {
474 let l2 = (*end - *start).length_squared();
476 mass * l2 / Fix128::from_int(12)
477 }
478 }
479}
480
481#[derive(Clone, Copy, Debug, PartialEq, Eq)]
487pub struct Contact2D {
488 pub point: Vec2Fix,
490 pub normal: Vec2Fix,
492 pub depth: Fix128,
494 pub body_a: usize,
496 pub body_b: usize,
498}
499
500#[derive(Clone, Debug)]
506pub struct PhysicsConfig2D {
507 pub gravity: Vec2Fix,
509 pub substeps: usize,
511 pub iterations: usize,
513 pub damping: Fix128,
515}
516
517impl Default for PhysicsConfig2D {
518 fn default() -> Self {
519 Self {
520 gravity: Vec2Fix::new(Fix128::ZERO, Fix128::from_int(-10)),
521 substeps: 4,
522 iterations: 8,
523 damping: Fix128::from_ratio(99, 100),
524 }
525 }
526}
527
528pub struct PhysicsWorld2D {
534 pub bodies: Vec<RigidBody2D>,
536 pub gravity: Vec2Fix,
538 pub config: PhysicsConfig2D,
540 pub joints: Vec<Joint2D>,
542}
543
544impl PhysicsWorld2D {
545 #[must_use]
547 pub const fn new(config: PhysicsConfig2D) -> Self {
548 let gravity = config.gravity;
549 Self {
550 bodies: Vec::new(),
551 gravity,
552 config,
553 joints: Vec::new(),
554 }
555 }
556
557 pub fn add_body(&mut self, body: RigidBody2D) -> usize {
559 let idx = self.bodies.len();
560 self.bodies.push(body);
561 idx
562 }
563
564 pub fn remove_body(&mut self, index: usize) -> Option<RigidBody2D> {
568 if index >= self.bodies.len() {
569 return None;
570 }
571 Some(self.bodies.swap_remove(index))
572 }
573
574 pub fn add_joint(&mut self, joint: Joint2D) {
576 self.joints.push(joint);
577 }
578
579 pub fn step(&mut self, dt: Fix128) {
584 self.step_with_tethers(dt, &Tethers2D::new());
585 }
586
587 pub fn step_with_tethers(&mut self, dt: Fix128, tethers: &Tethers2D) {
592 let substeps = self.config.substeps;
593 if substeps == 0 {
594 return;
595 }
596 let sub_dt = dt / Fix128::from_int(substeps as i64);
597
598 for _ in 0..substeps {
599 self.substep(sub_dt, tethers);
600 }
601
602 let damping = self.config.damping;
604 for body in &mut self.bodies {
605 if body.body_type == BodyType2D::Dynamic {
606 body.velocity = body.velocity * damping;
607 body.angular_velocity = body.angular_velocity * damping;
608 }
609 }
610 }
611
612 fn substep(&mut self, sub_dt: Fix128, tethers: &Tethers2D) {
614 let gravity = self.gravity;
615 let iterations = self.config.iterations;
616
617 for body in &mut self.bodies {
619 if body.body_type != BodyType2D::Dynamic {
620 body.prev_position = body.position;
622 body.prev_angle = body.angle;
623 if body.body_type == BodyType2D::Kinematic {
624 body.position = body.position + body.velocity * sub_dt;
625 body.angle = body.angle + body.angular_velocity * sub_dt;
626 }
627 continue;
628 }
629 body.prev_position = body.position;
630 body.prev_angle = body.angle;
631
632 body.velocity = body.velocity + gravity * sub_dt;
634
635 body.position = body.position + body.velocity * sub_dt;
637 body.angle = body.angle + body.angular_velocity * sub_dt;
638 }
639 for drive in tethers.live_drives() {
640 apply_kinematic_drive(&mut self.bodies, drive, sub_dt);
641 }
642
643 let contacts = self.detect_all_contacts();
646 let pre_normal_velocity: Vec<Fix128> = contacts
647 .iter()
648 .map(|c| self.contact_normal_velocity(c))
649 .collect();
650 let mut lambda_n = vec![Fix128::ZERO; contacts.len()];
651 let mut joint_lambda = vec![Vec2Fix::ZERO; self.joints.len()];
652 let mut tether_lambda = vec![Fix128::ZERO; tethers.angular.len()];
653
654 for _ in 0..iterations {
661 for (k, pair) in contacts.iter().enumerate() {
662 let Some(current) = self.check_collision_2d(pair.body_a, pair.body_b) else {
663 continue;
664 };
665 lambda_n[k] = lambda_n[k] + solve_contact_position(&mut self.bodies, ¤t);
666 }
667
668 solve_joints_2d_accumulated(&mut self.bodies, &self.joints, sub_dt, &mut joint_lambda);
670
671 for (slot, lambda) in tethers.angular.iter().zip(tether_lambda.iter_mut()) {
672 if let Some(tether) = slot {
673 solve_angular_tether(&mut self.bodies, tether, sub_dt, lambda);
674 }
675 }
676 }
677
678 if sub_dt.is_zero() {
680 return;
681 }
682 let inv_dt = Fix128::ONE / sub_dt;
683 for body in &mut self.bodies {
684 if body.body_type != BodyType2D::Dynamic {
685 continue;
686 }
687 body.velocity = (body.position - body.prev_position) * inv_dt;
688 body.angular_velocity = (body.angle - body.prev_angle) * inv_dt;
689 }
690
691 let restitution_threshold = self.gravity.length() * sub_dt * Fix128::from_int(2);
697 for (k, pair) in contacts.iter().enumerate() {
698 if lambda_n[k] <= Fix128::ZERO {
699 continue;
700 }
701 self.apply_contact_velocity_pass(
702 pair,
703 pre_normal_velocity[k],
704 lambda_n[k] * inv_dt,
705 restitution_threshold,
706 );
707 }
708 }
709
710 fn contact_normal_velocity(&self, c: &Contact2D) -> Fix128 {
714 let (va, vb) = (
715 self.point_velocity(c.body_a, c.point),
716 self.point_velocity(c.body_b, c.point),
717 );
718 (vb - va).dot(c.normal)
719 }
720
721 fn point_velocity(&self, body: usize, p: Vec2Fix) -> Vec2Fix {
723 let b = &self.bodies[body];
724 let r = p - b.position;
725 b.velocity + r.perpendicular() * b.angular_velocity
726 }
727
728 fn apply_contact_velocity_pass(
732 &mut self,
733 c: &Contact2D,
734 pre_vn: Fix128,
735 lambda_n_over_h: Fix128,
736 restitution_threshold: Fix128,
737 ) {
738 let (a, b, n) = (c.body_a, c.body_b, c.normal);
739 let dyn_a = self.bodies[a].body_type == BodyType2D::Dynamic;
740 let dyn_b = self.bodies[b].body_type == BodyType2D::Dynamic;
741 if !dyn_a && !dyn_b {
742 return;
743 }
744 let r_a = c.point - self.bodies[a].position;
745 let r_b = c.point - self.bodies[b].position;
746 let inv_m_a = if dyn_a {
747 self.bodies[a].inv_mass
748 } else {
749 Fix128::ZERO
750 };
751 let inv_m_b = if dyn_b {
752 self.bodies[b].inv_mass
753 } else {
754 Fix128::ZERO
755 };
756 let inv_i_a = if dyn_a {
757 self.bodies[a].inv_inertia
758 } else {
759 Fix128::ZERO
760 };
761 let inv_i_b = if dyn_b {
762 self.bodies[b].inv_inertia
763 } else {
764 Fix128::ZERO
765 };
766 let w_along = |d: Vec2Fix| {
768 let ra_d = r_a.cross_scalar(d);
769 let rb_d = r_b.cross_scalar(d);
770 inv_m_a + inv_m_b + inv_i_a * ra_d * ra_d + inv_i_b * rb_d * rb_d
771 };
772 let apply = |bodies: &mut [RigidBody2D], d: Vec2Fix, p: Fix128| {
774 if dyn_a {
775 bodies[a].velocity = bodies[a].velocity - d * (p * inv_m_a);
776 bodies[a].angular_velocity =
777 bodies[a].angular_velocity - r_a.cross_scalar(d) * p * inv_i_a;
778 }
779 if dyn_b {
780 bodies[b].velocity = bodies[b].velocity + d * (p * inv_m_b);
781 bodies[b].angular_velocity =
782 bodies[b].angular_velocity + r_b.cross_scalar(d) * p * inv_i_b;
783 }
784 };
785
786 let v_rel = self.point_velocity(b, c.point) - self.point_velocity(a, c.point);
788 let vn = v_rel.dot(n);
789 let e = if pre_vn < -restitution_threshold {
792 let ea = self.bodies[a].restitution;
793 let eb = self.bodies[b].restitution;
794 if ea > eb {
795 ea
796 } else {
797 eb
798 }
799 } else {
800 Fix128::ZERO
801 };
802 let target = -e * pre_vn;
803 let target = if target.is_negative() {
804 Fix128::ZERO
805 } else {
806 target
807 };
808 let w_n = w_along(n);
809 if vn < target && !w_n.is_zero() {
810 apply(&mut self.bodies, n, (target - vn) / w_n);
811 }
812
813 let v_rel = self.point_velocity(b, c.point) - self.point_velocity(a, c.point);
815 let vt = v_rel - n * v_rel.dot(n);
816 let vt_len = vt.length();
817 if vt_len.is_zero() {
818 return;
819 }
820 let mu = (self.bodies[a].friction * self.bodies[b].friction).sqrt();
821 let max_dv = mu * lambda_n_over_h;
822 let dv = if vt_len < max_dv { vt_len } else { max_dv };
823 if dv.is_zero() {
824 return;
825 }
826 let t = vt * (Fix128::ONE / vt_len);
827 let w_t = w_along(t);
828 if !w_t.is_zero() {
829 apply(&mut self.bodies, t, -dv / w_t);
830 }
831 }
832
833 fn detect_all_contacts(&self) -> Vec<Contact2D> {
835 let mut contacts = Vec::new();
836 let n = self.bodies.len();
837 for i in 0..n {
838 for j in (i + 1)..n {
839 if self.bodies[i].is_static_or_kinematic()
841 && self.bodies[j].is_static_or_kinematic()
842 {
843 continue;
844 }
845 if let Some(contact) = self.check_collision_2d(i, j) {
846 contacts.push(contact);
847 }
848 }
849 }
850 contacts
851 }
852
853 #[must_use]
855 pub fn check_collision_2d(&self, idx_a: usize, idx_b: usize) -> Option<Contact2D> {
856 let body_a = &self.bodies[idx_a];
857 let body_b = &self.bodies[idx_b];
858
859 let result = match (&body_a.shape, &body_b.shape) {
860 (Shape2D::Circle { radius: ra }, Shape2D::Circle { radius: rb }) => {
861 circle_vs_circle(body_a.position, *ra, body_b.position, *rb)
862 }
863 (Shape2D::Circle { radius }, Shape2D::Polygon { vertices }) => {
864 circle_vs_polygon(body_a.position, *radius, body_b, vertices)
865 }
866 (Shape2D::Polygon { vertices }, Shape2D::Circle { radius }) => {
867 circle_vs_polygon(body_b.position, *radius, body_a, vertices).map(|mut c| {
868 c.normal = -c.normal;
869 c
870 })
871 }
872 (Shape2D::Polygon { vertices: va }, Shape2D::Polygon { vertices: vb }) => {
873 polygon_vs_polygon(body_a, va, body_b, vb)
874 }
875 (
876 Shape2D::Circle { radius: rc },
877 Shape2D::Capsule {
878 radius: rcap,
879 half_length,
880 },
881 ) => {
882 capsule_vs_circle(body_b, *rcap, *half_length, body_a.position, *rc).map(|mut c| {
884 c.normal = -c.normal;
885 c
886 })
887 }
888 (
889 Shape2D::Capsule {
890 radius: rcap,
891 half_length,
892 },
893 Shape2D::Circle { radius: rc },
894 ) => capsule_vs_circle(body_a, *rcap, *half_length, body_b.position, *rc),
895 (Shape2D::Edge { start, end }, Shape2D::Circle { radius }) => {
896 edge_vs_circle(body_a, *start, *end, body_b.position, *radius)
897 }
898 (Shape2D::Circle { radius }, Shape2D::Edge { start, end }) => {
899 edge_vs_circle(body_b, *start, *end, body_a.position, *radius).map(|mut c| {
900 c.normal = -c.normal;
901 c
902 })
903 }
904 (Shape2D::Edge { .. }, Shape2D::Edge { .. }) => None,
909 (
910 Shape2D::Capsule { .. } | Shape2D::Edge { .. } | Shape2D::Polygon { .. },
911 Shape2D::Capsule { .. } | Shape2D::Edge { .. } | Shape2D::Polygon { .. },
912 ) => match (rounded_core(body_a), rounded_core(body_b)) {
913 (Some((core_a, ra)), Some((core_b, rb))) => {
914 rounded_core_vs_rounded_core(&core_a, ra, &core_b, rb)
915 }
916 _ => None,
917 },
918 };
919
920 result.map(|mut c| {
921 c.body_a = idx_a;
922 c.body_b = idx_b;
923 c
924 })
925 }
926}
927
928fn circle_vs_circle(
934 pos_a: Vec2Fix,
935 radius_a: Fix128,
936 pos_b: Vec2Fix,
937 radius_b: Fix128,
938) -> Option<Contact2D> {
939 let delta = pos_b - pos_a;
940 let dist_sq = delta.length_squared();
941 let sum_r = radius_a + radius_b;
942 let sum_r_sq = sum_r * sum_r;
943
944 if dist_sq > sum_r_sq || dist_sq.is_zero() {
945 return None;
946 }
947
948 let dist = dist_sq.sqrt();
949 let normal = if dist.is_zero() {
950 Vec2Fix::UNIT_Y
951 } else {
952 delta / dist
953 };
954
955 let depth = sum_r - dist;
956 let point = pos_a + normal * (radius_a - depth.half());
957
958 Some(Contact2D {
959 point,
960 normal,
961 depth,
962 body_a: 0,
963 body_b: 0,
964 })
965}
966
967fn transform_vertices(body: &RigidBody2D, local_verts: &[Vec2Fix]) -> Vec<Vec2Fix> {
969 local_verts.iter().map(|v| body.world_point(*v)).collect()
970}
971
972fn circle_vs_polygon(
978 circle_pos: Vec2Fix,
979 circle_radius: Fix128,
980 poly_body: &RigidBody2D,
981 poly_verts: &[Vec2Fix],
982) -> Option<Contact2D> {
983 if poly_verts.len() < 3 {
984 return None;
985 }
986
987 let world_verts = transform_vertices(poly_body, poly_verts);
988 let n = world_verts.len();
989
990 let mut best_dist = Fix128::from_int(-999_999);
992 let mut best_normal = Vec2Fix::ZERO;
993 let mut best_idx = 0;
994
995 for i in 0..n {
996 let a = world_verts[i];
997 let b = world_verts[(i + 1) % n];
998 let edge = b - a;
999 let normal = Vec2Fix::new(edge.y, -edge.x).normalize();
1001 let d = (circle_pos - a).dot(normal);
1002
1003 if d > best_dist {
1004 best_dist = d;
1005 best_normal = normal;
1006 best_idx = i;
1007 }
1008 }
1009
1010 if best_dist > circle_radius {
1012 return None;
1013 }
1014
1015 let a = world_verts[best_idx];
1017 let b = world_verts[(best_idx + 1) % n];
1018 let edge = b - a;
1019 let edge_len_sq = edge.length_squared();
1020
1021 let t = if edge_len_sq.is_zero() {
1022 Fix128::ZERO
1023 } else {
1024 (circle_pos - a).dot(edge) / edge_len_sq
1025 };
1026
1027 if t < Fix128::ZERO {
1028 let delta = circle_pos - a;
1030 let dist = delta.length();
1031 if dist > circle_radius || dist.is_zero() {
1032 return None;
1033 }
1034 let normal = delta / dist;
1035 let depth = circle_radius - dist;
1036 Some(Contact2D {
1037 point: a,
1038 normal: -normal,
1039 depth,
1040 body_a: 0,
1041 body_b: 0,
1042 })
1043 } else if t > Fix128::ONE {
1044 let delta = circle_pos - b;
1046 let dist = delta.length();
1047 if dist > circle_radius || dist.is_zero() {
1048 return None;
1049 }
1050 let normal = delta / dist;
1051 let depth = circle_radius - dist;
1052 Some(Contact2D {
1053 point: b,
1054 normal: -normal,
1055 depth,
1056 body_a: 0,
1057 body_b: 0,
1058 })
1059 } else {
1060 let depth = circle_radius - best_dist;
1062 if depth.is_negative() {
1063 return None;
1064 }
1065 let point = circle_pos - best_normal * best_dist;
1066 Some(Contact2D {
1067 point,
1068 normal: -best_normal,
1069 depth,
1070 body_a: 0,
1071 body_b: 0,
1072 })
1073 }
1074}
1075
1076fn polygon_vs_polygon(
1078 body_a: &RigidBody2D,
1079 verts_a: &[Vec2Fix],
1080 body_b: &RigidBody2D,
1081 verts_b: &[Vec2Fix],
1082) -> Option<Contact2D> {
1083 if verts_a.len() < 3 || verts_b.len() < 3 {
1084 return None;
1085 }
1086
1087 let world_a = transform_vertices(body_a, verts_a);
1088 let world_b = transform_vertices(body_b, verts_b);
1089
1090 let mut min_depth = Fix128::from_int(999_999);
1091 let mut best_normal = Vec2Fix::ZERO;
1092
1093 if let Some((depth, normal)) = sat_test_axes(&world_a, &world_b) {
1095 if depth < min_depth {
1096 min_depth = depth;
1097 best_normal = normal;
1098 }
1099 } else {
1100 return None; }
1102
1103 if let Some((depth, normal)) = sat_test_axes(&world_b, &world_a) {
1105 if depth < min_depth {
1106 min_depth = depth;
1107 best_normal = normal;
1108 }
1109 } else {
1110 return None; }
1112
1113 let center_a = polygon_centroid(&world_a);
1115 let center_b = polygon_centroid(&world_b);
1116 let ab = center_b - center_a;
1117 if ab.dot(best_normal).is_negative() {
1118 best_normal = -best_normal;
1119 }
1120
1121 let point = (center_a + center_b) * Fix128::from_ratio(1, 2);
1123
1124 Some(Contact2D {
1125 point,
1126 normal: best_normal,
1127 depth: min_depth,
1128 body_a: 0,
1129 body_b: 0,
1130 })
1131}
1132
1133fn sat_test_axes(poly_ref: &[Vec2Fix], poly_test: &[Vec2Fix]) -> Option<(Fix128, Vec2Fix)> {
1137 let n = poly_ref.len();
1138 let mut min_depth = Fix128::from_int(999_999);
1139 let mut best_normal = Vec2Fix::ZERO;
1140
1141 for i in 0..n {
1142 let a = poly_ref[i];
1143 let b = poly_ref[(i + 1) % n];
1144 let edge = b - a;
1145 let normal = Vec2Fix::new(edge.y, -edge.x).normalize();
1146
1147 let (min_a, max_a) = project_polygon(poly_ref, normal);
1149 let (min_b, max_b) = project_polygon(poly_test, normal);
1150
1151 if max_a < min_b || max_b < min_a {
1153 return None; }
1155
1156 let overlap1 = max_a - min_b;
1158 let overlap2 = max_b - min_a;
1159 let depth = if overlap1 < overlap2 {
1160 overlap1
1161 } else {
1162 overlap2
1163 };
1164
1165 if depth < min_depth {
1166 min_depth = depth;
1167 best_normal = normal;
1168 }
1169 }
1170
1171 Some((min_depth, best_normal))
1172}
1173
1174fn project_polygon(verts: &[Vec2Fix], axis: Vec2Fix) -> (Fix128, Fix128) {
1176 let mut min_proj = verts[0].dot(axis);
1177 let mut max_proj = min_proj;
1178
1179 for v in verts.iter().skip(1) {
1180 let p = v.dot(axis);
1181 if p < min_proj {
1182 min_proj = p;
1183 }
1184 if p > max_proj {
1185 max_proj = p;
1186 }
1187 }
1188
1189 (min_proj, max_proj)
1190}
1191
1192fn polygon_centroid(verts: &[Vec2Fix]) -> Vec2Fix {
1194 let mut sum = Vec2Fix::ZERO;
1195 for v in verts {
1196 sum = sum + *v;
1197 }
1198 let n = Fix128::from_int(verts.len() as i64);
1199 sum / n
1200}
1201
1202fn capsule_vs_circle(
1208 body_cap: &RigidBody2D,
1209 cap_radius: Fix128,
1210 cap_half_length: Fix128,
1211 circle_pos: Vec2Fix,
1212 circle_radius: Fix128,
1213) -> Option<Contact2D> {
1214 let local_a = Vec2Fix::new(-cap_half_length, Fix128::ZERO);
1216 let local_b = Vec2Fix::new(cap_half_length, Fix128::ZERO);
1217 let seg_a = body_cap.world_point(local_a);
1218 let seg_b = body_cap.world_point(local_b);
1219
1220 let closest = closest_point_on_segment(seg_a, seg_b, circle_pos);
1222
1223 let sum_r = cap_radius + circle_radius;
1225 let delta = circle_pos - closest;
1226 let dist_sq = delta.length_squared();
1227 let sum_r_sq = sum_r * sum_r;
1228
1229 if dist_sq > sum_r_sq {
1230 return None;
1231 }
1232
1233 let dist = dist_sq.sqrt();
1234 let normal = if dist.is_zero() {
1235 Vec2Fix::UNIT_Y
1236 } else {
1237 delta / dist
1238 };
1239
1240 let depth = sum_r - dist;
1241 let point = closest + normal * cap_radius;
1242
1243 Some(Contact2D {
1244 point,
1245 normal,
1246 depth,
1247 body_a: 0,
1248 body_b: 0,
1249 })
1250}
1251
1252fn edge_vs_circle(
1256 edge_body: &RigidBody2D,
1257 local_start: Vec2Fix,
1258 local_end: Vec2Fix,
1259 circle_pos: Vec2Fix,
1260 circle_radius: Fix128,
1261) -> Option<Contact2D> {
1262 let world_start = edge_body.world_point(local_start);
1263 let world_end = edge_body.world_point(local_end);
1264
1265 let closest = closest_point_on_segment(world_start, world_end, circle_pos);
1266 let delta = circle_pos - closest;
1267 let dist_sq = delta.length_squared();
1268 let r_sq = circle_radius * circle_radius;
1269
1270 if dist_sq > r_sq {
1271 return None;
1272 }
1273
1274 let dist = dist_sq.sqrt();
1275 let normal = if dist.is_zero() {
1276 let edge = world_end - world_start;
1278 Vec2Fix::new(edge.y, -edge.x).normalize()
1279 } else {
1280 delta / dist
1281 };
1282
1283 let depth = circle_radius - dist;
1284
1285 Some(Contact2D {
1286 point: closest,
1287 normal,
1288 depth,
1289 body_a: 0,
1290 body_b: 0,
1291 })
1292}
1293
1294fn closest_point_on_segment(seg_a: Vec2Fix, seg_b: Vec2Fix, point: Vec2Fix) -> Vec2Fix {
1296 let ab = seg_b - seg_a;
1297 let len_sq = ab.length_squared();
1298 if len_sq.is_zero() {
1299 return seg_a;
1300 }
1301 let t = (point - seg_a).dot(ab) / len_sq;
1302 let t_clamped = if t.is_negative() {
1304 Fix128::ZERO
1305 } else if t > Fix128::ONE {
1306 Fix128::ONE
1307 } else {
1308 t
1309 };
1310 seg_a + ab * t_clamped
1311}
1312
1313fn rounded_core(body: &RigidBody2D) -> Option<(Vec<Vec2Fix>, Fix128)> {
1321 match &body.shape {
1322 Shape2D::Capsule {
1323 radius,
1324 half_length,
1325 } => Some((
1326 vec![
1327 body.world_point(Vec2Fix::new(-*half_length, Fix128::ZERO)),
1328 body.world_point(Vec2Fix::new(*half_length, Fix128::ZERO)),
1329 ],
1330 *radius,
1331 )),
1332 Shape2D::Edge { start, end } => Some((
1333 vec![body.world_point(*start), body.world_point(*end)],
1334 Fix128::ZERO,
1335 )),
1336 Shape2D::Polygon { vertices } if vertices.len() >= 3 => {
1337 Some((transform_vertices(body, vertices), Fix128::ZERO))
1338 }
1339 _ => None,
1340 }
1341}
1342
1343fn core_edges(core: &[Vec2Fix]) -> Vec<(Vec2Fix, Vec2Fix)> {
1345 if core.len() == 2 {
1346 return vec![(core[0], core[1])];
1347 }
1348 (0..core.len())
1349 .map(|i| (core[i], core[(i + 1) % core.len()]))
1350 .collect()
1351}
1352
1353fn rounded_core_vs_rounded_core(
1368 core_a: &[Vec2Fix],
1369 radius_a: Fix128,
1370 core_b: &[Vec2Fix],
1371 radius_b: Fix128,
1372) -> Option<Contact2D> {
1373 let edges_a = core_edges(core_a);
1374 let edges_b = core_edges(core_b);
1375 let radius_sum = radius_a + radius_b;
1376
1377 let mut closest: Option<(Fix128, Vec2Fix, Vec2Fix)> = None;
1379 for &(pa, qa) in &edges_a {
1380 for &(pb, qb) in &edges_b {
1381 let (ca, cb) = closest_points_segment_segment(pa, qa, pb, qb);
1382 let dist_sq = (cb - ca).length_squared();
1383 if closest.is_none_or(|(d, _, _)| dist_sq < d) {
1384 closest = Some((dist_sq, ca, cb));
1385 }
1386 }
1387 }
1388 let (dist_sq, ca, cb) = closest?;
1389
1390 let mut axes: Vec<Vec2Fix> = edges_a
1397 .iter()
1398 .chain(edges_b.iter())
1399 .map(|&(p, q)| (q - p).perpendicular().normalize())
1400 .collect();
1401 axes.push((cb - ca).normalize());
1402 let mut separated = false;
1403 let mut best: Option<(Fix128, Vec2Fix)> = None;
1404 for axis in axes {
1405 if axis == Vec2Fix::ZERO {
1406 continue;
1407 }
1408 let (min_a, max_a) = project_polygon(core_a, axis);
1409 let (min_b, max_b) = project_polygon(core_b, axis);
1410 if max_a < min_b || max_b < min_a {
1411 separated = true;
1412 break;
1413 }
1414 for (overlap, dir) in [(max_a - min_b, axis), (max_b - min_a, -axis)] {
1416 if best.is_none_or(|(d, _)| overlap < d) {
1417 best = Some((overlap, dir));
1418 }
1419 }
1420 }
1421
1422 let (normal, depth) = if separated {
1423 if dist_sq > radius_sum * radius_sum {
1424 return None;
1425 }
1426 let dist = dist_sq.sqrt();
1427 if dist.is_zero() {
1428 return None;
1429 }
1430 ((cb - ca) / dist, radius_sum - dist)
1431 } else {
1432 match best {
1433 Some((overlap, dir)) => (dir, overlap + radius_sum),
1434 None => (Vec2Fix::UNIT_Y, radius_sum),
1436 }
1437 };
1438 if depth.is_negative() {
1439 return None;
1440 }
1441
1442 Some(Contact2D {
1443 point: overlap_midpoint(core_a, radius_a, core_b, radius_b, normal),
1444 normal,
1445 depth,
1446 body_a: 0,
1447 body_b: 0,
1448 })
1449}
1450
1451fn overlap_midpoint(
1453 core_a: &[Vec2Fix],
1454 radius_a: Fix128,
1455 core_b: &[Vec2Fix],
1456 radius_b: Fix128,
1457 normal: Vec2Fix,
1458) -> Vec2Fix {
1459 let eps = Fix128::from_ratio(1, 1 << 30);
1462 let tangent = normal.perpendicular();
1463 let (_, top_a) = project_polygon(core_a, normal);
1464 let (bottom_b, _) = project_polygon(core_b, normal);
1465 let extent = |core: &[Vec2Fix], on_feature: &dyn Fn(Fix128) -> bool| {
1466 let mut lo: Option<Fix128> = None;
1467 let mut hi: Option<Fix128> = None;
1468 for v in core {
1469 if on_feature(v.dot(normal)) {
1470 let t = v.dot(tangent);
1471 lo = Some(lo.map_or(t, |l| if t < l { t } else { l }));
1472 hi = Some(hi.map_or(t, |h| if t > h { t } else { h }));
1473 }
1474 }
1475 (lo.unwrap_or(Fix128::ZERO), hi.unwrap_or(Fix128::ZERO))
1476 };
1477 let (lo_a, hi_a) = extent(core_a, &|d| d >= top_a - eps);
1478 let (lo_b, hi_b) = extent(core_b, &|d| d <= bottom_b + eps);
1479 let lo = if lo_a > lo_b { lo_a } else { lo_b };
1480 let hi = if hi_a < hi_b { hi_a } else { hi_b };
1481 let along_tangent = (lo + hi).half();
1482 let along_normal = ((top_a + radius_a) + (bottom_b - radius_b)).half();
1483 tangent * along_tangent + normal * along_normal
1484}
1485
1486fn closest_points_segment_segment(
1489 p1: Vec2Fix,
1490 q1: Vec2Fix,
1491 p2: Vec2Fix,
1492 q2: Vec2Fix,
1493) -> (Vec2Fix, Vec2Fix) {
1494 let clamp01 = |x: Fix128| {
1495 if x.is_negative() {
1496 Fix128::ZERO
1497 } else if x > Fix128::ONE {
1498 Fix128::ONE
1499 } else {
1500 x
1501 }
1502 };
1503 let d1 = q1 - p1;
1504 let d2 = q2 - p2;
1505 let r = p1 - p2;
1506 let a = d1.length_squared();
1507 let e = d2.length_squared();
1508 let f = d2.dot(r);
1509 let (s, t) = if a.is_zero() && e.is_zero() {
1510 (Fix128::ZERO, Fix128::ZERO)
1511 } else if a.is_zero() {
1512 (Fix128::ZERO, clamp01(f / e))
1513 } else {
1514 let c = d1.dot(r);
1515 if e.is_zero() {
1516 (clamp01(-c / a), Fix128::ZERO)
1517 } else {
1518 let b = d1.dot(d2);
1519 let denom = a * e - b * b;
1520 let s = if denom.is_zero() {
1521 Fix128::ZERO
1522 } else {
1523 clamp01((b * f - c * e) / denom)
1524 };
1525 let t = (b * s + f) / e;
1526 if t.is_negative() {
1527 (clamp01(-c / a), Fix128::ZERO)
1528 } else if t > Fix128::ONE {
1529 (clamp01((b - c) / a), Fix128::ONE)
1530 } else {
1531 (s, t)
1532 }
1533 }
1534 };
1535 (p1 + d1 * s, p2 + d2 * t)
1536}
1537
1538fn solve_contact_position(bodies: &mut [RigidBody2D], contact: &Contact2D) -> Fix128 {
1547 {
1548 let a = contact.body_a;
1549 let b = contact.body_b;
1550 let normal = contact.normal;
1551 let depth = contact.depth;
1552
1553 if depth.is_negative() || depth.is_zero() {
1554 return Fix128::ZERO;
1555 }
1556
1557 let inv_mass_a = bodies[a].inv_mass;
1558 let inv_mass_b = bodies[b].inv_mass;
1559 let total_inv_mass = inv_mass_a + inv_mass_b;
1560
1561 if total_inv_mass.is_zero() {
1562 return Fix128::ZERO;
1563 }
1564
1565 let correction = normal * depth;
1566 let ratio_a = inv_mass_a / total_inv_mass;
1567 let ratio_b = inv_mass_b / total_inv_mass;
1568
1569 if bodies[a].body_type == BodyType2D::Dynamic {
1570 bodies[a].position = bodies[a].position - correction * ratio_a;
1571 }
1572 if bodies[b].body_type == BodyType2D::Dynamic {
1573 bodies[b].position = bodies[b].position + correction * ratio_b;
1574 }
1575
1576 let r_a = contact.point - bodies[a].position;
1578 let r_b = contact.point - bodies[b].position;
1579 let rn_a = r_a.cross_scalar(normal);
1580 let rn_b = r_b.cross_scalar(normal);
1581
1582 let ang_inv_a = bodies[a].inv_inertia * rn_a * rn_a;
1583 let ang_inv_b = bodies[b].inv_inertia * rn_b * rn_b;
1584 let total_ang = ang_inv_a + ang_inv_b;
1585
1586 if !total_ang.is_zero() {
1587 let ang_correction = depth * Fix128::from_ratio(1, 4);
1588 if bodies[a].body_type == BodyType2D::Dynamic {
1589 let da =
1590 rn_a * ang_correction * bodies[a].inv_inertia / (total_inv_mass + total_ang);
1591 bodies[a].angle = bodies[a].angle - da;
1592 }
1593 if bodies[b].body_type == BodyType2D::Dynamic {
1594 let db =
1595 rn_b * ang_correction * bodies[b].inv_inertia / (total_inv_mass + total_ang);
1596 bodies[b].angle = bodies[b].angle + db;
1597 }
1598 }
1599 depth
1600 }
1601}
1602
1603#[derive(Clone, Debug)]
1609pub enum Joint2D {
1610 Revolute {
1612 body_a: usize,
1614 body_b: usize,
1616 local_anchor_a: Vec2Fix,
1618 local_anchor_b: Vec2Fix,
1620 compliance: Fix128,
1622 },
1623 Distance {
1625 body_a: usize,
1627 body_b: usize,
1629 local_anchor_a: Vec2Fix,
1631 local_anchor_b: Vec2Fix,
1633 target_distance: Fix128,
1635 compliance: Fix128,
1637 },
1638 Weld {
1640 body_a: usize,
1642 body_b: usize,
1644 local_anchor_a: Vec2Fix,
1646 local_anchor_b: Vec2Fix,
1648 reference_angle: Fix128,
1650 compliance: Fix128,
1652 },
1653 Mouse {
1663 body: usize,
1665 target: Vec2Fix,
1667 max_force: Fix128,
1670 stiffness: Fix128,
1673 damping: Fix128,
1677 },
1678}
1679
1680pub fn solve_joints_2d(bodies: &mut [RigidBody2D], joints: &[Joint2D], sub_dt: Fix128) {
1686 let mut lambdas = vec![Vec2Fix::ZERO; joints.len()];
1687 solve_joints_2d_accumulated(bodies, joints, sub_dt, &mut lambdas);
1688}
1689
1690fn solve_joints_2d_accumulated(
1694 bodies: &mut [RigidBody2D],
1695 joints: &[Joint2D],
1696 sub_dt: Fix128,
1697 lambdas: &mut [Vec2Fix],
1698) {
1699 let alpha = if sub_dt.is_zero() {
1700 Fix128::ZERO
1701 } else {
1702 Fix128::ONE / (sub_dt * sub_dt)
1703 };
1704
1705 for (joint, lambda) in joints.iter().zip(lambdas.iter_mut()) {
1706 match joint {
1707 Joint2D::Revolute {
1708 body_a,
1709 body_b,
1710 local_anchor_a,
1711 local_anchor_b,
1712 compliance,
1713 } => {
1714 solve_revolute(
1715 bodies,
1716 *body_a,
1717 *body_b,
1718 *local_anchor_a,
1719 *local_anchor_b,
1720 *compliance,
1721 alpha,
1722 );
1723 }
1724 Joint2D::Distance {
1725 body_a,
1726 body_b,
1727 local_anchor_a,
1728 local_anchor_b,
1729 target_distance,
1730 compliance,
1731 } => {
1732 solve_distance(
1733 bodies,
1734 *body_a,
1735 *body_b,
1736 *local_anchor_a,
1737 *local_anchor_b,
1738 *target_distance,
1739 *compliance,
1740 alpha,
1741 &mut lambda.x,
1742 );
1743 }
1744 Joint2D::Weld {
1745 body_a,
1746 body_b,
1747 local_anchor_a,
1748 local_anchor_b,
1749 reference_angle,
1750 compliance,
1751 } => {
1752 solve_weld(
1753 bodies,
1754 *body_a,
1755 *body_b,
1756 *local_anchor_a,
1757 *local_anchor_b,
1758 *reference_angle,
1759 *compliance,
1760 alpha,
1761 );
1762 }
1763 Joint2D::Mouse {
1764 body,
1765 target,
1766 max_force,
1767 stiffness,
1768 damping,
1769 } => {
1770 solve_mouse(
1771 bodies, *body, *target, *max_force, *stiffness, *damping, sub_dt, lambda,
1772 );
1773 }
1774 }
1775 }
1776}
1777
1778fn solve_revolute(
1780 bodies: &mut [RigidBody2D],
1781 a: usize,
1782 b: usize,
1783 local_a: Vec2Fix,
1784 local_b: Vec2Fix,
1785 compliance: Fix128,
1786 alpha: Fix128,
1787) {
1788 let world_a = bodies[a].world_point(local_a);
1789 let world_b = bodies[b].world_point(local_b);
1790 let delta = world_b - world_a;
1791 let dist_sq = delta.length_squared();
1792 if dist_sq.is_zero() {
1793 return;
1794 }
1795 let dist = dist_sq.sqrt();
1796 let n = delta / dist;
1797
1798 let inv_mass_a = bodies[a].inv_mass;
1799 let inv_mass_b = bodies[b].inv_mass;
1800
1801 let r_a = world_a - bodies[a].position;
1802 let r_b = world_b - bodies[b].position;
1803 let rn_a = r_a.cross_scalar(n);
1804 let rn_b = r_b.cross_scalar(n);
1805
1806 let w = inv_mass_a
1807 + inv_mass_b
1808 + rn_a * rn_a * bodies[a].inv_inertia
1809 + rn_b * rn_b * bodies[b].inv_inertia
1810 + compliance * alpha;
1811
1812 if w.is_zero() {
1813 return;
1814 }
1815
1816 let lambda = -dist / w;
1817 let p = n * lambda;
1818
1819 if bodies[a].body_type == BodyType2D::Dynamic {
1820 bodies[a].position = bodies[a].position - p * inv_mass_a;
1821 bodies[a].angle = bodies[a].angle - rn_a * lambda * bodies[a].inv_inertia;
1822 }
1823 if bodies[b].body_type == BodyType2D::Dynamic {
1824 bodies[b].position = bodies[b].position + p * inv_mass_b;
1825 bodies[b].angle = bodies[b].angle + rn_b * lambda * bodies[b].inv_inertia;
1826 }
1827}
1828
1829#[allow(clippy::too_many_arguments)]
1831fn solve_distance(
1832 bodies: &mut [RigidBody2D],
1833 a: usize,
1834 b: usize,
1835 local_a: Vec2Fix,
1836 local_b: Vec2Fix,
1837 target_distance: Fix128,
1838 compliance: Fix128,
1839 alpha: Fix128,
1840 accumulated: &mut Fix128,
1841) {
1842 let world_a = bodies[a].world_point(local_a);
1843 let world_b = bodies[b].world_point(local_b);
1844 let delta = world_b - world_a;
1845 let dist = delta.length();
1846
1847 if dist.is_zero() {
1848 return;
1849 }
1850 let n = delta / dist;
1851
1852 let c = dist - target_distance;
1853
1854 let inv_mass_a = bodies[a].inv_mass;
1855 let inv_mass_b = bodies[b].inv_mass;
1856
1857 let r_a = world_a - bodies[a].position;
1858 let r_b = world_b - bodies[b].position;
1859 let rn_a = r_a.cross_scalar(n);
1860 let rn_b = r_b.cross_scalar(n);
1861
1862 let w = inv_mass_a
1863 + inv_mass_b
1864 + rn_a * rn_a * bodies[a].inv_inertia
1865 + rn_b * rn_b * bodies[b].inv_inertia
1866 + compliance * alpha;
1867
1868 if w.is_zero() {
1869 return;
1870 }
1871
1872 let lambda = (-c - compliance * alpha * *accumulated) / w;
1875 *accumulated = *accumulated + lambda;
1876 let p = n * lambda;
1877
1878 if bodies[a].body_type == BodyType2D::Dynamic {
1879 bodies[a].position = bodies[a].position - p * inv_mass_a;
1880 bodies[a].angle = bodies[a].angle - rn_a * lambda * bodies[a].inv_inertia;
1881 }
1882 if bodies[b].body_type == BodyType2D::Dynamic {
1883 bodies[b].position = bodies[b].position + p * inv_mass_b;
1884 bodies[b].angle = bodies[b].angle + rn_b * lambda * bodies[b].inv_inertia;
1885 }
1886}
1887
1888#[allow(clippy::too_many_arguments)]
1890fn solve_weld(
1891 bodies: &mut [RigidBody2D],
1892 a: usize,
1893 b: usize,
1894 local_a: Vec2Fix,
1895 local_b: Vec2Fix,
1896 reference_angle: Fix128,
1897 compliance: Fix128,
1898 alpha: Fix128,
1899) {
1900 solve_revolute(bodies, a, b, local_a, local_b, compliance, alpha);
1902
1903 let angle_error = bodies[b].angle - bodies[a].angle - reference_angle;
1905 if angle_error.is_zero() {
1906 return;
1907 }
1908
1909 let inv_i_a = bodies[a].inv_inertia;
1910 let inv_i_b = bodies[b].inv_inertia;
1911 let w = inv_i_a + inv_i_b + compliance * alpha;
1912
1913 if w.is_zero() {
1914 return;
1915 }
1916
1917 let lambda = -angle_error / w;
1918
1919 if bodies[a].body_type == BodyType2D::Dynamic {
1920 bodies[a].angle = bodies[a].angle - lambda * inv_i_a;
1921 }
1922 if bodies[b].body_type == BodyType2D::Dynamic {
1923 bodies[b].angle = bodies[b].angle + lambda * inv_i_b;
1924 }
1925}
1926
1927#[allow(clippy::too_many_arguments)]
1929fn solve_mouse(
1930 bodies: &mut [RigidBody2D],
1931 body_idx: usize,
1932 target: Vec2Fix,
1933 max_force: Fix128,
1934 stiffness: Fix128,
1935 damping: Fix128,
1936 sub_dt: Fix128,
1937 accumulated: &mut Vec2Fix,
1938) {
1939 let body = &bodies[body_idx];
1940 if body.body_type != BodyType2D::Dynamic || sub_dt.is_zero() {
1941 return;
1942 }
1943 let h = sub_dt;
1949 let kh2 = stiffness * h * h;
1950 let ch = damping * h;
1951 let w = body.inv_mass;
1952 let c = body.position - target;
1953 let moved = body.position - body.prev_position;
1954 let denom = (kh2 + ch) * w + Fix128::ONE;
1955 if denom.is_zero() {
1956 return;
1957 }
1958 let numer = c * (Fix128::ZERO - kh2) - *accumulated - moved * ch;
1959 let mut total = *accumulated + numer / denom;
1960 let limit = if max_force > Fix128::ZERO {
1961 max_force * h * h
1962 } else {
1963 Fix128::ZERO
1964 };
1965 let len = total.length();
1966 if len > limit {
1967 total = if len.is_zero() {
1968 Vec2Fix::ZERO
1969 } else {
1970 total * (limit / len)
1971 };
1972 }
1973 let dlambda = total - *accumulated;
1974 *accumulated = total;
1975 bodies[body_idx].position = bodies[body_idx].position + dlambda * w;
1976}
1977
1978#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1999#[non_exhaustive]
2000pub struct AngularTether2D {
2001 pub body: usize,
2003 pub target_angle: Fix128,
2005 pub stiffness: Fix128,
2007 pub damping: Fix128,
2009}
2010
2011impl AngularTether2D {
2012 #[must_use]
2014 pub const fn new(
2015 body: usize,
2016 target_angle: Fix128,
2017 stiffness: Fix128,
2018 damping: Fix128,
2019 ) -> Self {
2020 Self {
2021 body,
2022 target_angle,
2023 stiffness,
2024 damping,
2025 }
2026 }
2027
2028 #[must_use]
2031 pub fn critically_damped(
2032 body: usize,
2033 target_angle: Fix128,
2034 inertia: Fix128,
2035 omega: Fix128,
2036 ) -> Self {
2037 Self::new(
2038 body,
2039 target_angle,
2040 inertia * omega * omega,
2041 Fix128::from_int(2) * inertia * omega,
2042 )
2043 }
2044}
2045
2046#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2060#[non_exhaustive]
2061pub struct KinematicDrive2D {
2062 pub body: usize,
2064 pub target_position: Vec2Fix,
2066 pub target_angle: Fix128,
2068 pub omega: Fix128,
2071}
2072
2073impl KinematicDrive2D {
2074 #[must_use]
2076 pub const fn new(
2077 body: usize,
2078 target_position: Vec2Fix,
2079 target_angle: Fix128,
2080 omega: Fix128,
2081 ) -> Self {
2082 Self {
2083 body,
2084 target_position,
2085 target_angle,
2086 omega,
2087 }
2088 }
2089}
2090
2091#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
2093pub struct AngularTetherId(usize);
2094
2095#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
2097pub struct KinematicDriveId(usize);
2098
2099#[derive(Clone, Debug, Default)]
2106pub struct Tethers2D {
2107 angular: Vec<Option<AngularTether2D>>,
2108 drives: Vec<Option<KinematicDrive2D>>,
2109}
2110
2111impl Tethers2D {
2112 #[must_use]
2114 pub const fn new() -> Self {
2115 Self {
2116 angular: Vec::new(),
2117 drives: Vec::new(),
2118 }
2119 }
2120
2121 pub fn add_angular(&mut self, tether: AngularTether2D) -> AngularTetherId {
2123 self.angular.push(Some(tether));
2124 AngularTetherId(self.angular.len() - 1)
2125 }
2126
2127 pub fn remove_angular(&mut self, id: AngularTetherId) -> Option<AngularTether2D> {
2129 self.angular.get_mut(id.0).and_then(Option::take)
2130 }
2131
2132 #[must_use]
2134 pub fn angular(&self, id: AngularTetherId) -> Option<&AngularTether2D> {
2135 self.angular.get(id.0).and_then(Option::as_ref)
2136 }
2137
2138 pub fn angular_mut(&mut self, id: AngularTetherId) -> Option<&mut AngularTether2D> {
2140 self.angular.get_mut(id.0).and_then(Option::as_mut)
2141 }
2142
2143 pub fn add_drive(&mut self, drive: KinematicDrive2D) -> KinematicDriveId {
2145 self.drives.push(Some(drive));
2146 KinematicDriveId(self.drives.len() - 1)
2147 }
2148
2149 pub fn remove_drive(&mut self, id: KinematicDriveId) -> Option<KinematicDrive2D> {
2152 self.drives.get_mut(id.0).and_then(Option::take)
2153 }
2154
2155 #[must_use]
2157 pub fn drive(&self, id: KinematicDriveId) -> Option<&KinematicDrive2D> {
2158 self.drives.get(id.0).and_then(Option::as_ref)
2159 }
2160
2161 pub fn drive_mut(&mut self, id: KinematicDriveId) -> Option<&mut KinematicDrive2D> {
2163 self.drives.get_mut(id.0).and_then(Option::as_mut)
2164 }
2165
2166 #[must_use]
2168 pub fn angular_count(&self) -> usize {
2169 self.angular.iter().filter(|t| t.is_some()).count()
2170 }
2171
2172 #[must_use]
2174 pub fn drive_count(&self) -> usize {
2175 self.drives.iter().filter(|d| d.is_some()).count()
2176 }
2177
2178 fn live_drives(&self) -> impl Iterator<Item = &KinematicDrive2D> {
2179 self.drives.iter().flatten()
2180 }
2181}
2182
2183pub(crate) fn exp_neg(x: Fix128) -> Fix128 {
2187 if x <= Fix128::ZERO {
2188 return Fix128::ONE;
2189 }
2190 if x.hi >= 44 {
2191 return Fix128::ZERO;
2192 }
2193 let half = Fix128::from_ratio(1, 2);
2194 let mut y = x;
2195 let mut k = 0_u32;
2196 while y > half {
2197 y = y * half;
2198 k += 1;
2199 }
2200 let mut sum = Fix128::ONE;
2202 let mut term = Fix128::ONE;
2203 for n in 1..=26_i64 {
2204 term = Fix128::ZERO - term * y / Fix128::from_int(n);
2205 sum = sum + term;
2206 }
2207 for _ in 0..k {
2208 sum = sum * sum;
2209 }
2210 sum
2211}
2212
2213fn apply_kinematic_drive(bodies: &mut [RigidBody2D], drive: &KinematicDrive2D, h: Fix128) {
2215 let Some(body) = bodies.get_mut(drive.body) else {
2216 return;
2217 };
2218 if body.body_type != BodyType2D::Kinematic || drive.omega <= Fix128::ZERO {
2219 return;
2220 }
2221 let w = drive.omega;
2222 let e_factor = exp_neg(w * h);
2223 let e0 = body.prev_position - drive.target_position;
2225 let v0 = body.velocity;
2226 let s = v0 + e0 * w;
2227 body.position = drive.target_position + (e0 + s * h) * e_factor;
2228 body.velocity = (v0 - s * (w * h)) * e_factor;
2229 let a0 = body.prev_angle - drive.target_angle;
2231 let om0 = body.angular_velocity;
2232 let sa = om0 + w * a0;
2233 body.angle = drive.target_angle + (a0 + sa * h) * e_factor;
2234 body.angular_velocity = (om0 - sa * (w * h)) * e_factor;
2235}
2236
2237fn solve_angular_tether(
2239 bodies: &mut [RigidBody2D],
2240 tether: &AngularTether2D,
2241 h: Fix128,
2242 accumulated: &mut Fix128,
2243) {
2244 let Some(body) = bodies.get_mut(tether.body) else {
2245 return;
2246 };
2247 if body.body_type != BodyType2D::Dynamic || h.is_zero() {
2248 return;
2249 }
2250 let kh2 = tether.stiffness * h * h;
2253 let ch = tether.damping * h;
2254 let w = body.inv_inertia;
2255 let c = body.angle - tether.target_angle;
2256 let moved = body.angle - body.prev_angle;
2257 let denom = (kh2 + ch) * w + Fix128::ONE;
2258 if denom.is_zero() {
2259 return;
2260 }
2261 let numer = Fix128::ZERO - kh2 * c - *accumulated - ch * moved;
2262 let dlambda = numer / denom;
2263 *accumulated = *accumulated + dlambda;
2264 body.angle = body.angle + dlambda * w;
2265}
2266
2267impl core::fmt::Debug for PhysicsWorld2D {
2268 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2269 f.debug_struct("PhysicsWorld2D")
2270 .field("bodies", &format_args!("[{} items]", self.bodies.len()))
2271 .field("gravity", &self.gravity)
2272 .field("config", &self.config)
2273 .field("joints", &format_args!("[{} items]", self.joints.len()))
2274 .finish()
2275 }
2276}
2277
2278#[cfg(all(test, feature = "std"))]
2283mod tests {
2284 use super::*;
2285
2286 #[test]
2289 fn test_vec2_new_and_constants() {
2290 let v = Vec2Fix::new(Fix128::from_int(3), Fix128::from_int(4));
2291 assert_eq!(v.x.hi, 3);
2292 assert_eq!(v.y.hi, 4);
2293 assert!(Vec2Fix::ZERO.x.is_zero());
2294 assert!(Vec2Fix::ZERO.y.is_zero());
2295 assert_eq!(Vec2Fix::ONE.x.hi, 1);
2296 assert_eq!(Vec2Fix::ONE.y.hi, 1);
2297 assert_eq!(Vec2Fix::UNIT_X.x.hi, 1);
2298 assert!(Vec2Fix::UNIT_X.y.is_zero());
2299 assert!(Vec2Fix::UNIT_Y.x.is_zero());
2300 assert_eq!(Vec2Fix::UNIT_Y.y.hi, 1);
2301 }
2302
2303 #[test]
2304 fn test_vec2_from_int() {
2305 let v = Vec2Fix::from_int(7, -3);
2306 assert_eq!(v.x.hi, 7);
2307 assert_eq!(v.y.hi, -3);
2308 }
2309
2310 #[test]
2311 fn test_vec2_add_sub() {
2312 let a = Vec2Fix::from_int(3, 5);
2313 let b = Vec2Fix::from_int(1, 2);
2314 let sum = a + b;
2315 assert_eq!(sum.x.hi, 4);
2316 assert_eq!(sum.y.hi, 7);
2317 let diff = a - b;
2318 assert_eq!(diff.x.hi, 2);
2319 assert_eq!(diff.y.hi, 3);
2320 }
2321
2322 #[test]
2323 fn test_vec2_mul_div_scalar() {
2324 let v = Vec2Fix::from_int(6, 8);
2325 let scaled = v * Fix128::from_int(3);
2326 assert_eq!(scaled.x.hi, 18);
2327 assert_eq!(scaled.y.hi, 24);
2328 let halved = v / Fix128::from_int(2);
2329 assert_eq!(halved.x.hi, 3);
2330 assert_eq!(halved.y.hi, 4);
2331 }
2332
2333 #[test]
2334 fn test_vec2_neg() {
2335 let v = Vec2Fix::from_int(5, -3);
2336 let neg_v = -v;
2337 assert_eq!(neg_v.x.hi, -5);
2338 assert_eq!(neg_v.y.hi, 3);
2339 }
2340
2341 #[test]
2342 fn test_vec2_dot() {
2343 let a = Vec2Fix::from_int(3, 4);
2344 let b = Vec2Fix::from_int(2, 5);
2345 let d = a.dot(b);
2346 assert_eq!(d.hi, 26);
2348 }
2349
2350 #[test]
2351 fn test_vec2_cross_scalar() {
2352 let a = Vec2Fix::from_int(3, 4);
2353 let b = Vec2Fix::from_int(2, 5);
2354 let c = a.cross_scalar(b);
2355 assert_eq!(c.hi, 7);
2357 }
2358
2359 #[test]
2360 fn test_vec2_length_squared() {
2361 let v = Vec2Fix::from_int(3, 4);
2362 let len_sq = v.length_squared();
2363 assert_eq!(len_sq.hi, 25);
2365 }
2366
2367 #[test]
2368 fn test_vec2_length() {
2369 let v = Vec2Fix::from_int(3, 4);
2370 let len = v.length();
2371 assert_eq!(len.hi, 5);
2373 }
2374
2375 #[test]
2376 fn test_vec2_normalize() {
2377 let v = Vec2Fix::from_int(0, 5);
2378 let n = v.normalize();
2379 assert!(n.x.is_zero());
2380 assert_eq!(n.y.hi, 1);
2381
2382 let z = Vec2Fix::ZERO.normalize();
2384 assert!(z.x.is_zero());
2385 assert!(z.y.is_zero());
2386 }
2387
2388 #[test]
2389 fn test_vec2_perpendicular() {
2390 let v = Vec2Fix::from_int(3, 4);
2391 let p = v.perpendicular();
2392 assert_eq!(p.x.hi, -4);
2393 assert_eq!(p.y.hi, 3);
2394 assert!(v.dot(p).is_zero());
2396 }
2397
2398 #[test]
2399 fn test_vec2_distance_to() {
2400 let a = Vec2Fix::from_int(0, 0);
2401 let b = Vec2Fix::from_int(3, 4);
2402 let d = a.distance_to(b);
2403 assert_eq!(d.hi, 5);
2404 }
2405
2406 #[test]
2407 fn test_vec2_lerp() {
2408 let a = Vec2Fix::from_int(0, 0);
2409 let b = Vec2Fix::from_int(10, 20);
2410 let half = Fix128::from_ratio(1, 2);
2411 let mid = a.lerp(b, half);
2412 assert_eq!(mid.x.hi, 5);
2413 assert_eq!(mid.y.hi, 10);
2414
2415 let at0 = a.lerp(b, Fix128::ZERO);
2417 assert_eq!(at0.x.hi, 0);
2418 assert_eq!(at0.y.hi, 0);
2419 let at1 = a.lerp(b, Fix128::ONE);
2420 assert_eq!(at1.x.hi, 10);
2421 assert_eq!(at1.y.hi, 20);
2422 }
2423
2424 #[test]
2425 fn test_vec2_rotate() {
2426 let v = Vec2Fix::UNIT_X;
2428 let rotated = v.rotate(Fix128::HALF_PI);
2429 assert!(rotated.x.abs() < Fix128::from_ratio(1, 1000));
2431 assert!((rotated.y - Fix128::ONE).abs() < Fix128::from_ratio(1, 1000));
2432 }
2433
2434 #[test]
2437 fn test_body_creation_dynamic() {
2438 let shape = Shape2D::Circle {
2439 radius: Fix128::ONE,
2440 };
2441 let body = RigidBody2D::new_dynamic(Vec2Fix::from_int(5, 10), Fix128::from_int(2), shape);
2442 assert_eq!(body.position.x.hi, 5);
2443 assert_eq!(body.position.y.hi, 10);
2444 assert_eq!(body.body_type, BodyType2D::Dynamic);
2445 assert!(!body.inv_mass.is_zero());
2446 }
2447
2448 #[test]
2449 fn test_body_creation_static() {
2450 let shape = Shape2D::Circle {
2451 radius: Fix128::from_int(5),
2452 };
2453 let body = RigidBody2D::new_static(Vec2Fix::ZERO, shape);
2454 assert_eq!(body.body_type, BodyType2D::Static);
2455 assert!(body.inv_mass.is_zero());
2456 assert!(body.inv_inertia.is_zero());
2457 }
2458
2459 #[test]
2460 fn test_body_creation_kinematic() {
2461 let shape = Shape2D::Circle {
2462 radius: Fix128::ONE,
2463 };
2464 let body = RigidBody2D::new_kinematic(Vec2Fix::from_int(1, 2), shape);
2465 assert_eq!(body.body_type, BodyType2D::Kinematic);
2466 assert!(body.inv_mass.is_zero());
2467 }
2468
2469 #[test]
2470 fn test_body_apply_impulse() {
2471 let shape = Shape2D::Circle {
2472 radius: Fix128::ONE,
2473 };
2474 let mut body = RigidBody2D::new_dynamic(Vec2Fix::ZERO, Fix128::ONE, shape);
2475 body.apply_impulse(Vec2Fix::from_int(10, 0));
2476 assert_eq!(body.velocity.x.hi, 10);
2477 assert!(body.velocity.y.is_zero());
2478 }
2479
2480 #[test]
2481 fn test_static_body_ignores_impulse() {
2482 let shape = Shape2D::Circle {
2483 radius: Fix128::ONE,
2484 };
2485 let mut body = RigidBody2D::new_static(Vec2Fix::ZERO, shape);
2486 body.apply_impulse(Vec2Fix::from_int(100, 100));
2487 assert!(body.velocity.x.is_zero());
2488 assert!(body.velocity.y.is_zero());
2489 }
2490
2491 #[test]
2492 fn test_body_world_point() {
2493 let shape = Shape2D::Circle {
2494 radius: Fix128::ONE,
2495 };
2496 let mut body = RigidBody2D::new_dynamic(Vec2Fix::from_int(10, 20), Fix128::ONE, shape);
2497 body.angle = Fix128::ZERO;
2498 let wp = body.world_point(Vec2Fix::from_int(1, 0));
2499 let dx = (wp.x - Fix128::from_int(11)).abs();
2503 let dy = (wp.y - Fix128::from_int(20)).abs();
2504 assert!(dx < Fix128::ONE, "world_point x off by more than 1");
2505 assert!(dy < Fix128::ONE, "world_point y off by more than 1");
2506 }
2507
2508 #[test]
2511 fn test_circle_vs_circle_collision() {
2512 let c = circle_vs_circle(
2513 Vec2Fix::from_int(0, 0),
2514 Fix128::ONE,
2515 Vec2Fix::from_int(1, 0),
2516 Fix128::ONE,
2517 );
2518 assert!(c.is_some());
2519 let contact = c.unwrap();
2520 assert_eq!(contact.depth.hi, 1); assert_eq!(contact.normal.x.hi, 1); assert!(contact.normal.y.is_zero());
2523 }
2524
2525 #[test]
2526 fn test_circle_vs_circle_no_collision() {
2527 let c = circle_vs_circle(
2528 Vec2Fix::from_int(0, 0),
2529 Fix128::ONE,
2530 Vec2Fix::from_int(5, 0),
2531 Fix128::ONE,
2532 );
2533 assert!(c.is_none());
2534 }
2535
2536 #[test]
2537 fn test_polygon_vs_polygon_collision() {
2538 let verts_a = vec![
2540 Vec2Fix::from_int(-1, -1),
2541 Vec2Fix::from_int(1, -1),
2542 Vec2Fix::from_int(1, 1),
2543 Vec2Fix::from_int(-1, 1),
2544 ];
2545 let verts_b = vec![
2546 Vec2Fix::from_int(-1, -1),
2547 Vec2Fix::from_int(1, -1),
2548 Vec2Fix::from_int(1, 1),
2549 Vec2Fix::from_int(-1, 1),
2550 ];
2551
2552 let body_a = RigidBody2D::new_dynamic(
2553 Vec2Fix::from_int(0, 0),
2554 Fix128::ONE,
2555 Shape2D::Polygon {
2556 vertices: verts_a.clone(),
2557 },
2558 );
2559 let body_b = RigidBody2D::new_dynamic(
2560 Vec2Fix::from_int(1, 0),
2561 Fix128::ONE,
2562 Shape2D::Polygon {
2563 vertices: verts_b.clone(),
2564 },
2565 );
2566
2567 let result = polygon_vs_polygon(&body_a, &verts_a, &body_b, &verts_b);
2568 assert!(result.is_some());
2569 let contact = result.unwrap();
2570 assert!(contact.depth > Fix128::ZERO);
2571 }
2572
2573 #[test]
2574 fn test_polygon_vs_polygon_no_collision() {
2575 let verts_a = vec![
2576 Vec2Fix::from_int(-1, -1),
2577 Vec2Fix::from_int(1, -1),
2578 Vec2Fix::from_int(1, 1),
2579 Vec2Fix::from_int(-1, 1),
2580 ];
2581 let verts_b = vec![
2582 Vec2Fix::from_int(-1, -1),
2583 Vec2Fix::from_int(1, -1),
2584 Vec2Fix::from_int(1, 1),
2585 Vec2Fix::from_int(-1, 1),
2586 ];
2587
2588 let body_a = RigidBody2D::new_dynamic(
2589 Vec2Fix::from_int(0, 0),
2590 Fix128::ONE,
2591 Shape2D::Polygon {
2592 vertices: verts_a.clone(),
2593 },
2594 );
2595 let body_b = RigidBody2D::new_dynamic(
2596 Vec2Fix::from_int(10, 0),
2597 Fix128::ONE,
2598 Shape2D::Polygon {
2599 vertices: verts_b.clone(),
2600 },
2601 );
2602
2603 let result = polygon_vs_polygon(&body_a, &verts_a, &body_b, &verts_b);
2604 assert!(result.is_none());
2605 }
2606
2607 #[test]
2608 fn test_circle_vs_polygon_collision() {
2609 let verts = vec![
2610 Vec2Fix::from_int(-2, -2),
2611 Vec2Fix::from_int(2, -2),
2612 Vec2Fix::from_int(2, 2),
2613 Vec2Fix::from_int(-2, 2),
2614 ];
2615 let poly_body = RigidBody2D::new_dynamic(
2616 Vec2Fix::from_int(0, 0),
2617 Fix128::ONE,
2618 Shape2D::Polygon {
2619 vertices: verts.clone(),
2620 },
2621 );
2622
2623 let result = circle_vs_polygon(Vec2Fix::from_int(2, 0), Fix128::ONE, &poly_body, &verts);
2624 assert!(result.is_some());
2625 }
2626
2627 #[test]
2628 fn test_circle_vs_polygon_no_collision() {
2629 let verts = vec![
2630 Vec2Fix::from_int(-1, -1),
2631 Vec2Fix::from_int(1, -1),
2632 Vec2Fix::from_int(1, 1),
2633 Vec2Fix::from_int(-1, 1),
2634 ];
2635 let poly_body = RigidBody2D::new_dynamic(
2636 Vec2Fix::from_int(0, 0),
2637 Fix128::ONE,
2638 Shape2D::Polygon {
2639 vertices: verts.clone(),
2640 },
2641 );
2642
2643 let result = circle_vs_polygon(Vec2Fix::from_int(10, 10), Fix128::ONE, &poly_body, &verts);
2644 assert!(result.is_none());
2645 }
2646
2647 #[test]
2648 fn test_capsule_vs_circle_collision() {
2649 let cap_body = RigidBody2D::new_dynamic(
2650 Vec2Fix::from_int(0, 0),
2651 Fix128::ONE,
2652 Shape2D::Capsule {
2653 radius: Fix128::ONE,
2654 half_length: Fix128::from_int(2),
2655 },
2656 );
2657 let result = capsule_vs_circle(
2658 &cap_body,
2659 Fix128::ONE,
2660 Fix128::from_int(2),
2661 Vec2Fix::from_int(3, 0),
2662 Fix128::ONE,
2663 );
2664 assert!(result.is_some());
2665 }
2666
2667 #[test]
2668 fn test_capsule_vs_circle_no_collision() {
2669 let cap_body = RigidBody2D::new_dynamic(
2670 Vec2Fix::from_int(0, 0),
2671 Fix128::ONE,
2672 Shape2D::Capsule {
2673 radius: Fix128::ONE,
2674 half_length: Fix128::from_int(2),
2675 },
2676 );
2677 let result = capsule_vs_circle(
2678 &cap_body,
2679 Fix128::ONE,
2680 Fix128::from_int(2),
2681 Vec2Fix::from_int(10, 10),
2682 Fix128::ONE,
2683 );
2684 assert!(result.is_none());
2685 }
2686
2687 #[test]
2688 fn test_edge_vs_circle_collision() {
2689 let edge_body = RigidBody2D::new_static(
2690 Vec2Fix::ZERO,
2691 Shape2D::Edge {
2692 start: Vec2Fix::from_int(-5, 0),
2693 end: Vec2Fix::from_int(5, 0),
2694 },
2695 );
2696 let result = edge_vs_circle(
2697 &edge_body,
2698 Vec2Fix::from_int(-5, 0),
2699 Vec2Fix::from_int(5, 0),
2700 Vec2Fix::new(Fix128::ZERO, Fix128::from_ratio(1, 2)),
2701 Fix128::ONE,
2702 );
2703 assert!(result.is_some());
2704 let contact = result.unwrap();
2705 assert!(contact.depth > Fix128::ZERO);
2706 }
2707
2708 #[test]
2711 fn test_falling_body() {
2712 let config = PhysicsConfig2D::default();
2713 let mut world = PhysicsWorld2D::new(config);
2714
2715 let shape = Shape2D::Circle {
2716 radius: Fix128::ONE,
2717 };
2718 let body = RigidBody2D::new_dynamic(Vec2Fix::from_int(0, 100), Fix128::ONE, shape);
2719 let id = world.add_body(body);
2720
2721 let dt = Fix128::from_ratio(1, 60);
2722 for _ in 0..60 {
2723 world.step(dt);
2724 }
2725
2726 assert!(
2728 world.bodies[id].position.y < Fix128::from_int(100),
2729 "Body should have fallen"
2730 );
2731 }
2732
2733 #[test]
2734 fn test_static_body_does_not_move() {
2735 let config = PhysicsConfig2D::default();
2736 let mut world = PhysicsWorld2D::new(config);
2737
2738 let shape = Shape2D::Circle {
2739 radius: Fix128::from_int(5),
2740 };
2741 let body = RigidBody2D::new_static(Vec2Fix::from_int(10, 20), shape);
2742 let id = world.add_body(body);
2743
2744 let dt = Fix128::from_ratio(1, 60);
2745 for _ in 0..120 {
2746 world.step(dt);
2747 }
2748
2749 assert_eq!(world.bodies[id].position.x.hi, 10);
2750 assert_eq!(world.bodies[id].position.y.hi, 20);
2751 }
2752
2753 #[test]
2754 fn test_kinematic_body_moves_by_velocity() {
2755 let config = PhysicsConfig2D::default();
2756 let mut world = PhysicsWorld2D::new(config);
2757
2758 let shape = Shape2D::Circle {
2759 radius: Fix128::ONE,
2760 };
2761 let mut body = RigidBody2D::new_kinematic(Vec2Fix::ZERO, shape);
2762 body.velocity = Vec2Fix::from_int(10, 0);
2763 let id = world.add_body(body);
2764
2765 let dt = Fix128::from_ratio(1, 60);
2766 for _ in 0..60 {
2767 world.step(dt);
2768 }
2769
2770 let x = world.bodies[id].position.x;
2772 assert!(x > Fix128::from_int(5));
2773 }
2774
2775 #[test]
2778 fn test_determinism_2d() {
2779 fn run_sim() -> (Vec2Fix, Fix128) {
2780 let config = PhysicsConfig2D {
2781 gravity: Vec2Fix::new(Fix128::ZERO, Fix128::from_int(-10)),
2782 substeps: 4,
2783 iterations: 8,
2784 damping: Fix128::from_ratio(99, 100),
2785 };
2786 let mut world = PhysicsWorld2D::new(config);
2787
2788 let shape = Shape2D::Circle {
2789 radius: Fix128::ONE,
2790 };
2791 let body =
2792 RigidBody2D::new_dynamic(Vec2Fix::from_int(5, 50), Fix128::from_ratio(3, 2), shape);
2793 world.add_body(body);
2794
2795 let dt = Fix128::from_ratio(1, 60);
2796 for _ in 0..120 {
2797 world.step(dt);
2798 }
2799
2800 (world.bodies[0].position, world.bodies[0].angle)
2801 }
2802
2803 let (pos1, angle1) = run_sim();
2804 let (pos2, angle2) = run_sim();
2805
2806 assert_eq!(pos1.x.hi, pos2.x.hi);
2808 assert_eq!(pos1.x.lo, pos2.x.lo);
2809 assert_eq!(pos1.y.hi, pos2.y.hi);
2810 assert_eq!(pos1.y.lo, pos2.y.lo);
2811 assert_eq!(angle1.hi, angle2.hi);
2812 assert_eq!(angle1.lo, angle2.lo);
2813 }
2814
2815 #[test]
2818 fn test_distance_joint() {
2819 let config = PhysicsConfig2D::default();
2820 let mut world = PhysicsWorld2D::new(config);
2821
2822 let shape = Shape2D::Circle {
2823 radius: Fix128::ONE,
2824 };
2825 let body_a = RigidBody2D::new_dynamic(Vec2Fix::from_int(0, 10), Fix128::ONE, shape.clone());
2826 let body_b = RigidBody2D::new_dynamic(Vec2Fix::from_int(5, 10), Fix128::ONE, shape);
2827 let id_a = world.add_body(body_a);
2828 let id_b = world.add_body(body_b);
2829
2830 world.add_joint(Joint2D::Distance {
2831 body_a: id_a,
2832 body_b: id_b,
2833 local_anchor_a: Vec2Fix::ZERO,
2834 local_anchor_b: Vec2Fix::ZERO,
2835 target_distance: Fix128::from_int(5),
2836 compliance: Fix128::ZERO,
2837 });
2838
2839 let dt = Fix128::from_ratio(1, 60);
2840 for _ in 0..60 {
2841 world.step(dt);
2842 }
2843
2844 let pos_a = world.bodies[id_a].position;
2845 let pos_b = world.bodies[id_b].position;
2846 let dist = (pos_b - pos_a).length();
2847 let target = Fix128::from_int(5);
2848 let error = (dist - target).abs();
2849
2850 assert!(
2852 error < Fix128::from_int(2),
2853 "Distance joint error too large"
2854 );
2855 }
2856
2857 #[test]
2858 fn test_revolute_joint() {
2859 let config = PhysicsConfig2D {
2860 gravity: Vec2Fix::ZERO,
2861 substeps: 4,
2862 iterations: 8,
2863 damping: Fix128::from_ratio(99, 100),
2864 };
2865 let mut world = PhysicsWorld2D::new(config);
2866
2867 let shape = Shape2D::Circle {
2868 radius: Fix128::ONE,
2869 };
2870 let body_a = RigidBody2D::new_static(Vec2Fix::ZERO, shape.clone());
2871 let body_b = RigidBody2D::new_dynamic(Vec2Fix::from_int(3, 0), Fix128::ONE, shape);
2872 let id_a = world.add_body(body_a);
2873 let id_b = world.add_body(body_b);
2874
2875 world.add_joint(Joint2D::Revolute {
2876 body_a: id_a,
2877 body_b: id_b,
2878 local_anchor_a: Vec2Fix::ZERO,
2879 local_anchor_b: Vec2Fix::from_int(-3, 0),
2880 compliance: Fix128::ZERO,
2881 });
2882
2883 let dt = Fix128::from_ratio(1, 60);
2884 for _ in 0..60 {
2885 world.step(dt);
2886 }
2887
2888 let wa = world.bodies[id_a].world_point(Vec2Fix::ZERO);
2890 let wb = world.bodies[id_b].world_point(Vec2Fix::from_int(-3, 0));
2891 let separation = wa.distance_to(wb);
2892 assert!(
2893 separation < Fix128::ONE,
2894 "Revolute joint anchor separation too large"
2895 );
2896 }
2897
2898 #[test]
2899 fn test_weld_joint() {
2900 let config = PhysicsConfig2D {
2901 gravity: Vec2Fix::ZERO,
2902 substeps: 4,
2903 iterations: 8,
2904 damping: Fix128::from_ratio(99, 100),
2905 };
2906 let mut world = PhysicsWorld2D::new(config);
2907
2908 let shape = Shape2D::Circle {
2909 radius: Fix128::ONE,
2910 };
2911 let body_a = RigidBody2D::new_static(Vec2Fix::ZERO, shape.clone());
2912 let mut body_b = RigidBody2D::new_dynamic(Vec2Fix::from_int(2, 0), Fix128::ONE, shape);
2913 body_b.angular_velocity = Fix128::from_int(5); let id_a = world.add_body(body_a);
2915 let id_b = world.add_body(body_b);
2916
2917 world.add_joint(Joint2D::Weld {
2918 body_a: id_a,
2919 body_b: id_b,
2920 local_anchor_a: Vec2Fix::from_int(2, 0),
2921 local_anchor_b: Vec2Fix::ZERO,
2922 reference_angle: Fix128::ZERO,
2923 compliance: Fix128::ZERO,
2924 });
2925
2926 let dt = Fix128::from_ratio(1, 60);
2927 for _ in 0..120 {
2928 world.step(dt);
2929 }
2930
2931 let angle_diff = (world.bodies[id_b].angle - world.bodies[id_a].angle).abs();
2933 assert!(
2934 angle_diff < Fix128::from_int(2),
2935 "Weld joint should constrain angle"
2936 );
2937 }
2938
2939 #[test]
2940 fn test_mouse_joint() {
2941 let config = PhysicsConfig2D {
2942 gravity: Vec2Fix::ZERO,
2943 substeps: 4,
2944 iterations: 8,
2945 damping: Fix128::from_ratio(99, 100),
2946 };
2947 let mut world = PhysicsWorld2D::new(config);
2948
2949 let shape = Shape2D::Circle {
2950 radius: Fix128::ONE,
2951 };
2952 let body = RigidBody2D::new_dynamic(Vec2Fix::ZERO, Fix128::ONE, shape);
2953 let id = world.add_body(body);
2954
2955 let target = Vec2Fix::from_int(10, 10);
2956 world.add_joint(Joint2D::Mouse {
2957 body: id,
2958 target,
2959 max_force: Fix128::from_int(100),
2960 stiffness: Fix128::from_int(100),
2963 damping: Fix128::from_int(20),
2964 });
2965
2966 let dt = Fix128::from_ratio(1, 60);
2967 for _ in 0..300 {
2968 world.step(dt);
2969 }
2970
2971 let pos = world.bodies[id].position;
2973 let dist = pos.distance_to(target);
2976 assert!(
2977 dist < Fix128::from_ratio(1, 100),
2978 "Mouse joint should pull body toward target"
2979 );
2980 }
2981
2982 #[test]
2985 fn test_circle_circle_contact_resolution() {
2986 let config = PhysicsConfig2D {
2987 gravity: Vec2Fix::ZERO,
2988 substeps: 4,
2989 iterations: 8,
2990 damping: Fix128::ONE, };
2992 let mut world = PhysicsWorld2D::new(config);
2993
2994 let shape_a = Shape2D::Circle {
2995 radius: Fix128::ONE,
2996 };
2997 let shape_b = Shape2D::Circle {
2998 radius: Fix128::ONE,
2999 };
3000 let body_a = RigidBody2D::new_dynamic(Vec2Fix::from_int(0, 0), Fix128::ONE, shape_a);
3002 let body_b = RigidBody2D::new_dynamic(Vec2Fix::from_int(1, 0), Fix128::ONE, shape_b);
3003 world.add_body(body_a);
3004 world.add_body(body_b);
3005
3006 let dt = Fix128::from_ratio(1, 60);
3007 for _ in 0..60 {
3008 world.step(dt);
3009 }
3010
3011 let dist = world.bodies[0]
3013 .position
3014 .distance_to(world.bodies[1].position);
3015 assert!(
3016 dist >= Fix128::ONE,
3017 "Circles should have been pushed apart by contact solver"
3018 );
3019 }
3020
3021 #[test]
3022 fn test_body_rests_on_static_edge() {
3023 let config = PhysicsConfig2D::default();
3024 let mut world = PhysicsWorld2D::new(config);
3025
3026 let ground = RigidBody2D::new_static(
3028 Vec2Fix::ZERO,
3029 Shape2D::Edge {
3030 start: Vec2Fix::from_int(-100, 0),
3031 end: Vec2Fix::from_int(100, 0),
3032 },
3033 );
3034 world.add_body(ground);
3035
3036 let ball = RigidBody2D::new_dynamic(
3038 Vec2Fix::from_int(0, 5),
3039 Fix128::ONE,
3040 Shape2D::Circle {
3041 radius: Fix128::ONE,
3042 },
3043 );
3044 let ball_id = world.add_body(ball);
3045
3046 let dt = Fix128::from_ratio(1, 60);
3047 for _ in 0..300 {
3048 world.step(dt);
3049 }
3050
3051 let y = world.bodies[ball_id].position.y;
3053 assert!(
3054 y > Fix128::from_int(-5),
3055 "Ball should rest on or above the edge"
3056 );
3057 }
3058
3059 #[test]
3062 fn test_add_remove_body() {
3063 let config = PhysicsConfig2D::default();
3064 let mut world = PhysicsWorld2D::new(config);
3065
3066 let shape = Shape2D::Circle {
3067 radius: Fix128::ONE,
3068 };
3069 let id0 = world.add_body(RigidBody2D::new_dynamic(
3070 Vec2Fix::from_int(0, 0),
3071 Fix128::ONE,
3072 shape.clone(),
3073 ));
3074 let _id1 = world.add_body(RigidBody2D::new_dynamic(
3075 Vec2Fix::from_int(5, 5),
3076 Fix128::ONE,
3077 shape,
3078 ));
3079 assert_eq!(world.bodies.len(), 2);
3080
3081 let removed = world.remove_body(id0);
3082 assert!(removed.is_some());
3083 assert_eq!(world.bodies.len(), 1);
3084 }
3085
3086 #[test]
3087 fn test_remove_body_invalid_index() {
3088 let config = PhysicsConfig2D::default();
3089 let mut world = PhysicsWorld2D::new(config);
3090 let removed = world.remove_body(999);
3091 assert!(removed.is_none());
3092 }
3093
3094 #[test]
3097 fn test_circle_inertia() {
3098 let shape = Shape2D::Circle {
3099 radius: Fix128::from_int(2),
3100 };
3101 let mass = Fix128::from_int(4);
3102 let inertia = compute_inertia(&shape, mass);
3103 assert_eq!(inertia.hi, 8);
3105 }
3106
3107 #[test]
3108 fn test_polygon_inertia_nonzero() {
3109 let verts = vec![
3110 Vec2Fix::from_int(-1, -1),
3111 Vec2Fix::from_int(1, -1),
3112 Vec2Fix::from_int(1, 1),
3113 Vec2Fix::from_int(-1, 1),
3114 ];
3115 let shape = Shape2D::Polygon { vertices: verts };
3116 let inertia = compute_inertia(&shape, Fix128::from_int(4));
3117 assert!(!inertia.is_zero());
3118 assert!(!inertia.is_negative());
3119 }
3120
3121 #[test]
3124 fn test_multiple_bodies_simulation() {
3125 let config = PhysicsConfig2D::default();
3126 let mut world = PhysicsWorld2D::new(config);
3127
3128 let shape = Shape2D::Circle {
3129 radius: Fix128::ONE,
3130 };
3131
3132 for i in 0..5 {
3134 let body = RigidBody2D::new_dynamic(
3135 Vec2Fix::from_int(i as i64 * 3, (i as i64 + 1) * 10),
3136 Fix128::ONE,
3137 shape.clone(),
3138 );
3139 world.add_body(body);
3140 }
3141
3142 let dt = Fix128::from_ratio(1, 60);
3143 for _ in 0..60 {
3144 world.step(dt);
3145 }
3146
3147 for body in &world.bodies {
3149 assert!(body.velocity.y.is_negative() || body.position.y < Fix128::from_int(50));
3150 }
3151 }
3152
3153 #[test]
3156 fn test_world_check_collision_2d() {
3157 let config = PhysicsConfig2D::default();
3158 let mut world = PhysicsWorld2D::new(config);
3159
3160 let shape_a = Shape2D::Circle {
3161 radius: Fix128::ONE,
3162 };
3163 let shape_b = Shape2D::Circle {
3164 radius: Fix128::ONE,
3165 };
3166 world.add_body(RigidBody2D::new_dynamic(
3167 Vec2Fix::from_int(0, 0),
3168 Fix128::ONE,
3169 shape_a,
3170 ));
3171 world.add_body(RigidBody2D::new_dynamic(
3172 Vec2Fix::from_int(1, 0),
3173 Fix128::ONE,
3174 shape_b,
3175 ));
3176
3177 let result = world.check_collision_2d(0, 1);
3178 assert!(result.is_some());
3179 }
3180
3181 #[test]
3182 fn test_world_no_collision_far_apart() {
3183 let config = PhysicsConfig2D::default();
3184 let mut world = PhysicsWorld2D::new(config);
3185
3186 let shape = Shape2D::Circle {
3187 radius: Fix128::ONE,
3188 };
3189 world.add_body(RigidBody2D::new_dynamic(
3190 Vec2Fix::from_int(0, 0),
3191 Fix128::ONE,
3192 shape.clone(),
3193 ));
3194 world.add_body(RigidBody2D::new_dynamic(
3195 Vec2Fix::from_int(100, 100),
3196 Fix128::ONE,
3197 shape,
3198 ));
3199
3200 let result = world.check_collision_2d(0, 1);
3201 assert!(result.is_none());
3202 }
3203
3204 #[test]
3207 fn test_zero_dt_step() {
3208 let config = PhysicsConfig2D::default();
3209 let mut world = PhysicsWorld2D::new(config);
3210
3211 let shape = Shape2D::Circle {
3212 radius: Fix128::ONE,
3213 };
3214 let body = RigidBody2D::new_dynamic(Vec2Fix::from_int(0, 10), Fix128::ONE, shape);
3215 let id = world.add_body(body);
3216
3217 world.step(Fix128::ZERO);
3219 assert_eq!(world.bodies[id].position.x.hi, 0);
3220 assert_eq!(world.bodies[id].position.y.hi, 10);
3221 }
3222
3223 #[test]
3226 fn test_config_default() {
3227 let config = PhysicsConfig2D::default();
3228 assert!(config.gravity.x.is_zero());
3229 assert_eq!(config.gravity.y.hi, -10);
3230 assert_eq!(config.substeps, 4);
3231 assert_eq!(config.iterations, 8);
3232 }
3233
3234 #[test]
3237 fn test_closest_point_on_segment_middle() {
3238 let a = Vec2Fix::from_int(0, 0);
3239 let b = Vec2Fix::from_int(10, 0);
3240 let p = Vec2Fix::from_int(5, 5);
3241 let closest = closest_point_on_segment(a, b, p);
3242 assert_eq!(closest.x.hi, 5);
3243 assert!(closest.y.is_zero());
3244 }
3245
3246 #[test]
3247 fn test_closest_point_on_segment_endpoint_a() {
3248 let a = Vec2Fix::from_int(0, 0);
3249 let b = Vec2Fix::from_int(10, 0);
3250 let p = Vec2Fix::from_int(-5, 0);
3251 let closest = closest_point_on_segment(a, b, p);
3252 assert_eq!(closest.x.hi, 0);
3253 assert!(closest.y.is_zero());
3254 }
3255
3256 #[test]
3257 fn test_closest_point_on_segment_endpoint_b() {
3258 let a = Vec2Fix::from_int(0, 0);
3259 let b = Vec2Fix::from_int(10, 0);
3260 let p = Vec2Fix::from_int(15, 0);
3261 let closest = closest_point_on_segment(a, b, p);
3262 assert_eq!(closest.x.hi, 10);
3263 assert!(closest.y.is_zero());
3264 }
3265
3266 #[test]
3269 fn test_apply_impulse_at_point() {
3270 let shape = Shape2D::Circle {
3271 radius: Fix128::from_int(2),
3272 };
3273 let mut body = RigidBody2D::new_dynamic(Vec2Fix::ZERO, Fix128::ONE, shape);
3274 let point = Vec2Fix::from_int(2, 0);
3275 let impulse = Vec2Fix::from_int(0, 10);
3276 body.apply_impulse_at_point(impulse, point);
3277
3278 assert_eq!(body.velocity.y.hi, 10);
3280 assert!(!body.angular_velocity.is_zero());
3281 }
3282
3283 #[test]
3286 fn test_apply_force() {
3287 let shape = Shape2D::Circle {
3288 radius: Fix128::ONE,
3289 };
3290 let mut body = RigidBody2D::new_dynamic(Vec2Fix::ZERO, Fix128::ONE, shape);
3291 let force = Vec2Fix::from_int(100, 0);
3292 let dt = Fix128::from_ratio(1, 60);
3293 body.apply_force(force, dt);
3294 assert!(body.velocity.x > Fix128::ZERO);
3295 }
3296
3297 fn tether_world(substeps: usize) -> PhysicsWorld2D {
3301 PhysicsWorld2D::new(PhysicsConfig2D {
3302 gravity: Vec2Fix::ZERO,
3303 substeps,
3304 iterations: 2,
3305 damping: Fix128::ONE,
3306 })
3307 }
3308
3309 #[test]
3310 fn exp_neg_matches_reference_values() {
3311 for (x, want) in [
3313 (1.0, 0.367_879_441_171_442_3),
3314 (0.25, 0.778_800_783_071_404_9),
3315 (5.0, 0.006_737_946_999_085_467),
3316 ] {
3317 let got = exp_neg(Fix128::from_f64(x)).to_f64();
3318 assert!((got - want).abs() < 1e-15, "e^-{x}: {got} vs {want}");
3319 }
3320 assert_eq!(exp_neg(Fix128::ZERO), Fix128::ONE);
3321 assert_eq!(exp_neg(Fix128::from_int(-3)), Fix128::ONE);
3322 assert_eq!(exp_neg(Fix128::from_int(50)), Fix128::ZERO);
3323 }
3324
3325 #[test]
3326 fn angular_tether_one_substep_is_backward_euler() {
3327 let mut w = tether_world(1);
3329 let mut b = RigidBody2D::new_dynamic(
3330 Vec2Fix::ZERO,
3331 Fix128::ONE,
3332 Shape2D::Circle {
3333 radius: Fix128::ONE,
3334 },
3335 );
3336 b.inv_inertia = Fix128::from_ratio(1, 2);
3337 b.angle = Fix128::ONE;
3338 let idx = w.add_body(b);
3339 let mut set = Tethers2D::new();
3340 let id = set.add_angular(AngularTether2D::critically_damped(
3341 idx,
3342 Fix128::ZERO,
3343 Fix128::from_int(2),
3344 Fix128::from_int(6),
3345 ));
3346 assert_eq!(
3347 set.angular(id).map(|t| t.stiffness),
3348 Some(Fix128::from_int(72))
3349 );
3350 w.step_with_tethers(Fix128::from_ratio(1, 60), &set);
3351 let a = 0.1_f64;
3352 let want = (1.0 + 2.0 * a) / ((1.0 + a) * (1.0 + a));
3353 assert!((w.bodies[idx].angle.to_f64() - want).abs() < 1e-15);
3354 assert!((w.bodies[idx].angular_velocity.to_f64() - (want - 1.0) * 60.0).abs() < 1e-12);
3355 set.angular_mut(id).unwrap().target_angle = Fix128::ONE;
3357 assert_eq!(set.angular_count(), 1);
3358 assert!(set.remove_angular(id).is_some());
3359 assert!(set.angular(id).is_none() && set.angular_mut(id).is_none());
3360 assert_eq!(set.angular_count(), 0);
3361 }
3362
3363 #[test]
3364 fn kinematic_drive_one_frame_is_the_closed_form() {
3365 let mut w = tether_world(4);
3367 let mut b = RigidBody2D::new_kinematic(
3368 Vec2Fix::from_int(2, -1),
3369 Shape2D::Circle {
3370 radius: Fix128::ONE,
3371 },
3372 );
3373 b.angle = Fix128::from_int(3);
3374 b.velocity = Vec2Fix::from_int(1, 0);
3375 let idx = w.add_body(b);
3376 let mut set = Tethers2D::new();
3377 let id = set.add_drive(KinematicDrive2D::new(
3378 idx,
3379 Vec2Fix::ZERO,
3380 Fix128::ONE,
3381 Fix128::from_int(5),
3382 ));
3383 assert_eq!(set.drive_count(), 1);
3384 w.step_with_tethers(Fix128::from_ratio(1, 10), &set);
3385 let (om, t) = (5.0_f64, 0.1_f64);
3386 let ex = |e0: f64, v0: f64| (e0 + (v0 + om * e0) * t) * crate::det_math::exp64(-om * t);
3387 let body = &w.bodies[idx];
3388 assert!((body.position.x.to_f64() - ex(2.0, 1.0)).abs() < 1e-14);
3389 assert!((body.position.y.to_f64() - ex(-1.0, 0.0)).abs() < 1e-14);
3390 assert!((body.angle.to_f64() - 1.0 - ex(2.0, 0.0)).abs() < 1e-14);
3391 set.drive_mut(id).unwrap().omega = Fix128::ZERO;
3392 assert_eq!(set.drive(id).map(|d| d.omega), Some(Fix128::ZERO));
3393 assert!(set.remove_drive(id).is_some());
3394 assert!(
3395 set.drive(id).is_none()
3396 && set.drive_mut(id).is_none()
3397 && set.remove_drive(id).is_none()
3398 );
3399 assert_eq!(set.drive_count(), 0);
3400 }
3401
3402 #[test]
3403 fn tethers_skip_out_of_range_and_wrong_body_types() {
3404 let mut w = tether_world(1);
3405 let d = w.add_body(RigidBody2D::new_dynamic(
3406 Vec2Fix::ZERO,
3407 Fix128::ONE,
3408 Shape2D::Circle {
3409 radius: Fix128::ONE,
3410 },
3411 ));
3412 let mut k = RigidBody2D::new_kinematic(
3413 Vec2Fix::from_int(10, 0),
3414 Shape2D::Circle {
3415 radius: Fix128::ONE,
3416 },
3417 );
3418 k.inv_inertia = Fix128::ONE;
3419 k.angle = Fix128::ONE;
3420 let k = w.add_body(k);
3421 let mut set = Tethers2D::new();
3422 set.add_angular(AngularTether2D::new(
3423 7,
3424 Fix128::ONE,
3425 Fix128::ONE,
3426 Fix128::ONE,
3427 ));
3428 set.add_angular(AngularTether2D::new(
3429 k,
3430 Fix128::ZERO,
3431 Fix128::from_int(9),
3432 Fix128::ONE,
3433 ));
3434 set.add_drive(KinematicDrive2D::new(
3435 7,
3436 Vec2Fix::ZERO,
3437 Fix128::ZERO,
3438 Fix128::ONE,
3439 ));
3440 set.add_drive(KinematicDrive2D::new(
3441 d,
3442 Vec2Fix::from_int(1, 1),
3443 Fix128::ZERO,
3444 Fix128::ONE,
3445 ));
3446 w.step_with_tethers(Fix128::from_ratio(1, 60), &set);
3447 assert_eq!(w.bodies[d].position, Vec2Fix::ZERO);
3448 assert_eq!(w.bodies[k].angle, Fix128::ONE);
3449 let before = w.bodies[k].position;
3451 w.step_with_tethers(Fix128::ZERO, &set);
3452 assert_eq!(w.bodies[k].position, before);
3453 }
3454}