1use crate::math::{Fix128, QuatFix, Vec3Fix};
16
17#[cfg(not(feature = "std"))]
18use alloc::vec::Vec;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum JointType {
23 Ball,
25 Hinge,
27 Fixed,
29 Slider,
31 Spring,
33 D6,
35 ConeTwist,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub struct BallJoint {
44 pub body_a: usize,
46 pub body_b: usize,
48 pub local_anchor_a: Vec3Fix,
50 pub local_anchor_b: Vec3Fix,
52 pub compliance: Fix128,
54 pub break_force: Option<Fix128>,
56}
57
58impl BallJoint {
59 #[inline]
61 #[must_use]
62 pub const fn new(body_a: usize, body_b: usize, anchor_a: Vec3Fix, anchor_b: Vec3Fix) -> Self {
63 Self {
64 body_a,
65 body_b,
66 local_anchor_a: anchor_a,
67 local_anchor_b: anchor_b,
68 compliance: Fix128::ZERO,
69 break_force: None,
70 }
71 }
72
73 #[must_use]
75 pub const fn with_compliance(mut self, compliance: Fix128) -> Self {
76 self.compliance = compliance;
77 self
78 }
79
80 #[must_use]
82 pub const fn with_break_force(mut self, force: Fix128) -> Self {
83 self.break_force = Some(force);
84 self
85 }
86}
87
88const fn fix128_min_max_violated(min: Fix128, max: Fix128) -> bool {
93 min.hi > max.hi || (min.hi == max.hi && min.lo > max.lo)
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct HingeJoint {
99 pub body_a: usize,
101 pub body_b: usize,
103 pub local_anchor_a: Vec3Fix,
105 pub local_anchor_b: Vec3Fix,
107 pub local_axis_a: Vec3Fix,
109 pub local_axis_b: Vec3Fix,
111 pub angle_min: Option<Fix128>,
113 pub angle_max: Option<Fix128>,
115 pub compliance: Fix128,
117 pub angular_compliance: Fix128,
119 pub break_force: Option<Fix128>,
121}
122
123impl HingeJoint {
124 #[inline]
126 #[must_use]
127 pub const fn new(
128 body_a: usize,
129 body_b: usize,
130 anchor_a: Vec3Fix,
131 anchor_b: Vec3Fix,
132 axis_a: Vec3Fix,
133 axis_b: Vec3Fix,
134 ) -> Self {
135 Self {
136 body_a,
137 body_b,
138 local_anchor_a: anchor_a,
139 local_anchor_b: anchor_b,
140 local_axis_a: axis_a,
141 local_axis_b: axis_b,
142 angle_min: None,
143 angle_max: None,
144 compliance: Fix128::ZERO,
145 angular_compliance: Fix128::ZERO,
146 break_force: None,
147 }
148 }
149
150 #[must_use]
156 pub const fn with_limits(mut self, min: Fix128, max: Fix128) -> Self {
157 if fix128_min_max_violated(min, max) {
158 panic!("HingeJoint::with_limits: min must not exceed max");
159 }
160 self.angle_min = Some(min);
161 self.angle_max = Some(max);
162 self
163 }
164
165 #[must_use]
167 pub const fn with_compliance(mut self, compliance: Fix128) -> Self {
168 self.compliance = compliance;
169 self
170 }
171
172 #[must_use]
174 pub const fn with_break_force(mut self, force: Fix128) -> Self {
175 self.break_force = Some(force);
176 self
177 }
178}
179
180#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182pub struct FixedJoint {
183 pub body_a: usize,
185 pub body_b: usize,
187 pub local_anchor_a: Vec3Fix,
189 pub local_anchor_b: Vec3Fix,
191 pub relative_rotation: QuatFix,
193 pub compliance: Fix128,
195 pub angular_compliance: Fix128,
197 pub break_force: Option<Fix128>,
199}
200
201impl FixedJoint {
202 #[inline]
204 #[must_use]
205 pub const fn new(
206 body_a: usize,
207 body_b: usize,
208 anchor_a: Vec3Fix,
209 anchor_b: Vec3Fix,
210 relative_rotation: QuatFix,
211 ) -> Self {
212 Self {
213 body_a,
214 body_b,
215 local_anchor_a: anchor_a,
216 local_anchor_b: anchor_b,
217 relative_rotation,
218 compliance: Fix128::ZERO,
219 angular_compliance: Fix128::ZERO,
220 break_force: None,
221 }
222 }
223
224 #[must_use]
226 pub const fn with_break_force(mut self, force: Fix128) -> Self {
227 self.break_force = Some(force);
228 self
229 }
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq)]
234pub struct SliderJoint {
235 pub body_a: usize,
237 pub body_b: usize,
239 pub local_axis: Vec3Fix,
241 pub local_anchor_a: Vec3Fix,
243 pub local_anchor_b: Vec3Fix,
245 pub limit_min: Option<Fix128>,
247 pub limit_max: Option<Fix128>,
249 pub compliance: Fix128,
251 pub break_force: Option<Fix128>,
253}
254
255impl SliderJoint {
256 #[inline]
258 #[must_use]
259 pub const fn new(
260 body_a: usize,
261 body_b: usize,
262 axis: Vec3Fix,
263 anchor_a: Vec3Fix,
264 anchor_b: Vec3Fix,
265 ) -> Self {
266 Self {
267 body_a,
268 body_b,
269 local_axis: axis,
270 local_anchor_a: anchor_a,
271 local_anchor_b: anchor_b,
272 limit_min: None,
273 limit_max: None,
274 compliance: Fix128::ZERO,
275 break_force: None,
276 }
277 }
278
279 #[must_use]
285 pub const fn with_limits(mut self, min: Fix128, max: Fix128) -> Self {
286 if fix128_min_max_violated(min, max) {
287 panic!("SliderJoint::with_limits: min must not exceed max");
288 }
289 self.limit_min = Some(min);
290 self.limit_max = Some(max);
291 self
292 }
293
294 #[must_use]
296 pub const fn with_break_force(mut self, force: Fix128) -> Self {
297 self.break_force = Some(force);
298 self
299 }
300}
301
302#[derive(Clone, Copy, Debug, PartialEq, Eq)]
330pub struct SpringJoint {
331 pub body_a: usize,
333 pub body_b: usize,
335 pub local_anchor_a: Vec3Fix,
337 pub local_anchor_b: Vec3Fix,
339 pub rest_length: Fix128,
342 pub stiffness: Fix128,
345 pub damping: Fix128,
350 pub break_force: Option<Fix128>,
353}
354
355impl SpringJoint {
356 #[inline]
358 #[must_use]
359 pub const fn new(
360 body_a: usize,
361 body_b: usize,
362 anchor_a: Vec3Fix,
363 anchor_b: Vec3Fix,
364 rest_length: Fix128,
365 stiffness: Fix128,
366 damping: Fix128,
367 ) -> Self {
368 Self {
369 body_a,
370 body_b,
371 local_anchor_a: anchor_a,
372 local_anchor_b: anchor_b,
373 rest_length,
374 stiffness,
375 damping,
376 break_force: None,
377 }
378 }
379
380 #[must_use]
382 pub const fn with_break_force(mut self, force: Fix128) -> Self {
383 self.break_force = Some(force);
384 self
385 }
386}
387
388#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
390pub enum D6Motion {
391 Locked,
393 #[default]
395 Free,
396 Limited,
398}
399
400#[derive(Clone, Copy, Debug, PartialEq, Eq)]
405pub struct D6Joint {
406 pub body_a: usize,
408 pub body_b: usize,
410 pub local_anchor_a: Vec3Fix,
412 pub local_anchor_b: Vec3Fix,
414 pub local_frame_a: QuatFix,
416 pub local_frame_b: QuatFix,
418 pub linear_x: D6Motion,
420 pub linear_y: D6Motion,
422 pub linear_z: D6Motion,
424 pub angular_x: D6Motion,
426 pub angular_y: D6Motion,
428 pub angular_z: D6Motion,
430 pub linear_limit_min: Vec3Fix,
432 pub linear_limit_max: Vec3Fix,
434 pub angular_limit_min: Vec3Fix,
436 pub angular_limit_max: Vec3Fix,
438 pub compliance: Fix128,
440 pub angular_compliance: Fix128,
442 pub break_force: Option<Fix128>,
444}
445
446impl D6Joint {
447 #[must_use]
449 pub fn new(body_a: usize, body_b: usize, anchor_a: Vec3Fix, anchor_b: Vec3Fix) -> Self {
450 Self {
451 body_a,
452 body_b,
453 local_anchor_a: anchor_a,
454 local_anchor_b: anchor_b,
455 local_frame_a: QuatFix::IDENTITY,
456 local_frame_b: QuatFix::IDENTITY,
457 linear_x: D6Motion::Free,
458 linear_y: D6Motion::Free,
459 linear_z: D6Motion::Free,
460 angular_x: D6Motion::Free,
461 angular_y: D6Motion::Free,
462 angular_z: D6Motion::Free,
463 linear_limit_min: Vec3Fix::new(-Fix128::ONE, -Fix128::ONE, -Fix128::ONE),
464 linear_limit_max: Vec3Fix::new(Fix128::ONE, Fix128::ONE, Fix128::ONE),
465 angular_limit_min: Vec3Fix::new(-Fix128::PI, -Fix128::PI, -Fix128::PI),
466 angular_limit_max: Vec3Fix::new(Fix128::PI, Fix128::PI, Fix128::PI),
467 compliance: Fix128::ZERO,
468 angular_compliance: Fix128::ZERO,
469 break_force: None,
470 }
471 }
472
473 #[must_use]
475 pub const fn with_linear_motion(mut self, x: D6Motion, y: D6Motion, z: D6Motion) -> Self {
476 self.linear_x = x;
477 self.linear_y = y;
478 self.linear_z = z;
479 self
480 }
481
482 #[must_use]
484 pub const fn with_angular_motion(mut self, x: D6Motion, y: D6Motion, z: D6Motion) -> Self {
485 self.angular_x = x;
486 self.angular_y = y;
487 self.angular_z = z;
488 self
489 }
490
491 #[must_use]
498 pub const fn with_linear_limits(mut self, min: Vec3Fix, max: Vec3Fix) -> Self {
499 if fix128_min_max_violated(min.x, max.x)
500 || fix128_min_max_violated(min.y, max.y)
501 || fix128_min_max_violated(min.z, max.z)
502 {
503 panic!("D6Joint::with_linear_limits: min must not exceed max on any axis");
504 }
505 self.linear_limit_min = min;
506 self.linear_limit_max = max;
507 self
508 }
509
510 #[must_use]
517 pub const fn with_angular_limits(mut self, min: Vec3Fix, max: Vec3Fix) -> Self {
518 if fix128_min_max_violated(min.x, max.x)
519 || fix128_min_max_violated(min.y, max.y)
520 || fix128_min_max_violated(min.z, max.z)
521 {
522 panic!("D6Joint::with_angular_limits: min must not exceed max on any axis");
523 }
524 self.angular_limit_min = min;
525 self.angular_limit_max = max;
526 self
527 }
528
529 #[must_use]
531 pub const fn with_break_force(mut self, force: Fix128) -> Self {
532 self.break_force = Some(force);
533 self
534 }
535}
536
537#[derive(Clone, Copy, Debug, PartialEq, Eq)]
542pub struct ConeTwistJoint {
543 pub body_a: usize,
545 pub body_b: usize,
547 pub local_anchor_a: Vec3Fix,
549 pub local_anchor_b: Vec3Fix,
551 pub twist_axis_a: Vec3Fix,
553 pub twist_axis_b: Vec3Fix,
555 pub cone_limit: Fix128,
557 pub twist_limit: Fix128,
559 pub compliance: Fix128,
561 pub angular_compliance: Fix128,
563 pub break_force: Option<Fix128>,
565}
566
567impl ConeTwistJoint {
568 #[must_use]
570 pub const fn new(
571 body_a: usize,
572 body_b: usize,
573 anchor_a: Vec3Fix,
574 anchor_b: Vec3Fix,
575 twist_axis_a: Vec3Fix,
576 twist_axis_b: Vec3Fix,
577 ) -> Self {
578 Self {
579 body_a,
580 body_b,
581 local_anchor_a: anchor_a,
582 local_anchor_b: anchor_b,
583 twist_axis_a,
584 twist_axis_b,
585 cone_limit: Fix128::HALF_PI,
586 twist_limit: Fix128::PI,
587 compliance: Fix128::ZERO,
588 angular_compliance: Fix128::ZERO,
589 break_force: None,
590 }
591 }
592
593 #[must_use]
600 pub const fn with_limits(mut self, cone: Fix128, twist: Fix128) -> Self {
601 if cone.is_negative() {
602 panic!("ConeTwistJoint::with_limits: cone must not be negative");
603 }
604 if twist.is_negative() {
605 panic!("ConeTwistJoint::with_limits: twist must not be negative");
606 }
607 self.cone_limit = cone;
608 self.twist_limit = twist;
609 self
610 }
611
612 #[must_use]
614 pub const fn with_break_force(mut self, force: Fix128) -> Self {
615 self.break_force = Some(force);
616 self
617 }
618}
619
620#[derive(Clone, Copy, Debug, PartialEq, Eq)]
622pub enum Joint {
623 Ball(BallJoint),
625 Hinge(HingeJoint),
627 Fixed(FixedJoint),
629 Slider(SliderJoint),
631 Spring(SpringJoint),
633 D6(D6Joint),
635 ConeTwist(ConeTwistJoint),
637}
638
639impl Joint {
640 #[inline]
642 #[must_use]
643 pub const fn bodies(&self) -> (usize, usize) {
644 match self {
645 Self::Ball(j) => (j.body_a, j.body_b),
646 Self::Hinge(j) => (j.body_a, j.body_b),
647 Self::Fixed(j) => (j.body_a, j.body_b),
648 Self::Slider(j) => (j.body_a, j.body_b),
649 Self::Spring(j) => (j.body_a, j.body_b),
650 Self::D6(j) => (j.body_a, j.body_b),
651 Self::ConeTwist(j) => (j.body_a, j.body_b),
652 }
653 }
654
655 #[inline]
657 #[must_use]
658 pub const fn joint_type(&self) -> JointType {
659 match self {
660 Self::Ball(_) => JointType::Ball,
661 Self::Hinge(_) => JointType::Hinge,
662 Self::Fixed(_) => JointType::Fixed,
663 Self::Slider(_) => JointType::Slider,
664 Self::Spring(_) => JointType::Spring,
665 Self::D6(_) => JointType::D6,
666 Self::ConeTwist(_) => JointType::ConeTwist,
667 }
668 }
669
670 #[inline]
672 #[must_use]
673 pub const fn break_force(&self) -> Option<Fix128> {
674 match self {
675 Self::Ball(j) => j.break_force,
676 Self::Hinge(j) => j.break_force,
677 Self::Fixed(j) => j.break_force,
678 Self::Slider(j) => j.break_force,
679 Self::Spring(j) => j.break_force,
680 Self::D6(j) => j.break_force,
681 Self::ConeTwist(j) => j.break_force,
682 }
683 }
684
685 #[must_use]
693 pub fn compute_force(&self, bodies: &[crate::solver::RigidBody]) -> Fix128 {
694 let (a_idx, b_idx) = self.bodies();
695 let body_a = &bodies[a_idx];
696 let body_b = &bodies[b_idx];
697
698 match self {
699 Self::Ball(j) => {
700 let anchor_a = body_a.position + body_a.rotation.rotate_vec(j.local_anchor_a);
701 let anchor_b = body_b.position + body_b.rotation.rotate_vec(j.local_anchor_b);
702 (anchor_b - anchor_a).length()
703 }
704 Self::Hinge(j) => {
705 let anchor_a = body_a.position + body_a.rotation.rotate_vec(j.local_anchor_a);
706 let anchor_b = body_b.position + body_b.rotation.rotate_vec(j.local_anchor_b);
707 (anchor_b - anchor_a).length()
708 }
709 Self::Fixed(j) => {
710 let anchor_a = body_a.position + body_a.rotation.rotate_vec(j.local_anchor_a);
711 let anchor_b = body_b.position + body_b.rotation.rotate_vec(j.local_anchor_b);
712 (anchor_b - anchor_a).length()
713 }
714 Self::Slider(j) => {
715 let anchor_a = body_a.position + body_a.rotation.rotate_vec(j.local_anchor_a);
716 let anchor_b = body_b.position + body_b.rotation.rotate_vec(j.local_anchor_b);
717 let delta = anchor_b - anchor_a;
718 let world_axis = body_a.rotation.rotate_vec(j.local_axis).normalize();
719 let along = delta.dot(world_axis);
720 let perp = delta - world_axis * along;
721 perp.length()
722 }
723 Self::Spring(j) => {
724 let anchor_a = body_a.position + body_a.rotation.rotate_vec(j.local_anchor_a);
725 let anchor_b = body_b.position + body_b.rotation.rotate_vec(j.local_anchor_b);
726 let dist = (anchor_b - anchor_a).length();
727 let displacement = dist - j.rest_length;
728 (j.stiffness * displacement).abs()
729 }
730 Self::D6(j) => {
731 let anchor_a = body_a.position + body_a.rotation.rotate_vec(j.local_anchor_a);
732 let anchor_b = body_b.position + body_b.rotation.rotate_vec(j.local_anchor_b);
733 (anchor_b - anchor_a).length()
734 }
735 Self::ConeTwist(j) => {
736 let anchor_a = body_a.position + body_a.rotation.rotate_vec(j.local_anchor_a);
737 let anchor_b = body_b.position + body_b.rotation.rotate_vec(j.local_anchor_b);
738 (anchor_b - anchor_a).length()
739 }
740 }
741 }
742}
743
744pub fn solve_joints(joints: &[Joint], bodies: &mut [crate::solver::RigidBody], dt: Fix128) {
759 for joint in joints {
760 let _ = solve_one(joint, bodies, dt);
761 }
762}
763
764fn solve_one(joint: &Joint, bodies: &mut [crate::solver::RigidBody], dt: Fix128) -> Vec3Fix {
773 let (a, b) = joint.bodies();
774 if a == b {
775 return Vec3Fix::ZERO;
776 }
777 match joint {
778 Joint::Ball(j) => solve_ball_joint(j, bodies, dt),
779 Joint::Hinge(j) => solve_hinge_joint(j, bodies, dt),
780 Joint::Fixed(j) => solve_fixed_joint(j, bodies, dt),
781 Joint::Slider(j) => solve_slider_joint(j, bodies, dt),
782 Joint::Spring(j) => solve_spring_joint(j, bodies, dt),
783 Joint::D6(j) => solve_d6_joint(j, bodies, dt),
784 Joint::ConeTwist(j) => solve_cone_twist_joint(j, bodies, dt),
785 }
786}
787
788#[deprecated(
813 since = "2.2.0",
814 note = "the threshold is the anchor separation (m), not a force; use solve_joints_breaking_on_force, whose break_force is the reaction force λ/dt² in N"
815)]
816pub fn solve_joints_breakable(
817 joints: &[Joint],
818 bodies: &mut [crate::solver::RigidBody],
819 dt: Fix128,
820) -> Vec<usize> {
821 let mut broken = Vec::new();
823 for (i, joint) in joints.iter().enumerate() {
824 let (a, b) = joint.bodies();
825 if a == b {
826 continue;
828 }
829 if let Some(max_force) = joint.break_force() {
830 let force = joint.compute_force(bodies);
831 if force > max_force {
832 broken.push(i);
833 }
834 }
835 }
836
837 broken.sort_unstable();
839
840 for (i, joint) in joints.iter().enumerate() {
842 if broken.binary_search(&i).is_ok() {
843 continue;
844 }
845 let _ = solve_one(joint, bodies, dt);
846 }
847
848 broken.sort_unstable_by(|a, b| b.cmp(a));
850 broken
851}
852
853pub fn solve_joints_with_reaction_forces(
878 joints: &[Joint],
879 bodies: &mut [crate::solver::RigidBody],
880 dt: Fix128,
881) -> Vec<Fix128> {
882 let h2 = dt * dt;
883 joints
884 .iter()
885 .map(|joint| {
886 let impulse = solve_one(joint, bodies, dt);
887 if h2.is_zero() {
888 Fix128::ZERO
889 } else {
890 impulse.length() / h2
891 }
892 })
893 .collect()
894}
895
896pub fn solve_joints_breaking_on_force(
907 joints: &[Joint],
908 bodies: &mut [crate::solver::RigidBody],
909 dt: Fix128,
910) -> Vec<usize> {
911 let forces = solve_joints_with_reaction_forces(joints, bodies, dt);
912 let mut broken: Vec<usize> = joints
913 .iter()
914 .zip(&forces)
915 .enumerate()
916 .filter(|(_, (joint, force))| joint.break_force().is_some_and(|max| **force > max))
917 .map(|(i, _)| i)
918 .collect();
919 broken.reverse();
920 broken
921}
922
923fn solve_ball_joint(
925 joint: &BallJoint,
926 bodies: &mut [crate::solver::RigidBody],
927 dt: Fix128,
928) -> Vec3Fix {
929 let mut impulse = Vec3Fix::ZERO;
930 let body_a = bodies[joint.body_a];
931 let body_b = bodies[joint.body_b];
932
933 let r_a = body_a.rotation.rotate_vec(joint.local_anchor_a);
935 let r_b = body_b.rotation.rotate_vec(joint.local_anchor_b);
936 let anchor_a = body_a.position + r_a;
937 let anchor_b = body_b.position + r_b;
938
939 let delta = anchor_b - anchor_a;
940 let (normal, distance) = delta.normalize_with_length();
941
942 if distance.is_zero() {
943 return impulse;
944 }
945
946 let compliance_term = joint.compliance / (dt * dt);
947 let w_sum = point_w_sum(bodies, joint.body_a, joint.body_b, r_a, r_b, normal) + compliance_term;
948
949 if w_sum.is_zero() {
950 return impulse;
951 }
952
953 let lambda = distance / w_sum;
954 apply_point_correction(bodies, joint.body_a, joint.body_b, r_a, r_b, normal, lambda);
955 impulse = impulse + normal * lambda;
956 impulse
957}
958
959fn solve_hinge_joint(
966 joint: &HingeJoint,
967 bodies: &mut [crate::solver::RigidBody],
968 dt: Fix128,
969) -> Vec3Fix {
970 let mut impulse = Vec3Fix::ZERO;
971 let body_a = bodies[joint.body_a];
972 let body_b = bodies[joint.body_b];
973
974 let r_a = body_a.rotation.rotate_vec(joint.local_anchor_a);
976 let r_b = body_b.rotation.rotate_vec(joint.local_anchor_b);
977 let anchor_a = body_a.position + r_a;
978 let anchor_b = body_b.position + r_b;
979
980 let delta = anchor_b - anchor_a;
981 let (normal, distance) = delta.normalize_with_length();
982
983 if !distance.is_zero() {
984 let compliance_term = joint.compliance / (dt * dt);
985 let w_sum =
986 point_w_sum(bodies, joint.body_a, joint.body_b, r_a, r_b, normal) + compliance_term;
987
988 if !w_sum.is_zero() {
989 let lambda = distance / w_sum;
990 apply_point_correction(bodies, joint.body_a, joint.body_b, r_a, r_b, normal, lambda);
991 impulse = impulse + normal * lambda;
992 }
993 }
994
995 let body_a = bodies[joint.body_a];
997 let body_b = bodies[joint.body_b];
998 let world_axis_a = body_a.rotation.rotate_vec(joint.local_axis_a);
999 let world_axis_b = body_b.rotation.rotate_vec(joint.local_axis_b);
1000
1001 let axis_error = world_axis_a.cross(world_axis_b);
1002 let (correction_axis, sin_err) = axis_error.normalize_with_length();
1003
1004 if !sin_err.is_zero() {
1005 let error_mag = Fix128::atan2(sin_err, world_axis_a.dot(world_axis_b));
1007 let angular_compliance = joint.angular_compliance / (dt * dt);
1008 let w_ang =
1009 angular_w_sum(bodies, joint.body_a, joint.body_b, correction_axis) + angular_compliance;
1010
1011 if !w_ang.is_zero() {
1012 let inv_w_ang = Fix128::ONE / w_ang;
1013 let angular_lambda = error_mag * inv_w_ang;
1014
1015 apply_angular_correction(
1016 bodies,
1017 joint.body_a,
1018 joint.body_b,
1019 correction_axis,
1020 angular_lambda,
1021 );
1022 }
1023 }
1024
1025 if joint.angle_min.is_some() || joint.angle_max.is_some() {
1027 let body_a = bodies[joint.body_a];
1028 let body_b = bodies[joint.body_b];
1029 let world_axis_a = body_a.rotation.rotate_vec(joint.local_axis_a);
1030
1031 let rel_quat = body_b.rotation.mul(body_a.rotation.conjugate());
1033 let angle = compute_twist_angle(rel_quat, world_axis_a);
1034
1035 if let Some(min_angle) = joint.angle_min.filter(|m| angle < *m) {
1036 let error = min_angle - angle;
1037 let w_ang = angular_w_sum(bodies, joint.body_a, joint.body_b, world_axis_a);
1038 if !w_ang.is_zero() {
1039 let inv_w_ang = Fix128::ONE / w_ang;
1040 apply_angular_correction(
1041 bodies,
1042 joint.body_a,
1043 joint.body_b,
1044 world_axis_a,
1045 -(error * inv_w_ang),
1046 );
1047 }
1048 } else if let Some(max_angle) = joint.angle_max.filter(|m| angle > *m) {
1049 let error = angle - max_angle;
1050 let w_ang = angular_w_sum(bodies, joint.body_a, joint.body_b, world_axis_a);
1051 if !w_ang.is_zero() {
1052 let inv_w_ang = Fix128::ONE / w_ang;
1053 apply_angular_correction(
1054 bodies,
1055 joint.body_a,
1056 joint.body_b,
1057 world_axis_a,
1058 error * inv_w_ang,
1059 );
1060 }
1061 }
1062 }
1063 impulse
1064}
1065
1066fn solve_fixed_joint(
1068 joint: &FixedJoint,
1069 bodies: &mut [crate::solver::RigidBody],
1070 dt: Fix128,
1071) -> Vec3Fix {
1072 let mut impulse = Vec3Fix::ZERO;
1073 let body_a = bodies[joint.body_a];
1074 let body_b = bodies[joint.body_b];
1075
1076 let r_a = body_a.rotation.rotate_vec(joint.local_anchor_a);
1078 let r_b = body_b.rotation.rotate_vec(joint.local_anchor_b);
1079 let anchor_a = body_a.position + r_a;
1080 let anchor_b = body_b.position + r_b;
1081
1082 let delta = anchor_b - anchor_a;
1083 let (normal, distance) = delta.normalize_with_length();
1084
1085 if !distance.is_zero() {
1086 let compliance_term = joint.compliance / (dt * dt);
1087 let w_sum =
1088 point_w_sum(bodies, joint.body_a, joint.body_b, r_a, r_b, normal) + compliance_term;
1089
1090 if !w_sum.is_zero() {
1091 let lambda = distance / w_sum;
1092 apply_point_correction(bodies, joint.body_a, joint.body_b, r_a, r_b, normal, lambda);
1093 impulse = impulse + normal * lambda;
1094 }
1095 }
1096
1097 let body_a = bodies[joint.body_a];
1099 let body_b = bodies[joint.body_b];
1100 let target_rot_b = body_a.rotation.mul(joint.relative_rotation);
1101 let rot_error = body_b.rotation.mul(target_rot_b.conjugate());
1102
1103 let error_vec = Vec3Fix::new(rot_error.x, rot_error.y, rot_error.z);
1105 let (correction_axis, sin_half) = error_vec.normalize_with_length();
1106
1107 if !sin_half.is_zero() {
1108 let (sin_half, cos_half) = if rot_error.w.is_negative() {
1110 (-sin_half, -rot_error.w)
1111 } else {
1112 (sin_half, rot_error.w)
1113 };
1114 let error_mag = Fix128::atan2(sin_half, cos_half).double();
1115 let angular_compliance = joint.angular_compliance / (dt * dt);
1116 let w_ang =
1117 angular_w_sum(bodies, joint.body_a, joint.body_b, correction_axis) + angular_compliance;
1118
1119 if !w_ang.is_zero() {
1120 let inv_w_ang = Fix128::ONE / w_ang;
1121 let angular_lambda = error_mag * inv_w_ang;
1122
1123 apply_angular_correction(
1124 bodies,
1125 joint.body_a,
1126 joint.body_b,
1127 correction_axis,
1128 angular_lambda,
1129 );
1130 }
1131 }
1132 impulse
1133}
1134
1135fn solve_slider_joint(
1142 joint: &SliderJoint,
1143 bodies: &mut [crate::solver::RigidBody],
1144 dt: Fix128,
1145) -> Vec3Fix {
1146 let mut impulse = Vec3Fix::ZERO;
1147 let body_a = bodies[joint.body_a];
1148 let body_b = bodies[joint.body_b];
1149
1150 let world_axis = body_a.rotation.rotate_vec(joint.local_axis).normalize();
1151
1152 let r_a = body_a.rotation.rotate_vec(joint.local_anchor_a);
1153 let r_b = body_b.rotation.rotate_vec(joint.local_anchor_b);
1154 let anchor_a = body_a.position + r_a;
1155 let anchor_b = body_b.position + r_b;
1156
1157 let delta = anchor_b - anchor_a;
1158
1159 let along_axis = delta.dot(world_axis);
1161
1162 let perp = delta - world_axis * along_axis;
1164 let (perp_normal, perp_dist) = perp.normalize_with_length();
1165
1166 if !perp_dist.is_zero() {
1167 let compliance_term = joint.compliance / (dt * dt);
1168 let w_sum = point_w_sum(bodies, joint.body_a, joint.body_b, r_a, r_b, perp_normal)
1169 + compliance_term;
1170
1171 if !w_sum.is_zero() {
1172 let lambda = perp_dist / w_sum;
1173 apply_point_correction(
1174 bodies,
1175 joint.body_a,
1176 joint.body_b,
1177 r_a,
1178 r_b,
1179 perp_normal,
1180 lambda,
1181 );
1182 impulse = impulse + perp_normal * lambda;
1183 }
1184 }
1185
1186 if joint.limit_min.is_some() || joint.limit_max.is_some() {
1188 if let Some(min_d) = joint.limit_min.filter(|m| along_axis < *m) {
1189 let error = min_d - along_axis;
1190 let w_sum = body_a.inv_mass + body_b.inv_mass;
1191 if !w_sum.is_zero() {
1192 let inv_w_sum = Fix128::ONE / w_sum;
1193 let correction = world_axis * (error * inv_w_sum);
1194 impulse = impulse - correction;
1196 if !body_a.inv_mass.is_zero() {
1197 bodies[joint.body_a].position =
1198 bodies[joint.body_a].position - correction * body_a.inv_mass;
1199 }
1200 if !body_b.inv_mass.is_zero() {
1201 bodies[joint.body_b].position =
1202 bodies[joint.body_b].position + correction * body_b.inv_mass;
1203 }
1204 }
1205 } else if let Some(max_d) = joint.limit_max.filter(|m| along_axis > *m) {
1206 let error = along_axis - max_d;
1207 let w_sum = body_a.inv_mass + body_b.inv_mass;
1208 if !w_sum.is_zero() {
1209 let inv_w_sum = Fix128::ONE / w_sum;
1210 let correction = world_axis * (error * inv_w_sum);
1211 impulse = impulse + correction;
1212 if !body_a.inv_mass.is_zero() {
1213 bodies[joint.body_a].position =
1214 bodies[joint.body_a].position + correction * body_a.inv_mass;
1215 }
1216 if !body_b.inv_mass.is_zero() {
1217 bodies[joint.body_b].position =
1218 bodies[joint.body_b].position - correction * body_b.inv_mass;
1219 }
1220 }
1221 }
1222 }
1223 impulse
1224}
1225
1226fn solve_spring_joint(
1264 joint: &SpringJoint,
1265 bodies: &mut [crate::solver::RigidBody],
1266 dt: Fix128,
1267) -> Vec3Fix {
1268 let mut impulse = Vec3Fix::ZERO;
1269 let body_a = bodies[joint.body_a];
1270 let body_b = bodies[joint.body_b];
1271 if body_a.inv_mass.is_zero() && body_b.inv_mass.is_zero() {
1272 return impulse;
1273 }
1274
1275 let r_a = body_a.rotation.rotate_vec(joint.local_anchor_a);
1277 let r_b = body_b.rotation.rotate_vec(joint.local_anchor_b);
1278 let delta = (body_b.position + r_b) - (body_a.position + r_a);
1279
1280 let vel_a = body_a.velocity + body_a.angular_velocity.cross(r_a);
1282 let vel_b = body_b.velocity + body_b.angular_velocity.cross(r_b);
1283 let rel_vel = vel_b - vel_a;
1284
1285 let k = joint.stiffness;
1286 let c = joint.damping;
1287 let h2 = dt * dt;
1288
1289 let (normal, force) = if joint.rest_length.is_zero() {
1292 let (normal, force) = (delta * k + rel_vel * c).normalize_with_length();
1294 if force.is_zero() {
1295 return impulse;
1296 }
1297 (normal, force)
1298 } else {
1299 let (normal, distance) = delta.normalize_with_length();
1300 if distance.is_zero() {
1301 return impulse;
1302 }
1303 let displacement = distance - joint.rest_length;
1304 (normal, k * displacement + c * rel_vel.dot(normal))
1305 };
1306
1307 let w = point_w_sum(bodies, joint.body_a, joint.body_b, r_a, r_b, normal);
1308 let denom = Fix128::ONE + (k * h2 + c * dt) * w;
1309 if denom.is_zero() {
1310 return impulse;
1311 }
1312 let lambda = force * h2 / denom;
1313 apply_point_correction(bodies, joint.body_a, joint.body_b, r_a, r_b, normal, lambda);
1314 impulse = impulse + normal * lambda;
1315 impulse
1316}
1317
1318#[allow(clippy::too_many_lines)]
1320fn solve_d6_joint(joint: &D6Joint, bodies: &mut [crate::solver::RigidBody], dt: Fix128) -> Vec3Fix {
1321 let mut impulse = Vec3Fix::ZERO;
1322 let body_a = bodies[joint.body_a];
1323 let body_b = bodies[joint.body_b];
1324
1325 let anchor_a = body_a.position + body_a.rotation.rotate_vec(joint.local_anchor_a);
1329 let anchor_b = body_b.position + body_b.rotation.rotate_vec(joint.local_anchor_b);
1330 let delta = anchor_b - anchor_a;
1331
1332 let frame_a = body_a.rotation.mul(joint.local_frame_a);
1334 let axis_x = frame_a.rotate_vec(Vec3Fix::UNIT_X);
1335 let axis_y = frame_a.rotate_vec(Vec3Fix::UNIT_Y);
1336 let axis_z = frame_a.rotate_vec(Vec3Fix::UNIT_Z);
1337
1338 let compliance_term = joint.compliance / (dt * dt);
1339
1340 let axes = [
1342 (
1343 joint.linear_x,
1344 axis_x,
1345 joint.linear_limit_min.x,
1346 joint.linear_limit_max.x,
1347 ),
1348 (
1349 joint.linear_y,
1350 axis_y,
1351 joint.linear_limit_min.y,
1352 joint.linear_limit_max.y,
1353 ),
1354 (
1355 joint.linear_z,
1356 axis_z,
1357 joint.linear_limit_min.z,
1358 joint.linear_limit_max.z,
1359 ),
1360 ];
1361
1362 for &(motion, axis, limit_min, limit_max) in &axes {
1363 let proj = delta.dot(axis);
1364 let error = match motion {
1365 D6Motion::Locked => proj,
1366 D6Motion::Limited => {
1367 if proj < limit_min {
1368 proj - limit_min
1369 } else if proj > limit_max {
1370 proj - limit_max
1371 } else {
1372 Fix128::ZERO
1373 }
1374 }
1375 D6Motion::Free => Fix128::ZERO,
1376 };
1377
1378 if !error.is_zero() {
1379 let r_a = bodies[joint.body_a]
1384 .rotation
1385 .rotate_vec(joint.local_anchor_a);
1386 let r_b = bodies[joint.body_b]
1387 .rotation
1388 .rotate_vec(joint.local_anchor_b);
1389 let w_sum =
1390 point_w_sum(bodies, joint.body_a, joint.body_b, r_a, r_b, axis) + compliance_term;
1391 if !w_sum.is_zero() {
1392 let lambda = error / w_sum;
1393 apply_point_correction(bodies, joint.body_a, joint.body_b, r_a, r_b, axis, lambda);
1394 impulse = impulse + axis * lambda;
1395 }
1396 }
1397 }
1398
1399 let angular_compliance = joint.angular_compliance / (dt * dt);
1406 {
1407 let frame_a_now = bodies[joint.body_a].rotation.mul(joint.local_frame_a);
1408 let frame_b_now = bodies[joint.body_b].rotation.mul(joint.local_frame_b);
1409 let rel_quat = frame_b_now.mul(frame_a_now.conjugate());
1410
1411 let ang_axes = [
1412 (
1413 joint.angular_x,
1414 axis_x,
1415 joint.angular_limit_min.x,
1416 joint.angular_limit_max.x,
1417 ),
1418 (
1419 joint.angular_y,
1420 axis_y,
1421 joint.angular_limit_min.y,
1422 joint.angular_limit_max.y,
1423 ),
1424 (
1425 joint.angular_z,
1426 axis_z,
1427 joint.angular_limit_min.z,
1428 joint.angular_limit_max.z,
1429 ),
1430 ];
1431
1432 for &(motion, axis, limit_min, limit_max) in &ang_axes {
1433 let angle = compute_twist_angle(rel_quat, axis);
1434
1435 let error = match motion {
1436 D6Motion::Locked => angle,
1437 D6Motion::Limited => {
1438 if angle < limit_min {
1439 angle - limit_min
1440 } else if angle > limit_max {
1441 angle - limit_max
1442 } else {
1443 Fix128::ZERO
1444 }
1445 }
1446 D6Motion::Free => Fix128::ZERO,
1447 };
1448
1449 if !error.is_zero() {
1450 let w_ang =
1451 angular_w_sum(bodies, joint.body_a, joint.body_b, axis) + angular_compliance;
1452 if !w_ang.is_zero() {
1453 apply_angular_correction(
1454 bodies,
1455 joint.body_a,
1456 joint.body_b,
1457 axis,
1458 error / w_ang,
1459 );
1460 }
1461 }
1462 }
1463 }
1464 impulse
1465}
1466
1467fn solve_cone_twist_joint(
1469 joint: &ConeTwistJoint,
1470 bodies: &mut [crate::solver::RigidBody],
1471 dt: Fix128,
1472) -> Vec3Fix {
1473 let mut impulse = Vec3Fix::ZERO;
1474 let body_a = bodies[joint.body_a];
1475 let body_b = bodies[joint.body_b];
1476
1477 let r_a = body_a.rotation.rotate_vec(joint.local_anchor_a);
1479 let r_b = body_b.rotation.rotate_vec(joint.local_anchor_b);
1480 let anchor_a = body_a.position + r_a;
1481 let anchor_b = body_b.position + r_b;
1482 let delta = anchor_b - anchor_a;
1483 let (normal, distance) = delta.normalize_with_length();
1484
1485 if !distance.is_zero() {
1486 let compliance_term = joint.compliance / (dt * dt);
1487 let w_sum =
1488 point_w_sum(bodies, joint.body_a, joint.body_b, r_a, r_b, normal) + compliance_term;
1489
1490 if !w_sum.is_zero() {
1491 let lambda = distance / w_sum;
1492 apply_point_correction(bodies, joint.body_a, joint.body_b, r_a, r_b, normal, lambda);
1493 impulse = impulse + normal * lambda;
1494 }
1495 }
1496
1497 let body_a = bodies[joint.body_a];
1499 let body_b = bodies[joint.body_b];
1500 let world_axis_a = body_a.rotation.rotate_vec(joint.twist_axis_a).normalize();
1501 let world_axis_b = body_b.rotation.rotate_vec(joint.twist_axis_b).normalize();
1502
1503 let dot = world_axis_a.dot(world_axis_b);
1504 let cross = world_axis_a.cross(world_axis_b);
1505 let (correction_axis, cross_len) = cross.normalize_with_length();
1506 let cone_angle = Fix128::atan2(cross_len, dot);
1508 let cone_angle = if cone_angle < Fix128::ZERO {
1509 -cone_angle
1510 } else {
1511 cone_angle
1512 };
1513
1514 if cone_angle > joint.cone_limit {
1515 let error = cone_angle - joint.cone_limit;
1516
1517 if !cross_len.is_zero() {
1518 let angular_compliance = joint.angular_compliance / (dt * dt);
1519 let w_ang = angular_w_sum(bodies, joint.body_a, joint.body_b, correction_axis)
1520 + angular_compliance;
1521
1522 if !w_ang.is_zero() {
1523 let inv_w_ang = Fix128::ONE / w_ang;
1524 apply_angular_correction(
1525 bodies,
1526 joint.body_a,
1527 joint.body_b,
1528 correction_axis,
1529 error * inv_w_ang,
1530 );
1531 }
1532 }
1533 }
1534
1535 let rel_quat = bodies[joint.body_b]
1537 .rotation
1538 .mul(bodies[joint.body_a].rotation.conjugate());
1539 let twist_angle = compute_twist_angle(rel_quat, world_axis_a);
1540
1541 if twist_angle.abs() > joint.twist_limit {
1542 let error = if twist_angle > Fix128::ZERO {
1543 twist_angle - joint.twist_limit
1544 } else {
1545 twist_angle + joint.twist_limit
1546 };
1547
1548 let angular_compliance = joint.angular_compliance / (dt * dt);
1549 let w_ang =
1550 angular_w_sum(bodies, joint.body_a, joint.body_b, world_axis_a) + angular_compliance;
1551
1552 if !w_ang.is_zero() {
1553 let inv_w_ang = Fix128::ONE / w_ang;
1554 apply_angular_correction(
1555 bodies,
1556 joint.body_a,
1557 joint.body_b,
1558 world_axis_a,
1559 error * inv_w_ang,
1560 );
1561 }
1562 }
1563 impulse
1564}
1565
1566fn angular_inverse_mass(body: &crate::solver::RigidBody, axis: Vec3Fix) -> Fix128 {
1573 if body.inv_mass.is_zero() {
1574 return Fix128::ZERO;
1575 }
1576 let local = if crate::solver::inv_inertia_is_isotropic(body.inv_inertia) {
1577 axis
1578 } else {
1579 body.rotation.conjugate().rotate_vec(axis)
1580 };
1581 local.x * local.x * body.inv_inertia.x
1582 + local.y * local.y * body.inv_inertia.y
1583 + local.z * local.z * body.inv_inertia.z
1584}
1585
1586fn angular_w_sum(
1589 bodies: &[crate::solver::RigidBody],
1590 idx_a: usize,
1591 idx_b: usize,
1592 axis: Vec3Fix,
1593) -> Fix128 {
1594 angular_inverse_mass(&bodies[idx_a], axis) + angular_inverse_mass(&bodies[idx_b], axis)
1595}
1596
1597fn apply_angular_correction(
1605 bodies: &mut [crate::solver::RigidBody],
1606 idx_a: usize,
1607 idx_b: usize,
1608 axis: Vec3Fix,
1609 lambda: Fix128,
1610) {
1611 let w_a = angular_inverse_mass(&bodies[idx_a], axis);
1612 let w_b = angular_inverse_mass(&bodies[idx_b], axis);
1613 if !w_a.is_zero() {
1614 bodies[idx_a].rotation = rotate_by_angle(bodies[idx_a].rotation, axis, w_a * lambda);
1615 }
1616 if !w_b.is_zero() {
1617 bodies[idx_b].rotation = rotate_by_angle(bodies[idx_b].rotation, axis, -(w_b * lambda));
1618 }
1619}
1620
1621fn rotate_by_angle(q: QuatFix, axis: Vec3Fix, theta: Fix128) -> QuatFix {
1627 if theta.is_zero() {
1628 return q;
1629 }
1630 QuatFix::from_axis_angle(axis, theta).mul(q).normalize()
1631}
1632
1633fn point_angular_w(body: &crate::solver::RigidBody, r: Vec3Fix, normal: Vec3Fix) -> Fix128 {
1639 angular_inverse_mass(body, r.cross(normal))
1640}
1641
1642fn point_w_sum(
1653 bodies: &[crate::solver::RigidBody],
1654 idx_a: usize,
1655 idx_b: usize,
1656 r_a: Vec3Fix,
1657 r_b: Vec3Fix,
1658 normal: Vec3Fix,
1659) -> Fix128 {
1660 bodies[idx_a].inv_mass
1661 + bodies[idx_b].inv_mass
1662 + point_angular_w(&bodies[idx_a], r_a, normal)
1663 + point_angular_w(&bodies[idx_b], r_b, normal)
1664}
1665
1666fn apply_point_rotation(
1673 bodies: &mut [crate::solver::RigidBody],
1674 idx: usize,
1675 r: Vec3Fix,
1676 normal: Vec3Fix,
1677 lambda: Fix128,
1678) {
1679 let body = bodies[idx];
1680 if body.inv_mass.is_zero() {
1681 return;
1682 }
1683 let omega = body.world_inv_inertia_apply(r.cross(normal)) * lambda;
1684 let (axis, angle) = omega.normalize_with_length();
1685 if !angle.is_zero() {
1686 bodies[idx].rotation = rotate_by_angle(bodies[idx].rotation, axis, angle);
1687 }
1688}
1689
1690fn apply_point_correction(
1698 bodies: &mut [crate::solver::RigidBody],
1699 idx_a: usize,
1700 idx_b: usize,
1701 r_a: Vec3Fix,
1702 r_b: Vec3Fix,
1703 normal: Vec3Fix,
1704 lambda: Fix128,
1705) {
1706 let body_a = bodies[idx_a];
1707 let body_b = bodies[idx_b];
1708
1709 if !body_a.inv_mass.is_zero() {
1710 bodies[idx_a].position = body_a.position + normal * (lambda * body_a.inv_mass);
1711 }
1712 if !body_b.inv_mass.is_zero() {
1713 bodies[idx_b].position = body_b.position - normal * (lambda * body_b.inv_mass);
1714 }
1715 apply_point_rotation(bodies, idx_a, r_a, normal, lambda);
1716 apply_point_rotation(bodies, idx_b, r_b, normal, -lambda);
1717}
1718
1719pub(crate) fn compute_twist_angle(q: QuatFix, axis: Vec3Fix) -> Fix128 {
1726 let qv = Vec3Fix::new(q.x, q.y, q.z);
1727 let s = qv.dot(axis);
1728 let proj = axis * s;
1729 let twist = QuatFix::new(proj.x, proj.y, proj.z, q.w).normalize();
1730 let signed = Vec3Fix::new(twist.x, twist.y, twist.z).dot(axis);
1731 let (signed, w) = if twist.w.is_negative() {
1732 (-signed, -twist.w)
1733 } else {
1734 (signed, twist.w)
1735 };
1736 Fix128::atan2(signed, w).double()
1737}
1738
1739#[cfg(all(test, feature = "std"))]
1740mod tests {
1741 use super::*;
1742 use crate::solver::RigidBody;
1743
1744 #[test]
1745 fn test_ball_joint_holds() {
1746 let mut bodies = vec![
1747 RigidBody::new_static(Vec3Fix::ZERO),
1748 RigidBody::new(Vec3Fix::from_int(5, 0, 0), Fix128::ONE),
1749 ];
1750
1751 let joint = Joint::Ball(BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO));
1752 let dt = Fix128::from_ratio(1, 60);
1753
1754 for _ in 0..100 {
1755 solve_joints(&[joint], &mut bodies, dt);
1756 }
1757
1758 let dist = (bodies[1].position - bodies[0].position).length();
1760 assert!(
1761 dist < Fix128::from_int(5),
1762 "Ball joint should pull bodies together"
1763 );
1764 }
1765
1766 #[test]
1767 fn test_fixed_joint() {
1768 let mut bodies = vec![
1769 RigidBody::new_static(Vec3Fix::ZERO),
1770 RigidBody::new(Vec3Fix::from_int(3, 0, 0), Fix128::ONE),
1771 ];
1772
1773 let joint = Joint::Fixed(FixedJoint::new(
1774 0,
1775 1,
1776 Vec3Fix::from_int(1, 0, 0),
1777 Vec3Fix::ZERO,
1778 QuatFix::IDENTITY,
1779 ));
1780 let dt = Fix128::from_ratio(1, 60);
1781
1782 for _ in 0..100 {
1783 solve_joints(&[joint], &mut bodies, dt);
1784 }
1785
1786 let dist = (bodies[1].position - Vec3Fix::from_int(1, 0, 0)).length();
1788 assert!(
1789 dist < Fix128::from_int(3),
1790 "Fixed joint should hold position"
1791 );
1792 }
1793
1794 #[test]
1795 fn test_spring_joint() {
1796 let mut bodies = vec![
1797 RigidBody::new_static(Vec3Fix::ZERO),
1798 RigidBody::new(Vec3Fix::from_int(10, 0, 0), Fix128::ONE),
1799 ];
1800
1801 let joint = Joint::Spring(SpringJoint::new(
1802 0,
1803 1,
1804 Vec3Fix::ZERO,
1805 Vec3Fix::ZERO,
1806 Fix128::from_int(3), Fix128::from_int(10), Fix128::from_int(1), ));
1810 let dt = Fix128::from_ratio(1, 60);
1811
1812 for _ in 0..200 {
1813 solve_joints(&[joint], &mut bodies, dt);
1814 }
1815
1816 let dist = bodies[1].position.length();
1818 assert!(
1819 dist < Fix128::from_int(10),
1820 "Spring should pull body closer"
1821 );
1822 }
1823
1824 #[test]
1825 fn test_slider_joint() {
1826 let mut bodies = vec![
1827 RigidBody::new_static(Vec3Fix::ZERO),
1828 RigidBody::new(Vec3Fix::from_int(3, 5, 0), Fix128::ONE),
1829 ];
1830
1831 let joint = Joint::Slider(SliderJoint::new(
1832 0,
1833 1,
1834 Vec3Fix::UNIT_X,
1835 Vec3Fix::ZERO,
1836 Vec3Fix::ZERO,
1837 ));
1838 let dt = Fix128::from_ratio(1, 60);
1839
1840 for _ in 0..100 {
1841 solve_joints(&[joint], &mut bodies, dt);
1842 }
1843
1844 let y_abs = bodies[1].position.y.abs();
1846 assert!(
1847 y_abs < Fix128::from_int(5),
1848 "Slider should constrain perpendicular motion"
1849 );
1850 }
1851
1852 #[test]
1853 fn test_joint_bodies() {
1854 let ball = Joint::Ball(BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO));
1855 assert_eq!(ball.bodies(), (0, 1));
1856 assert_eq!(ball.joint_type(), JointType::Ball);
1857 }
1858
1859 #[allow(deprecated)] #[test]
1861 fn test_breakable_joint() {
1862 let mut bodies = vec![
1863 RigidBody::new_static(Vec3Fix::ZERO),
1864 RigidBody::new(Vec3Fix::from_int(20, 0, 0), Fix128::ONE), ];
1866
1867 let joint = Joint::Ball(
1869 BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO)
1870 .with_break_force(Fix128::from_int(5)),
1871 );
1872 let dt = Fix128::from_ratio(1, 60);
1873
1874 let broken = solve_joints_breakable(&[joint], &mut bodies, dt);
1875 assert_eq!(broken.len(), 1, "Joint should break under high force");
1877 assert_eq!(broken[0], 0);
1878 }
1879
1880 #[allow(deprecated)] #[test]
1882 fn test_unbreakable_joint() {
1883 let mut bodies = vec![
1884 RigidBody::new_static(Vec3Fix::ZERO),
1885 RigidBody::new(Vec3Fix::from_int(1, 0, 0), Fix128::ONE),
1886 ];
1887
1888 let joint = Joint::Ball(
1890 BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO)
1891 .with_break_force(Fix128::from_int(100)),
1892 );
1893 let dt = Fix128::from_ratio(1, 60);
1894
1895 let broken = solve_joints_breakable(&[joint], &mut bodies, dt);
1896 assert!(broken.is_empty(), "Joint should not break under low force");
1897 }
1898
1899 #[test]
1900 fn test_unbreakable_joint_on_force() {
1901 let mut bodies = vec![
1902 RigidBody::new_static(Vec3Fix::ZERO),
1903 RigidBody::new(Vec3Fix::from_int(1, 0, 0), Fix128::ONE),
1904 ];
1905
1906 let joint = Joint::Ball(
1908 BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO)
1909 .with_break_force(Fix128::from_int(4000)),
1910 );
1911 let dt = Fix128::from_ratio(1, 60);
1912
1913 let broken = solve_joints_breaking_on_force(&[joint], &mut bodies, dt);
1915 assert!(broken.is_empty(), "Joint should not break under low force");
1916 }
1917
1918 #[allow(deprecated)] #[test]
1920 fn test_no_break_force() {
1921 let mut bodies = vec![
1922 RigidBody::new_static(Vec3Fix::ZERO),
1923 RigidBody::new(Vec3Fix::from_int(100, 0, 0), Fix128::ONE),
1924 ];
1925
1926 let joint = Joint::Ball(BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO));
1928 let dt = Fix128::from_ratio(1, 60);
1929
1930 let broken = solve_joints_breakable(&[joint], &mut bodies, dt);
1931 assert!(
1932 broken.is_empty(),
1933 "Joint without break_force should never break"
1934 );
1935 }
1936
1937 fn fi(n: i64) -> Fix128 {
1941 Fix128::from_int(n)
1942 }
1943
1944 fn v3i(x: i64, y: i64, z: i64) -> Vec3Fix {
1945 Vec3Fix::from_int(x, y, z)
1946 }
1947
1948 const DT: Fix128 = Fix128 { hi: 0, lo: 1 << 62 }; fn pair(pa: Vec3Fix, ia: i64, pb: Vec3Fix, ib: i64) -> Vec<RigidBody> {
1952 let mut a = RigidBody::new_dynamic(pa, Fix128::ONE);
1953 let mut b = RigidBody::new_dynamic(pb, Fix128::ONE);
1954 a.inv_mass = fi(ia);
1955 b.inv_mass = fi(ib);
1956 a.inv_inertia = v3i(1, 1, 1);
1957 b.inv_inertia = v3i(1, 1, 1);
1958 if ia == 0 {
1959 a.body_type = crate::solver::BodyType::Static;
1960 }
1961 if ib == 0 {
1962 b.body_type = crate::solver::BodyType::Static;
1963 }
1964 vec![a, b]
1965 }
1966
1967 fn near(a: Fix128, b: Fix128) -> bool {
1968 (a - b).abs() < Fix128 { hi: 0, lo: 1 << 24 }
1969 }
1970
1971 fn near_v(a: Vec3Fix, b: Vec3Fix) -> bool {
1972 near(a.x, b.x) && near(a.y, b.y) && near(a.z, b.z)
1973 }
1974
1975 #[test]
1978 fn ball_joint_splits_gap_by_inverse_mass() {
1979 let mut bodies = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 3);
1981 let j = BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
1982 solve_ball_joint(&j, &mut bodies, DT);
1983 assert_eq!(bodies[0].position, v3i(1, 0, 0));
1984 assert_eq!(bodies[1].position, v3i(1, 0, 0));
1985 }
1986
1987 #[test]
1988 fn ball_joint_local_anchors_are_rotated_and_static_side_fixed() {
1989 let mut bodies = pair(Vec3Fix::ZERO, 0, v3i(3, 0, 0), 1);
1992 bodies[0].rotation = QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
1993 let j = BallJoint::new(0, 1, v3i(1, 0, 0), Vec3Fix::ZERO);
1994 solve_ball_joint(&j, &mut bodies, DT);
1995 assert_eq!(bodies[0].position, Vec3Fix::ZERO);
1996 assert_eq!(bodies[1].position, v3i(-1, 0, 0));
1997 let mut bodies2 = pair(Vec3Fix::ZERO, 0, v3i(3, 0, 0), 1);
2013 bodies2[0].rotation = QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
2014 let j2 = BallJoint::new(0, 1, v3i(1, 0, 0), v3i(0, 2, 0));
2015 solve_ball_joint(&j2, &mut bodies2, DT);
2016 assert!(
2017 near_v(
2018 bodies2[1].position,
2019 Vec3Fix::new(
2020 Fix128::from_ratio(43, 21),
2021 Fix128::from_ratio(-10, 21),
2022 Fix128::ZERO
2023 )
2024 ),
2025 "{:?}",
2026 bodies2[1].position
2027 );
2028 assert!(near(
2029 compute_twist_angle(bodies2[1].rotation, Vec3Fix::UNIT_Z),
2030 Fix128::from_ratio(40, 21)
2031 ));
2032 }
2033
2034 #[test]
2035 fn ball_joint_compliance_and_degenerate_cases() {
2036 let mut bodies = pair(Vec3Fix::ZERO, 0, v3i(4, 0, 0), 1);
2038 let mut j = BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
2039 j.compliance = Fix128::from_ratio(1, 16);
2040 solve_ball_joint(&j, &mut bodies, DT);
2041 assert_eq!(bodies[1].position, v3i(2, 0, 0));
2042 let mut same = pair(v3i(1, 1, 1), 1, v3i(1, 1, 1), 1);
2044 solve_ball_joint(
2045 &BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO),
2046 &mut same,
2047 DT,
2048 );
2049 assert_eq!(same[0].position, v3i(1, 1, 1));
2050 let mut statics = pair(Vec3Fix::ZERO, 0, v3i(4, 0, 0), 0);
2051 solve_ball_joint(
2052 &BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO),
2053 &mut statics,
2054 DT,
2055 );
2056 assert_eq!(statics[1].position, v3i(4, 0, 0));
2057 }
2058
2059 #[test]
2062 fn hinge_joint_positional_part_matches_ball_joint() {
2063 let mut bodies = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 3);
2064 let j = HingeJoint::new(
2065 0,
2066 1,
2067 Vec3Fix::ZERO,
2068 Vec3Fix::ZERO,
2069 Vec3Fix::UNIT_Z,
2070 Vec3Fix::UNIT_Z,
2071 );
2072 solve_hinge_joint(&j, &mut bodies, DT);
2073 assert_eq!(bodies[0].position, v3i(1, 0, 0));
2074 assert_eq!(bodies[1].position, v3i(1, 0, 0));
2075 assert_eq!(bodies[0].rotation, QuatFix::IDENTITY);
2077 assert_eq!(bodies[1].rotation, QuatFix::IDENTITY);
2078 }
2079
2080 #[test]
2081 fn hinge_joint_aligns_axes_monotonically() {
2082 let mut bodies = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2084 bodies[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_X, Fix128::from_ratio(1, 2));
2085 let j = HingeJoint::new(
2086 0,
2087 1,
2088 Vec3Fix::ZERO,
2089 Vec3Fix::ZERO,
2090 Vec3Fix::UNIT_Z,
2091 Vec3Fix::UNIT_Z,
2092 );
2093 let err = |b: &[RigidBody]| {
2094 let a = b[0].rotation.rotate_vec(Vec3Fix::UNIT_Z);
2095 let c = b[1].rotation.rotate_vec(Vec3Fix::UNIT_Z);
2096 a.cross(c).length()
2097 };
2098 let mut prev = err(&bodies);
2099 assert!(prev > Fix128::from_ratio(1, 10));
2100 for i in 0..30 {
2101 solve_hinge_joint(&j, &mut bodies, DT);
2102 let e = err(&bodies);
2103 assert!(e <= prev, "iter {i}: {prev:?} -> {e:?}");
2104 prev = e;
2105 }
2106 assert!(prev < Fix128::from_ratio(1, 1000));
2107 assert_eq!(bodies[0].rotation, QuatFix::IDENTITY);
2108 let mut both = pair(Vec3Fix::ZERO, 1, Vec3Fix::ZERO, 1);
2110 both[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_X, Fix128::from_ratio(1, 2));
2111 solve_hinge_joint(&j, &mut both, DT);
2112 assert!(both[0].rotation != QuatFix::IDENTITY);
2113 }
2114
2115 #[test]
2116 fn hinge_joint_limits_push_angle_back_into_range() {
2117 let j = HingeJoint::new(
2119 0,
2120 1,
2121 Vec3Fix::ZERO,
2122 Vec3Fix::ZERO,
2123 Vec3Fix::UNIT_Z,
2124 Vec3Fix::UNIT_Z,
2125 )
2126 .with_limits(Fix128::from_ratio(-1, 2), Fix128::from_ratio(1, 2));
2127 let angle = |b: &[RigidBody]| {
2128 let rel = b[1].rotation.mul(b[0].rotation.conjugate());
2129 compute_twist_angle(rel, Vec3Fix::UNIT_Z)
2130 };
2131 let mut over = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2132 over[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::ONE);
2133 let a0 = angle(&over);
2134 assert!(near(a0, Fix128::ONE), "{a0:?}");
2135 solve_hinge_joint(&j, &mut over, DT);
2136 let a1 = angle(&over);
2137 assert!(
2138 a1 < a0 && a1 >= Fix128::from_ratio(1, 2) - Fix128::from_ratio(1, 100),
2139 "{a0:?} -> {a1:?}"
2140 );
2141 let mut under = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2143 under[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, fi(-1));
2144 let b0 = angle(&under);
2145 solve_hinge_joint(&j, &mut under, DT);
2146 let b1 = angle(&under);
2147 assert!(b1 > b0, "{b0:?} -> {b1:?}");
2148 let mut inside = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2150 inside[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::from_ratio(1, 4));
2151 let r0 = inside[1].rotation;
2152 solve_hinge_joint(&j, &mut inside, DT);
2153 assert_eq!(inside[1].rotation, r0);
2154 let mut free = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2155 free[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::ONE);
2156 let f0 = free[1].rotation;
2157 let nolimit = HingeJoint::new(
2158 0,
2159 1,
2160 Vec3Fix::ZERO,
2161 Vec3Fix::ZERO,
2162 Vec3Fix::UNIT_Z,
2163 Vec3Fix::UNIT_Z,
2164 );
2165 solve_hinge_joint(&nolimit, &mut free, DT);
2166 assert_eq!(free[1].rotation, f0);
2167 }
2168
2169 #[test]
2172 fn fixed_joint_restores_relative_rotation_and_position() {
2173 let mut bodies = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 3);
2174 let j = FixedJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, QuatFix::IDENTITY);
2175 solve_fixed_joint(&j, &mut bodies, DT);
2176 assert_eq!(bodies[0].position, v3i(1, 0, 0));
2177 assert_eq!(bodies[1].position, v3i(1, 0, 0));
2178 let mut rot = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2180 rot[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::from_ratio(1, 2));
2181 let err = |b: &[RigidBody]| {
2182 let e = b[1].rotation.mul(b[0].rotation.conjugate());
2183 Vec3Fix::new(e.x, e.y, e.z).length()
2184 };
2185 let mut prev = err(&rot);
2186 for i in 0..30 {
2187 solve_fixed_joint(&j, &mut rot, DT);
2188 let e = err(&rot);
2189 assert!(e <= prev, "iter {i}");
2190 prev = e;
2191 }
2192 assert!(prev < Fix128::from_ratio(1, 1000));
2193 let target = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::HALF_PI);
2195 let j90 = FixedJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, target);
2196 let mut ok = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2197 ok[1].rotation = target;
2198 let before = ok[1].rotation;
2199 solve_fixed_joint(&j90, &mut ok, DT);
2200 assert!(
2201 near(ok[1].rotation.x, before.x)
2202 && near(ok[1].rotation.z, before.z)
2203 && near(ok[1].rotation.w, before.w)
2204 );
2205 }
2206
2207 #[test]
2210 fn slider_joint_removes_perpendicular_offset_only() {
2211 let mut bodies = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2213 let j = SliderJoint::new(0, 1, Vec3Fix::UNIT_X, Vec3Fix::ZERO, Vec3Fix::ZERO);
2214 solve_slider_joint(&j, &mut bodies, DT);
2215 assert_eq!(bodies[1].position, v3i(3, 0, 0));
2216 let mut both = pair(Vec3Fix::ZERO, 1, v3i(3, 4, 0), 3);
2218 solve_slider_joint(&j, &mut both, DT);
2219 assert_eq!(both[0].position, v3i(0, 1, 0));
2220 assert_eq!(both[1].position, v3i(3, 1, 0));
2221 let mut rot = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2223 rot[0].rotation = QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
2224 solve_slider_joint(&j, &mut rot, DT);
2225 assert_eq!(rot[1].position, v3i(3, 0, 0));
2226 }
2227
2228 #[test]
2229 fn slider_joint_limits_clamp_travel_along_axis() {
2230 let j = SliderJoint::new(0, 1, Vec3Fix::UNIT_X, Vec3Fix::ZERO, Vec3Fix::ZERO)
2231 .with_limits(fi(-1), fi(2));
2232 let mut over = pair(Vec3Fix::ZERO, 0, v3i(5, 0, 0), 1);
2234 solve_slider_joint(&j, &mut over, DT);
2235 assert_eq!(over[1].position, v3i(2, 0, 0));
2236 let mut under = pair(Vec3Fix::ZERO, 0, v3i(-4, 0, 0), 1);
2238 solve_slider_joint(&j, &mut under, DT);
2239 assert_eq!(under[1].position, v3i(-1, 0, 0));
2240 let mut both = pair(Vec3Fix::ZERO, 1, v3i(6, 0, 0), 3);
2242 solve_slider_joint(&j, &mut both, DT);
2243 assert_eq!(both[0].position, v3i(1, 0, 0));
2244 assert_eq!(both[1].position, v3i(3, 0, 0));
2245 let mut edge = pair(Vec3Fix::ZERO, 0, v3i(2, 0, 0), 1);
2247 solve_slider_joint(&j, &mut edge, DT);
2248 assert_eq!(edge[1].position, v3i(2, 0, 0));
2249 let mut inside = pair(Vec3Fix::ZERO, 0, v3i(1, 0, 0), 1);
2250 solve_slider_joint(&j, &mut inside, DT);
2251 assert_eq!(inside[1].position, v3i(1, 0, 0));
2252 }
2253
2254 #[test]
2257 fn spring_joint_force_is_stiffness_times_displacement_plus_damping() {
2258 let mut bodies = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 3);
2261 let j = SpringJoint::new(
2262 0,
2263 1,
2264 Vec3Fix::ZERO,
2265 Vec3Fix::ZERO,
2266 fi(1),
2267 fi(2),
2268 Fix128::ZERO,
2269 );
2270 solve_spring_joint(&j, &mut bodies, DT);
2271 assert_eq!(
2272 bodies[0].position,
2273 Vec3Fix::new(Fix128::from_ratio(1, 4), Fix128::ZERO, Fix128::ZERO)
2274 );
2275 assert_eq!(
2276 bodies[1].position,
2277 Vec3Fix::new(Fix128::from_ratio(13, 4), Fix128::ZERO, Fix128::ZERO)
2278 );
2279 let mut comp = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 1);
2281 let jc = SpringJoint::new(
2282 0,
2283 1,
2284 Vec3Fix::ZERO,
2285 Vec3Fix::ZERO,
2286 fi(8),
2287 fi(2),
2288 Fix128::ZERO,
2289 );
2290 solve_spring_joint(&jc, &mut comp, DT);
2291 assert!(near_v(
2292 comp[0].position,
2293 Vec3Fix::new(Fix128::from_ratio(-2, 5), Fix128::ZERO, Fix128::ZERO)
2294 ));
2295 assert!(near_v(
2296 comp[1].position,
2297 Vec3Fix::new(Fix128::from_ratio(22, 5), Fix128::ZERO, Fix128::ZERO)
2298 ));
2299 let mut damp = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 1);
2303 damp[1].velocity = v3i(3, 0, 0);
2304 let jd = SpringJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, fi(4), fi(2), fi(2));
2305 solve_spring_joint(&jd, &mut damp, DT);
2306 assert!(near_v(
2307 damp[0].position,
2308 Vec3Fix::new(Fix128::from_ratio(1, 6), Fix128::ZERO, Fix128::ZERO)
2309 ));
2310 assert!(near_v(
2311 damp[1].position,
2312 Vec3Fix::new(Fix128::from_ratio(23, 6), Fix128::ZERO, Fix128::ZERO)
2313 ));
2314 let mut rest = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 1);
2316 solve_spring_joint(&jd, &mut rest, DT);
2317 assert_eq!(rest[1].position, v3i(4, 0, 0));
2318 }
2319
2320 #[test]
2323 fn d6_joint_linear_locked_limited_and_free_axes() {
2324 let mut j = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
2325 j.linear_x = D6Motion::Locked;
2326 j.linear_y = D6Motion::Limited; j.linear_z = D6Motion::Free;
2328 let mut bodies = pair(Vec3Fix::ZERO, 0, v3i(4, 3, 5), 1);
2330 solve_d6_joint(&j, &mut bodies, DT);
2331 assert_eq!(bodies[1].position, v3i(0, 1, 5));
2332 let mut low = pair(Vec3Fix::ZERO, 0, v3i(0, -6, 0), 1);
2334 solve_d6_joint(&j, &mut low, DT);
2335 assert_eq!(low[1].position, v3i(0, -1, 0));
2336 let mut both = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 3);
2338 solve_d6_joint(&j, &mut both, DT);
2339 assert_eq!(both[0].position, v3i(1, 0, 0));
2340 assert_eq!(both[1].position, v3i(1, 0, 0));
2341 let mut jf = j;
2343 jf.local_frame_a = QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
2344 let mut flipped = pair(Vec3Fix::ZERO, 0, v3i(4, 0, 0), 1);
2345 solve_d6_joint(&jf, &mut flipped, DT);
2346 assert_eq!(flipped[1].position, Vec3Fix::ZERO);
2347 }
2348
2349 #[test]
2365 fn d6_joint_second_locked_axis_uses_the_rotation_from_the_first() {
2366 let mut j = D6Joint::new(0, 1, Vec3Fix::ZERO, v3i(0, 2, 0));
2367 j.linear_x = D6Motion::Locked;
2368 j.linear_y = D6Motion::Locked;
2369 let mut bodies = pair(Vec3Fix::ZERO, 0, v3i(4, 3, 0), 1);
2370 solve_d6_joint(&j, &mut bodies, DT);
2371
2372 let theta = Fix128::from_ratio(8, 5);
2376 let sin_theta = theta.sin();
2377 let want_y = Fix128::from_int(3)
2378 - Fix128::from_int(5) / (Fix128::ONE + Fix128::from_int(4) * sin_theta * sin_theta);
2379 assert!(
2380 near(bodies[1].position.x, Fix128::from_ratio(16, 5)),
2381 "x {:?}",
2382 bodies[1].position.x
2383 );
2384 assert!(
2385 near(bodies[1].position.y, want_y),
2386 "y {:?} (want {want_y:?})",
2387 bodies[1].position.y
2388 );
2389 assert!(
2391 !near(bodies[1].position.y, Fix128::from_int(-2)),
2392 "y landed on the stale-cache value: {:?}",
2393 bodies[1].position.y
2394 );
2395 }
2396
2397 #[test]
2398 fn d6_joint_angular_locked_axis_pulls_rotation_back() {
2399 let mut j = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
2400 j.angular_z = D6Motion::Locked;
2401 let angle = |b: &[RigidBody]| {
2402 let rel = b[1].rotation.mul(b[0].rotation.conjugate());
2403 compute_twist_angle(rel, Vec3Fix::UNIT_Z)
2404 };
2405 let mut bodies = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2406 bodies[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::from_ratio(1, 2));
2407 let mut prev = angle(&bodies).abs();
2408 let noise = Fix128::from_raw(0, 1 << 24);
2411 for i in 0..30 {
2412 solve_d6_joint(&j, &mut bodies, DT);
2413 let a = angle(&bodies).abs();
2414 assert!(a <= prev + noise, "iter {i}");
2415 prev = a;
2416 }
2417 assert!(prev < Fix128::from_ratio(1, 1000));
2418 let mut jl = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
2420 jl.angular_z = D6Motion::Limited;
2421 jl.angular_limit_min = Vec3Fix::new(
2422 Fix128::from_ratio(-1, 4),
2423 Fix128::from_ratio(-1, 4),
2424 Fix128::from_ratio(-1, 4),
2425 );
2426 jl.angular_limit_max = Vec3Fix::new(
2427 Fix128::from_ratio(1, 4),
2428 Fix128::from_ratio(1, 4),
2429 Fix128::from_ratio(1, 4),
2430 );
2431 let mut over = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2432 over[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::from_ratio(1, 2));
2433 let o0 = angle(&over);
2434 solve_d6_joint(&jl, &mut over, DT);
2435 assert!(angle(&over) < o0);
2436 let mut inside = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2437 inside[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::from_ratio(1, 10));
2438 let r0 = inside[1].rotation;
2439 solve_d6_joint(&jl, &mut inside, DT);
2440 assert_eq!(inside[1].rotation, r0);
2441 let free = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
2442 let mut f = pair(Vec3Fix::ZERO, 0, v3i(4, 3, 5), 1);
2443 f[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::from_ratio(1, 2));
2444 let (p0, q0) = (f[1].position, f[1].rotation);
2445 solve_d6_joint(&free, &mut f, DT);
2446 assert_eq!((f[1].position, f[1].rotation), (p0, q0));
2447 }
2448
2449 #[test]
2452 fn cone_twist_joint_enforces_cone_and_twist_limits() {
2453 let cone_angle = |b: &[RigidBody]| {
2455 let a = b[0].rotation.rotate_vec(Vec3Fix::UNIT_Z);
2456 let c = b[1].rotation.rotate_vec(Vec3Fix::UNIT_Z);
2457 Fix128::atan2(a.cross(c).length(), a.dot(c))
2458 };
2459 let j = ConeTwistJoint::new(
2460 0,
2461 1,
2462 Vec3Fix::ZERO,
2463 Vec3Fix::ZERO,
2464 Vec3Fix::UNIT_Z,
2465 Vec3Fix::UNIT_Z,
2466 )
2467 .with_limits(Fix128::from_ratio(1, 4), Fix128::from_ratio(1, 4));
2468 let mut tilted = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2469 tilted[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_X, Fix128::ONE);
2470 let c0 = cone_angle(&tilted);
2471 assert!(near(c0, Fix128::ONE), "{c0:?}");
2472 solve_cone_twist_joint(&j, &mut tilted, DT);
2473 let c1 = cone_angle(&tilted);
2474 assert!(c1 < c0, "{c0:?} -> {c1:?}");
2475 let mut prev = c1;
2477 for _ in 0..40 {
2478 solve_cone_twist_joint(&j, &mut tilted, DT);
2479 let c = cone_angle(&tilted);
2480 assert!(c <= prev + Fix128 { hi: 0, lo: 1 << 30 });
2481 prev = c;
2482 }
2483 assert!(
2484 prev < Fix128::from_ratio(1, 4) + Fix128::from_ratio(1, 50),
2485 "{prev:?}"
2486 );
2487 let twist = |b: &[RigidBody]| {
2489 let rel = b[1].rotation.mul(b[0].rotation.conjugate());
2490 compute_twist_angle(rel, Vec3Fix::UNIT_Z)
2491 };
2492 let mut twisted = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2493 twisted[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::ONE);
2494 let t0 = twist(&twisted);
2495 solve_cone_twist_joint(&j, &mut twisted, DT);
2496 assert!(twist(&twisted) < t0);
2497 let mut inside = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2499 inside[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_X, Fix128::from_ratio(1, 10));
2500 let r0 = inside[1].rotation;
2501 solve_cone_twist_joint(&j, &mut inside, DT);
2502 assert_eq!(inside[1].rotation, r0);
2503 let mut pos = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 3);
2504 solve_cone_twist_joint(&j, &mut pos, DT);
2505 assert_eq!(pos[0].position, v3i(1, 0, 0));
2506 assert_eq!(pos[1].position, v3i(1, 0, 0));
2507 }
2508
2509 #[test]
2512 fn joint_bodies_and_compute_force_per_kind() {
2513 let bodies = pair(Vec3Fix::ZERO, 1, v3i(3, 4, 0), 1); let ball = Joint::Ball(BallJoint::new(2, 7, Vec3Fix::ZERO, Vec3Fix::ZERO));
2515 assert_eq!(ball.bodies(), (2, 7));
2516 let ball01 = Joint::Ball(BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO));
2517 assert_eq!(ball01.compute_force(&bodies), fi(5));
2518 let hinge = Joint::Hinge(HingeJoint::new(
2519 0,
2520 1,
2521 Vec3Fix::ZERO,
2522 Vec3Fix::ZERO,
2523 Vec3Fix::UNIT_Z,
2524 Vec3Fix::UNIT_Z,
2525 ));
2526 assert_eq!(hinge.compute_force(&bodies), fi(5));
2527 let fixed = Joint::Fixed(FixedJoint::new(
2528 0,
2529 1,
2530 Vec3Fix::ZERO,
2531 Vec3Fix::ZERO,
2532 QuatFix::IDENTITY,
2533 ));
2534 assert_eq!(fixed.compute_force(&bodies), fi(5));
2535 let slider = Joint::Slider(SliderJoint::new(
2537 0,
2538 1,
2539 Vec3Fix::UNIT_X,
2540 Vec3Fix::ZERO,
2541 Vec3Fix::ZERO,
2542 ));
2543 assert_eq!(slider.compute_force(&bodies), fi(4));
2544 let spring = Joint::Spring(SpringJoint::new(
2546 0,
2547 1,
2548 Vec3Fix::ZERO,
2549 Vec3Fix::ZERO,
2550 fi(8),
2551 fi(2),
2552 Fix128::ZERO,
2553 ));
2554 assert_eq!(spring.compute_force(&bodies), fi(6));
2555 let d6 = Joint::D6(D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO));
2556 assert_eq!(d6.compute_force(&bodies), fi(5));
2557 let ct = Joint::ConeTwist(ConeTwistJoint::new(
2558 0,
2559 1,
2560 Vec3Fix::ZERO,
2561 Vec3Fix::ZERO,
2562 Vec3Fix::UNIT_Z,
2563 Vec3Fix::UNIT_Z,
2564 ));
2565 assert_eq!(ct.compute_force(&bodies), fi(5));
2566 let anchored = Joint::Ball(BallJoint::new(0, 1, v3i(3, 4, 0), Vec3Fix::ZERO));
2568 assert_eq!(anchored.compute_force(&bodies), Fix128::ZERO);
2569 }
2570
2571 #[allow(deprecated)] #[test]
2573 fn solve_joints_breakable_breaks_strictly_above_threshold_and_skips_broken() {
2574 let mk = |bf: i64| {
2576 Joint::Ball(BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO).with_break_force(fi(bf)))
2577 };
2578 let mut hold = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2579 let broken = solve_joints_breakable(&[mk(5)], &mut hold, DT);
2580 assert!(broken.is_empty());
2581 assert!(
2582 near_v(hold[1].position, Vec3Fix::ZERO),
2583 "解かれて A に一致 {:?}",
2584 hold[1].position
2585 );
2586 let mut snap = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2587 let broken2 = solve_joints_breakable(&[mk(4)], &mut snap, DT);
2588 assert_eq!(broken2, vec![0]);
2589 assert_eq!(snap[1].position, v3i(3, 4, 0), "壊れた joint は解かれない");
2590 let mut multi = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2592 let js = [mk(4), mk(100), mk(1)];
2593 let broken3 = solve_joints_breakable(&js, &mut multi, DT);
2594 assert_eq!(broken3, vec![2, 0]);
2595 assert!(near_v(multi[1].position, Vec3Fix::ZERO));
2596 let mut all = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2598 solve_joints(&[mk(1)], &mut all, DT);
2599 assert!(near_v(all[1].position, Vec3Fix::ZERO));
2600 }
2601
2602 #[test]
2603 fn breaking_on_force_breaks_strictly_above_threshold_and_skips_broken() {
2604 let mk = |bf: i64| {
2606 Joint::Ball(BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO).with_break_force(fi(bf)))
2607 };
2608 let mut hold = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2609 let broken = solve_joints_breaking_on_force(&[mk(80)], &mut hold, DT);
2610 assert!(broken.is_empty());
2611 assert!(
2612 near_v(hold[1].position, Vec3Fix::ZERO),
2613 "解かれて A に一致 {:?}",
2614 hold[1].position
2615 );
2616 let mut snap = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2617 let broken2 = solve_joints_breaking_on_force(&[mk(64)], &mut snap, DT);
2618 assert_eq!(broken2, vec![0]);
2619 assert!(
2621 near_v(snap[1].position, Vec3Fix::ZERO),
2622 "{:?}",
2623 snap[1].position
2624 );
2625 let mut multi = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2628 let js = [mk(64), mk(1600), mk(16)];
2629 let broken3 = solve_joints_breaking_on_force(&js, &mut multi, DT);
2630 assert_eq!(broken3, vec![0]);
2631 assert!(near_v(multi[1].position, Vec3Fix::ZERO));
2632 let mut all = pair(Vec3Fix::ZERO, 0, v3i(3, 4, 0), 1);
2634 solve_joints(&[mk(16)], &mut all, DT);
2635 assert!(near_v(all[1].position, Vec3Fix::ZERO));
2636 }
2637
2638 #[test]
2641 fn d6_with_linear_motion_and_limits_builders_drive_solver() {
2642 let j = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO)
2644 .with_linear_motion(D6Motion::Locked, D6Motion::Limited, D6Motion::Free)
2645 .with_linear_limits(v3i(-7, -2, -9), v3i(7, 2, 9));
2646 assert_eq!(
2647 (j.linear_x, j.linear_y, j.linear_z),
2648 (D6Motion::Locked, D6Motion::Limited, D6Motion::Free)
2649 );
2650 assert_eq!(j.linear_limit_min, v3i(-7, -2, -9));
2651 assert_eq!(j.linear_limit_max, v3i(7, 2, 9));
2652 assert_eq!(
2654 (j.angular_x, j.angular_y, j.angular_z),
2655 (D6Motion::Free, D6Motion::Free, D6Motion::Free)
2656 );
2657 assert_eq!(j.angular_limit_max.y, Fix128::PI);
2658
2659 let mut manual = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
2661 manual.linear_x = D6Motion::Locked;
2662 manual.linear_y = D6Motion::Limited;
2663 manual.linear_z = D6Motion::Free;
2664 manual.linear_limit_min = v3i(-7, -2, -9);
2665 manual.linear_limit_max = v3i(7, 2, 9);
2666 assert_eq!(j, manual);
2667
2668 let mut bodies = pair(Vec3Fix::ZERO, 0, v3i(4, 3, 5), 1);
2670 solve_d6_joint(&j, &mut bodies, DT);
2671 assert_eq!(bodies[1].position, v3i(0, 2, 5));
2672 let mut low = pair(Vec3Fix::ZERO, 0, v3i(0, -6, 0), 1);
2674 solve_d6_joint(&j, &mut low, DT);
2675 assert_eq!(low[1].position, v3i(0, -2, 0));
2676 let mut inside = pair(Vec3Fix::ZERO, 0, v3i(0, 1, 0), 1);
2678 solve_d6_joint(&j, &mut inside, DT);
2679 assert_eq!(inside[1].position, v3i(0, 1, 0));
2680 }
2681
2682 #[test]
2683 fn d6_with_angular_motion_and_limits_builders_drive_solver() {
2684 let q = Fix128::from_ratio(1, 4);
2685 let j = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO)
2686 .with_angular_motion(D6Motion::Free, D6Motion::Locked, D6Motion::Limited)
2687 .with_angular_limits(Vec3Fix::new(-q, -q, -q), Vec3Fix::new(q, q, q));
2688 assert_eq!(
2689 (j.angular_x, j.angular_y, j.angular_z),
2690 (D6Motion::Free, D6Motion::Locked, D6Motion::Limited)
2691 );
2692 assert_eq!(j.angular_limit_min, Vec3Fix::new(-q, -q, -q));
2693 assert_eq!(j.angular_limit_max, Vec3Fix::new(q, q, q));
2694 assert_eq!(
2696 (j.linear_x, j.linear_y, j.linear_z),
2697 (D6Motion::Free, D6Motion::Free, D6Motion::Free)
2698 );
2699 assert_eq!(j.linear_limit_min, v3i(-1, -1, -1));
2700
2701 let angle = |b: &[RigidBody], axis: Vec3Fix| {
2702 let rel = b[1].rotation.mul(b[0].rotation.conjugate());
2703 compute_twist_angle(rel, axis)
2704 };
2705 let mut over = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2707 over[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::from_ratio(1, 2));
2708 let o0 = angle(&over, Vec3Fix::UNIT_Z);
2709 solve_d6_joint(&j, &mut over, DT);
2710 let o1 = angle(&over, Vec3Fix::UNIT_Z);
2711 assert!(o1 < o0, "{o1:?} < {o0:?}");
2712 assert!(
2714 o1 >= q - Fix128::from_raw(0, 1 << 24),
2715 "limit は over-correct しない: {o1:?}"
2716 );
2717 let mut inside = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2719 inside[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Z, Fix128::from_ratio(1, 10));
2720 let z0 = angle(&inside, Vec3Fix::UNIT_Z);
2721 solve_d6_joint(&j, &mut inside, DT);
2722 let z1 = angle(&inside, Vec3Fix::UNIT_Z);
2723 assert!(near(z0, z1), "{z0:?} vs {z1:?}");
2724 let mut locked = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2726 locked[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_Y, Fix128::from_ratio(1, 10));
2727 let l0 = angle(&locked, Vec3Fix::UNIT_Y).abs();
2728 solve_d6_joint(&j, &mut locked, DT);
2729 assert!(angle(&locked, Vec3Fix::UNIT_Y).abs() < l0);
2730 let mut free = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2731 free[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_X, Fix128::from_ratio(1, 2));
2732 let x0 = angle(&free, Vec3Fix::UNIT_X);
2733 solve_d6_joint(&j, &mut free, DT);
2734 let x1 = angle(&free, Vec3Fix::UNIT_X);
2735 assert!(near(x0, x1), "{x0:?} vs {x1:?}");
2736 assert!(
2737 x1 > Fix128::from_ratio(49, 100),
2738 "x Free は 1/2 のまま: {x1:?}"
2739 );
2740 }
2741
2742 fn anchored_pair() -> Vec<RigidBody> {
2747 let mut b = pair(Vec3Fix::ZERO, 0, v3i(3, -2, 0), 1);
2748 b[0].rotation = QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
2749 b
2750 }
2751
2752 const A_ANCHOR: Vec3Fix = Vec3Fix {
2753 x: Fix128::ONE,
2754 y: Fix128::ZERO,
2755 z: Fix128::ZERO,
2756 };
2757 const B_ANCHOR: Vec3Fix = Vec3Fix {
2758 x: Fix128::ZERO,
2759 y: Fix128 { hi: 2, lo: 0 },
2760 z: Fix128::ZERO,
2761 };
2762
2763 #[test]
2764 fn every_joint_positional_part_uses_rotated_local_anchors() {
2765 let want_pos = Vec3Fix::new(
2779 Fix128::from_ratio(11, 5),
2780 -Fix128::from_int(2),
2781 Fix128::ZERO,
2782 );
2783 let want_twist = Fix128::from_ratio(8, 5);
2784
2785 let mut b = anchored_pair();
2786 solve_ball_joint(&BallJoint::new(0, 1, A_ANCHOR, B_ANCHOR), &mut b, DT);
2787 assert!(near_v(b[1].position, want_pos), "ball {:?}", b[1].position);
2788 assert!(near(
2789 compute_twist_angle(b[1].rotation, Vec3Fix::UNIT_Z),
2790 want_twist
2791 ));
2792
2793 let mut h = anchored_pair();
2797 solve_hinge_joint(
2798 &HingeJoint::new(0, 1, A_ANCHOR, B_ANCHOR, Vec3Fix::UNIT_Z, Vec3Fix::UNIT_Z),
2799 &mut h,
2800 DT,
2801 );
2802 assert!(near_v(h[1].position, want_pos), "hinge {:?}", h[1].position);
2803 assert!(near(
2804 compute_twist_angle(h[1].rotation, Vec3Fix::UNIT_Z),
2805 want_twist
2806 ));
2807
2808 let mut f = anchored_pair();
2816 solve_fixed_joint(
2817 &FixedJoint::new(
2818 0,
2819 1,
2820 A_ANCHOR,
2821 B_ANCHOR,
2822 QuatFix::new(Fix128::ZERO, Fix128::ZERO, -Fix128::ONE, Fix128::ZERO),
2823 ),
2824 &mut f,
2825 DT,
2826 );
2827 assert!(near_v(f[1].position, want_pos), "fixed {:?}", f[1].position);
2828 assert_eq!(
2829 f[1].rotation,
2830 QuatFix::IDENTITY,
2831 "fixed rotation {:?}",
2832 f[1].rotation
2833 );
2834
2835 let mut ct = anchored_pair();
2840 solve_cone_twist_joint(
2841 &ConeTwistJoint::new(0, 1, A_ANCHOR, B_ANCHOR, Vec3Fix::UNIT_Z, Vec3Fix::UNIT_Z),
2842 &mut ct,
2843 DT,
2844 );
2845 assert!(
2846 near_v(ct[1].position, want_pos),
2847 "cone {:?}",
2848 ct[1].position
2849 );
2850 assert!(near(
2851 compute_twist_angle(ct[1].rotation, Vec3Fix::UNIT_Z),
2852 want_twist
2853 ));
2854
2855 let mut d6j = D6Joint::new(0, 1, A_ANCHOR, B_ANCHOR);
2862 d6j.linear_x = D6Motion::Locked;
2863 d6j.linear_y = D6Motion::Locked;
2864 d6j.linear_z = D6Motion::Locked;
2865 let mut d = anchored_pair();
2866 solve_d6_joint(&d6j, &mut d, DT);
2867 assert!(near_v(d[1].position, want_pos), "d6 {:?}", d[1].position);
2868 assert!(near(
2869 compute_twist_angle(d[1].rotation, Vec3Fix::UNIT_Z),
2870 want_twist
2871 ));
2872 let mut sl = anchored_pair();
2881 solve_slider_joint(
2882 &SliderJoint::new(0, 1, Vec3Fix::UNIT_X, A_ANCHOR, v3i(0, 3, 0)),
2883 &mut sl,
2884 DT,
2885 );
2886 assert!(
2887 near_v(sl[1].position, v3i(3, -3, 0)),
2888 "slider {:?}",
2889 sl[1].position
2890 );
2891 let mut sp0 = anchored_pair();
2893 solve_spring_joint(
2894 &SpringJoint::new(0, 1, A_ANCHOR, B_ANCHOR, fi(4), Fix128::ONE, Fix128::ZERO),
2895 &mut sp0,
2896 DT,
2897 );
2898 assert_eq!(sp0[1].position, v3i(3, -2, 0));
2899 let mut sp = anchored_pair();
2900 solve_spring_joint(
2901 &SpringJoint::new(0, 1, A_ANCHOR, B_ANCHOR, fi(2), Fix128::ONE, Fix128::ZERO),
2902 &mut sp,
2903 DT,
2904 );
2905 assert!(
2909 near_v(
2910 sp[1].position,
2911 Vec3Fix::new(Fix128::from_ratio(61, 21), fi(-2), Fix128::ZERO)
2912 ),
2913 "spring {:?}",
2914 sp[1].position
2915 );
2916 assert!(near(
2917 compute_twist_angle(sp[1].rotation, Vec3Fix::UNIT_Z),
2918 Fix128::from_ratio(4, 21)
2919 ));
2920 }
2921
2922 #[test]
2923 fn compliance_softens_every_positional_joint_identically() {
2924 let expect = v3i(2, 0, 0);
2926 let mut h = HingeJoint::new(
2927 0,
2928 1,
2929 Vec3Fix::ZERO,
2930 Vec3Fix::ZERO,
2931 Vec3Fix::UNIT_Z,
2932 Vec3Fix::UNIT_Z,
2933 );
2934 h.compliance = Fix128::from_ratio(1, 16);
2935 let mut bh = pair(Vec3Fix::ZERO, 0, v3i(4, 0, 0), 1);
2936 solve_hinge_joint(&h, &mut bh, DT);
2937 assert_eq!(bh[1].position, expect, "hinge");
2938 let mut f = FixedJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, QuatFix::IDENTITY);
2939 f.compliance = Fix128::from_ratio(1, 16);
2940 let mut bf = pair(Vec3Fix::ZERO, 0, v3i(4, 0, 0), 1);
2941 solve_fixed_joint(&f, &mut bf, DT);
2942 assert_eq!(bf[1].position, expect, "fixed");
2943 let mut c = ConeTwistJoint::new(
2944 0,
2945 1,
2946 Vec3Fix::ZERO,
2947 Vec3Fix::ZERO,
2948 Vec3Fix::UNIT_Z,
2949 Vec3Fix::UNIT_Z,
2950 );
2951 c.compliance = Fix128::from_ratio(1, 16);
2952 let mut bc = pair(Vec3Fix::ZERO, 0, v3i(4, 0, 0), 1);
2953 solve_cone_twist_joint(&c, &mut bc, DT);
2954 assert_eq!(bc[1].position, expect, "cone");
2955 let mut s = SliderJoint::new(0, 1, Vec3Fix::UNIT_X, Vec3Fix::ZERO, Vec3Fix::ZERO);
2956 s.compliance = Fix128::from_ratio(1, 16);
2957 let mut bs = pair(Vec3Fix::ZERO, 0, v3i(0, 4, 0), 1); solve_slider_joint(&s, &mut bs, DT);
2959 assert_eq!(bs[1].position, v3i(0, 2, 0), "slider");
2960 let mut d = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
2961 d.linear_x = D6Motion::Locked;
2962 d.compliance = Fix128::from_ratio(1, 16);
2963 let mut bd = pair(Vec3Fix::ZERO, 0, v3i(4, 0, 0), 1);
2964 solve_d6_joint(&d, &mut bd, DT);
2965 assert_eq!(bd[1].position, expect, "d6");
2966 let mk = |ac: Fix128| {
2968 let mut j = HingeJoint::new(
2969 0,
2970 1,
2971 Vec3Fix::ZERO,
2972 Vec3Fix::ZERO,
2973 Vec3Fix::UNIT_Z,
2974 Vec3Fix::UNIT_Z,
2975 );
2976 j.angular_compliance = ac;
2977 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
2978 b[1].rotation = QuatFix::from_axis_angle(Vec3Fix::UNIT_X, Fix128::from_ratio(1, 2));
2979 solve_hinge_joint(&j, &mut b, DT);
2980 let a = b[0].rotation.rotate_vec(Vec3Fix::UNIT_Z);
2981 let c = b[1].rotation.rotate_vec(Vec3Fix::UNIT_Z);
2982 a.cross(c).length()
2983 };
2984 assert!(
2985 mk(Fix128::from_ratio(1, 4)) > mk(Fix128::ZERO),
2986 "angular compliance で補正が弱まる"
2987 );
2988 }
2989
2990 #[test]
2991 fn limit_equality_is_inside_the_allowed_range() {
2992 let sj = SliderJoint::new(0, 1, Vec3Fix::UNIT_X, Vec3Fix::ZERO, Vec3Fix::ZERO)
2994 .with_limits(fi(-1), fi(2));
2995 let mut at_min = pair(Vec3Fix::ZERO, 0, v3i(-1, 0, 0), 1);
2996 solve_slider_joint(&sj, &mut at_min, DT);
2997 assert_eq!(at_min[1].position, v3i(-1, 0, 0));
2998 let mut dj = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
2999 dj.linear_y = D6Motion::Limited; let mut at_max = pair(Vec3Fix::ZERO, 0, v3i(0, 1, 0), 1);
3001 solve_d6_joint(&dj, &mut at_max, DT);
3002 assert_eq!(at_max[1].position, v3i(0, 1, 0));
3003 let mut at_lo = pair(Vec3Fix::ZERO, 0, v3i(0, -1, 0), 1);
3004 solve_d6_joint(&dj, &mut at_lo, DT);
3005 assert_eq!(at_lo[1].position, v3i(0, -1, 0));
3006 let mut below = pair(Vec3Fix::ZERO, 0, v3i(0, -3, 0), 1);
3008 solve_d6_joint(&dj, &mut below, DT);
3009 assert_eq!(below[1].position, v3i(0, -1, 0));
3010 }
3011
3012 #[test]
3013 fn compute_force_honours_anchors_and_rotation_for_every_kind() {
3014 let bodies = anchored_pair(); assert_eq!(
3016 Joint::Ball(BallJoint::new(0, 1, A_ANCHOR, B_ANCHOR)).compute_force(&bodies),
3017 fi(4)
3018 );
3019 assert_eq!(
3020 Joint::Hinge(HingeJoint::new(
3021 0,
3022 1,
3023 A_ANCHOR,
3024 B_ANCHOR,
3025 Vec3Fix::UNIT_Z,
3026 Vec3Fix::UNIT_Z
3027 ))
3028 .compute_force(&bodies),
3029 fi(4)
3030 );
3031 assert_eq!(
3032 Joint::Fixed(FixedJoint::new(0, 1, A_ANCHOR, B_ANCHOR, QuatFix::IDENTITY))
3033 .compute_force(&bodies),
3034 fi(4)
3035 );
3036 assert_eq!(
3037 Joint::D6(D6Joint::new(0, 1, A_ANCHOR, B_ANCHOR)).compute_force(&bodies),
3038 fi(4)
3039 );
3040 assert_eq!(
3041 Joint::ConeTwist(ConeTwistJoint::new(
3042 0,
3043 1,
3044 A_ANCHOR,
3045 B_ANCHOR,
3046 Vec3Fix::UNIT_Z,
3047 Vec3Fix::UNIT_Z
3048 ))
3049 .compute_force(&bodies),
3050 fi(4)
3051 );
3052 assert_eq!(
3054 Joint::Spring(SpringJoint::new(
3055 0,
3056 1,
3057 A_ANCHOR,
3058 B_ANCHOR,
3059 fi(1),
3060 fi(3),
3061 Fix128::ZERO
3062 ))
3063 .compute_force(&bodies),
3064 fi(9)
3065 );
3066 assert_eq!(
3068 Joint::Slider(SliderJoint::new(0, 1, Vec3Fix::UNIT_Y, A_ANCHOR, B_ANCHOR))
3069 .compute_force(&bodies),
3070 fi(4)
3071 );
3072 assert_eq!(
3073 Joint::Slider(SliderJoint::new(0, 1, Vec3Fix::UNIT_X, A_ANCHOR, B_ANCHOR))
3074 .compute_force(&bodies),
3075 Fix128::ZERO
3076 );
3077 assert_ne!(
3079 Joint::Ball(BallJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO)).compute_force(&bodies),
3080 fi(4)
3081 );
3082 }
3083
3084 fn set_inv_inertia(b: &mut [RigidBody], ia: Fix128, ib: Fix128) {
3099 b[0].inv_inertia = Vec3Fix::new(ia, ia, ia);
3100 b[1].inv_inertia = Vec3Fix::new(ib, ib, ib);
3101 }
3102
3103 fn q35(axis: Vec3Fix) -> QuatFix {
3105 let s = Fix128::from_ratio(3, 5);
3106 QuatFix::new(axis.x * s, axis.y * s, axis.z * s, Fix128::from_ratio(4, 5))
3107 }
3108
3109 fn rel_twist(b: &[RigidBody], axis: Vec3Fix) -> Fix128 {
3111 compute_twist_angle(b[1].rotation.mul(b[0].rotation.conjugate()), axis)
3112 }
3113
3114 fn tilt_z(b: &[RigidBody]) -> Fix128 {
3116 let a = b[0].rotation.rotate_vec(Vec3Fix::UNIT_Z);
3117 let c = b[1].rotation.rotate_vec(Vec3Fix::UNIT_Z);
3118 Fix128::atan2(a.cross(c).length(), a.dot(c))
3119 }
3120
3121 fn tilt_from_world_z(q: QuatFix) -> Fix128 {
3123 let c = q.rotate_vec(Vec3Fix::UNIT_Z);
3124 Fix128::atan2(Vec3Fix::UNIT_Z.cross(c).length(), Vec3Fix::UNIT_Z.dot(c))
3125 }
3126
3127 fn theta35() -> Fix128 {
3129 Fix128::atan2(fi(3), fi(4)).double()
3130 }
3131
3132 #[test]
3140 fn positional_body_a_update_multiplies_by_inverse_mass() {
3141 let expect = v3i(3, 0, 0);
3142 let mut h = pair(Vec3Fix::ZERO, 3, v3i(4, 0, 0), 1);
3143 solve_hinge_joint(
3144 &HingeJoint::new(
3145 0,
3146 1,
3147 Vec3Fix::ZERO,
3148 Vec3Fix::ZERO,
3149 Vec3Fix::UNIT_Z,
3150 Vec3Fix::UNIT_Z,
3151 ),
3152 &mut h,
3153 DT,
3154 );
3155 assert_eq!((h[0].position, h[1].position), (expect, expect), "hinge");
3156 let mut f = pair(Vec3Fix::ZERO, 3, v3i(4, 0, 0), 1);
3157 solve_fixed_joint(
3158 &FixedJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, QuatFix::IDENTITY),
3159 &mut f,
3160 DT,
3161 );
3162 assert_eq!((f[0].position, f[1].position), (expect, expect), "fixed");
3163 let mut c = pair(Vec3Fix::ZERO, 3, v3i(4, 0, 0), 1);
3164 solve_cone_twist_joint(
3165 &ConeTwistJoint::new(
3166 0,
3167 1,
3168 Vec3Fix::ZERO,
3169 Vec3Fix::ZERO,
3170 Vec3Fix::UNIT_Z,
3171 Vec3Fix::UNIT_Z,
3172 ),
3173 &mut c,
3174 DT,
3175 );
3176 assert_eq!((c[0].position, c[1].position), (expect, expect), "cone");
3177 let mut d6 = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
3178 d6.linear_x = D6Motion::Locked;
3179 let mut d = pair(Vec3Fix::ZERO, 3, v3i(4, 0, 0), 1);
3180 solve_d6_joint(&d6, &mut d, DT);
3181 assert_eq!((d[0].position, d[1].position), (expect, expect), "d6");
3182 let mut s = pair(Vec3Fix::ZERO, 3, v3i(0, 4, 0), 1);
3184 solve_slider_joint(
3185 &SliderJoint::new(0, 1, Vec3Fix::UNIT_X, Vec3Fix::ZERO, Vec3Fix::ZERO),
3186 &mut s,
3187 DT,
3188 );
3189 assert_eq!(
3190 (s[0].position, s[1].position),
3191 (v3i(0, 3, 0), v3i(0, 3, 0)),
3192 "slider"
3193 );
3194 let mut sp = pair(Vec3Fix::ZERO, 3, v3i(4, 0, 0), 1);
3195 solve_spring_joint(
3196 &SpringJoint::new(
3197 0,
3198 1,
3199 Vec3Fix::ZERO,
3200 Vec3Fix::ZERO,
3201 fi(1),
3202 fi(2),
3203 Fix128::ZERO,
3204 ),
3205 &mut sp,
3206 DT,
3207 );
3208 assert_eq!(
3209 sp[0].position,
3210 Vec3Fix::new(Fix128::from_ratio(3, 4), Fix128::ZERO, Fix128::ZERO),
3211 "spring A"
3212 );
3213 assert_eq!(
3214 sp[1].position,
3215 Vec3Fix::new(Fix128::from_ratio(15, 4), Fix128::ZERO, Fix128::ZERO),
3216 "spring B"
3217 );
3218 }
3219
3220 #[test]
3232 fn hinge_axis_alignment_uses_angular_compliance_and_inertia_sum() {
3233 let mut j = HingeJoint::new(
3234 0,
3235 1,
3236 Vec3Fix::ZERO,
3237 Vec3Fix::ZERO,
3238 Vec3Fix::UNIT_Z,
3239 Vec3Fix::UNIT_Z,
3240 );
3241 j.angular_compliance = Fix128::from_ratio(1, 64);
3242 let mut b = pair(Vec3Fix::ZERO, 1, Vec3Fix::ZERO, 1);
3243 set_inv_inertia(&mut b, Fix128::from_ratio(1, 2), Fix128::from_ratio(1, 4));
3244 b[1].rotation = q35(Vec3Fix::UNIT_X);
3245 let theta = theta35();
3246 assert!(near(tilt_z(&b), theta));
3247 solve_hinge_joint(&j, &mut b, DT);
3248 let a_tilt = tilt_from_world_z(b[0].rotation);
3249 let b_tilt = tilt_from_world_z(b[1].rotation);
3250 let rel = tilt_z(&b);
3251 assert!(near(a_tilt, theta.half()), "A tilt {a_tilt:?} vs θ/2");
3252 assert!(
3253 near(b_tilt, theta - theta / fi(4)),
3254 "B tilt {b_tilt:?} vs 3θ/4"
3255 );
3256 assert!(near(rel, theta / fi(4)), "residual {rel:?} vs θ/4");
3257 assert!(b[0].rotation.rotate_vec(Vec3Fix::UNIT_Z).y < Fix128::ZERO);
3259 }
3260
3261 #[test]
3271 fn fixed_joint_rotation_lock_uses_angular_compliance_and_inertia_sum() {
3272 let mut j = FixedJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, QuatFix::IDENTITY);
3273 j.angular_compliance = Fix128::from_ratio(1, 64);
3274 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3275 set_inv_inertia(&mut b, Fix128::ZERO, Fix128::from_ratio(3, 4));
3276 b[1].rotation = q35(Vec3Fix::UNIT_Z);
3277 let theta = theta35();
3278 assert!(near(rel_twist(&b, Vec3Fix::UNIT_Z), theta));
3279 solve_fixed_joint(&j, &mut b, DT);
3280 let after = rel_twist(&b, Vec3Fix::UNIT_Z);
3281 assert!(near(after, theta / fi(4)), "{after:?} vs θ/4");
3282 assert_eq!(b[0].rotation, QuatFix::IDENTITY, "static A never rotates");
3283 let rigid = FixedJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, QuatFix::IDENTITY);
3285 let mut r = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3286 set_inv_inertia(&mut r, Fix128::ZERO, Fix128::ONE);
3287 r[1].rotation = q35(Vec3Fix::UNIT_Z);
3288 solve_fixed_joint(&rigid, &mut r, DT);
3289 assert!(near(rel_twist(&r, Vec3Fix::UNIT_Z), Fix128::ZERO));
3290 assert!(near(r[1].rotation.w.abs(), Fix128::ONE));
3291 }
3292
3293 #[test]
3305 fn hinge_min_limit_lands_exactly_on_the_limit() {
3306 let j = HingeJoint::new(
3307 0,
3308 1,
3309 Vec3Fix::ZERO,
3310 Vec3Fix::ZERO,
3311 Vec3Fix::UNIT_Z,
3312 Vec3Fix::UNIT_Z,
3313 )
3314 .with_limits(fi(2), fi(3));
3315 let theta = theta35();
3316 let e = fi(2) - theta;
3317 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3318 set_inv_inertia(&mut b, Fix128::ZERO, fi(2));
3319 b[1].rotation = q35(Vec3Fix::UNIT_Z);
3320 assert!(near(rel_twist(&b, Vec3Fix::UNIT_Z), theta));
3321 solve_hinge_joint(&j, &mut b, DT);
3322 let after = rel_twist(&b, Vec3Fix::UNIT_Z);
3323 assert!(near(after, fi(2)), "{after:?} vs 2");
3324 assert_eq!(b[0].rotation, QuatFix::IDENTITY);
3325
3326 let mut both = pair(Vec3Fix::ZERO, 1, Vec3Fix::ZERO, 1);
3327 set_inv_inertia(&mut both, fi(2), fi(6));
3328 both[1].rotation = q35(Vec3Fix::UNIT_Z);
3329 solve_hinge_joint(&j, &mut both, DT);
3330 let rel = rel_twist(&both, Vec3Fix::UNIT_Z);
3331 let a_angle = compute_twist_angle(both[0].rotation, Vec3Fix::UNIT_Z);
3332 let b_angle = compute_twist_angle(both[1].rotation, Vec3Fix::UNIT_Z);
3333 assert!(near(rel, fi(2)), "rel {rel:?} vs 2");
3334 assert!(near(a_angle, -(e / fi(4))), "A {a_angle:?} vs −e/4");
3335 assert!(
3336 near(b_angle, theta + (e * fi(3)) / fi(4)),
3337 "B {b_angle:?} vs θ + 3e/4"
3338 );
3339 }
3340
3341 #[test]
3348 fn hinge_max_limit_lands_exactly_on_the_limit() {
3349 let j = HingeJoint::new(
3350 0,
3351 1,
3352 Vec3Fix::ZERO,
3353 Vec3Fix::ZERO,
3354 Vec3Fix::UNIT_Z,
3355 Vec3Fix::UNIT_Z,
3356 )
3357 .with_limits(Fix128::from_ratio(1, 4), Fix128::from_ratio(1, 2));
3358 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3359 set_inv_inertia(&mut b, Fix128::ZERO, fi(2));
3360 b[1].rotation = q35(Vec3Fix::UNIT_Z);
3361 solve_hinge_joint(&j, &mut b, DT);
3362 let after = rel_twist(&b, Vec3Fix::UNIT_Z);
3363 assert!(near(after, Fix128::from_ratio(1, 2)), "{after:?} vs 1/2");
3364 }
3365
3366 #[test]
3373 fn hinge_min_limit_with_negative_angle_pushes_toward_min() {
3374 let theta = theta35();
3375 let q = q35(Vec3Fix::UNIT_Z);
3376 let neg_q = QuatFix::new(-q.x, -q.y, -q.z, -q.w);
3377 assert!(near(compute_twist_angle(q, Vec3Fix::UNIT_Z), theta));
3378 assert!(near(compute_twist_angle(neg_q, Vec3Fix::UNIT_Z), theta));
3379 assert!(near(
3380 compute_twist_angle(q.conjugate(), Vec3Fix::UNIT_Z),
3381 -theta
3382 ));
3383
3384 let j = HingeJoint::new(
3385 0,
3386 1,
3387 Vec3Fix::ZERO,
3388 Vec3Fix::ZERO,
3389 Vec3Fix::UNIT_Z,
3390 Vec3Fix::UNIT_Z,
3391 )
3392 .with_limits(Fix128::from_ratio(-1, 2), Fix128::from_ratio(1, 2));
3393 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3394 set_inv_inertia(&mut b, Fix128::ZERO, Fix128::ONE);
3395 b[1].rotation = q.conjugate();
3396 assert!(near(rel_twist(&b, Vec3Fix::UNIT_Z), -theta));
3397 solve_hinge_joint(&j, &mut b, DT);
3398 let after = rel_twist(&b, Vec3Fix::UNIT_Z);
3399 assert!(near(after, Fix128::from_ratio(-1, 2)), "{after:?} vs −1/2");
3400 let mut p = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3402 set_inv_inertia(&mut p, Fix128::ZERO, Fix128::ONE);
3403 p[1].rotation = q;
3404 solve_hinge_joint(&j, &mut p, DT);
3405 assert!(near(
3406 rel_twist(&p, Vec3Fix::UNIT_Z),
3407 Fix128::from_ratio(1, 2)
3408 ));
3409 }
3410
3411 #[test]
3415 fn hinge_limit_equality_is_a_bitwise_no_op() {
3416 let mk = |min: Fix128, max: Fix128| {
3417 HingeJoint::new(
3418 0,
3419 1,
3420 Vec3Fix::ZERO,
3421 Vec3Fix::ZERO,
3422 Vec3Fix::UNIT_Z,
3423 Vec3Fix::UNIT_Z,
3424 )
3425 .with_limits(min, max)
3426 };
3427 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3428 set_inv_inertia(&mut b, fi(3), fi(1));
3429 b[1].rotation = q35(Vec3Fix::UNIT_Z);
3430 let before = b[1].rotation;
3431 let theta = rel_twist(&b, Vec3Fix::UNIT_Z);
3432 solve_hinge_joint(&mk(theta, theta + fi(1)), &mut b, DT);
3434 assert_eq!(b[1].rotation, before, "angle == min must not touch B");
3435 solve_hinge_joint(&mk(theta - fi(1), theta), &mut b, DT);
3437 assert_eq!(b[1].rotation, before, "angle == max must not touch B");
3438 }
3439
3440 #[test]
3446 fn slider_adds_anchor_a_and_uses_b_minus_a() {
3447 let j = SliderJoint::new(0, 1, Vec3Fix::UNIT_X, v3i(0, 1, 0), Vec3Fix::ZERO);
3448 let mut b = pair(Vec3Fix::ZERO, 0, v3i(3, 5, 0), 1);
3449 solve_slider_joint(&j, &mut b, DT);
3450 assert_eq!(b[1].position, v3i(3, 1, 0));
3451 }
3452
3453 #[test]
3464 fn slider_limit_corrections_split_by_inverse_mass() {
3465 let j = SliderJoint::new(0, 1, Vec3Fix::UNIT_X, Vec3Fix::ZERO, Vec3Fix::ZERO)
3466 .with_limits(fi(-1), fi(2));
3467 let half = Fix128::from_ratio(1, 2);
3468 let mut under = pair(Vec3Fix::ZERO, 3, v3i(-5, 0, 0), 5);
3469 solve_slider_joint(&j, &mut under, DT);
3470 assert_eq!(
3471 under[0].position,
3472 Vec3Fix::new(-(fi(3) * half), Fix128::ZERO, Fix128::ZERO)
3473 );
3474 assert_eq!(
3475 under[1].position,
3476 Vec3Fix::new(-(fi(5) * half), Fix128::ZERO, Fix128::ZERO)
3477 );
3478 assert_eq!(under[1].position.x - under[0].position.x, fi(-1));
3479 let mut over = pair(Vec3Fix::ZERO, 3, v3i(6, 0, 0), 5);
3480 solve_slider_joint(&j, &mut over, DT);
3481 assert_eq!(
3482 over[0].position,
3483 Vec3Fix::new(fi(3) * half, Fix128::ZERO, Fix128::ZERO)
3484 );
3485 assert_eq!(
3486 over[1].position,
3487 Vec3Fix::new(fi(7) * half, Fix128::ZERO, Fix128::ZERO)
3488 );
3489 assert_eq!(over[1].position.x - over[0].position.x, fi(2));
3490 }
3491
3492 #[test]
3500 fn spring_damping_uses_relative_velocity_b_minus_a() {
3501 let j = SpringJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, fi(4), fi(2), fi(2));
3502 let mut b = pair(Vec3Fix::ZERO, 1, v3i(4, 0, 0), 1);
3503 b[0].velocity = v3i(1, 0, 0);
3504 b[1].velocity = v3i(3, 0, 0);
3505 solve_spring_joint(&j, &mut b, DT);
3506 assert!(
3507 near_v(
3508 b[0].position,
3509 Vec3Fix::new(Fix128::from_ratio(1, 9), Fix128::ZERO, Fix128::ZERO)
3510 ),
3511 "A {:?}",
3512 b[0].position
3513 );
3514 assert!(
3515 near_v(
3516 b[1].position,
3517 Vec3Fix::new(Fix128::from_ratio(35, 9), Fix128::ZERO, Fix128::ZERO)
3518 ),
3519 "B {:?}",
3520 b[1].position
3521 );
3522 }
3523
3524 #[test]
3532 fn d6_locked_angular_axis_uses_angular_compliance_and_inertia_sum() {
3533 let mut j = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
3534 j.angular_z = D6Motion::Locked;
3535 j.angular_compliance = Fix128::from_ratio(3, 64);
3536 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3537 set_inv_inertia(&mut b, Fix128::ZERO, Fix128::from_ratio(3, 4));
3538 b[1].rotation = q35(Vec3Fix::UNIT_Z);
3539 let theta = theta35();
3540 solve_d6_joint(&j, &mut b, DT);
3541 let after = rel_twist(&b, Vec3Fix::UNIT_Z);
3542 assert!(near(after, theta.half()), "{after:?} vs θ/2");
3543 }
3544
3545 #[test]
3555 fn d6_limited_angular_axis_lands_exactly_on_the_limit() {
3556 let mk = |min: Fix128, max: Fix128| {
3557 let mut j = D6Joint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO);
3558 j.angular_z = D6Motion::Limited;
3559 j.angular_limit_min.z = min;
3560 j.angular_limit_max.z = max;
3561 j
3562 };
3563 let half = Fix128::from_ratio(1, 2);
3564 let run = |rot: QuatFix, min: Fix128, max: Fix128| {
3565 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3566 set_inv_inertia(&mut b, Fix128::ZERO, Fix128::ONE);
3567 b[1].rotation = rot;
3568 solve_d6_joint(&mk(min, max), &mut b, DT);
3569 rel_twist(&b, Vec3Fix::UNIT_Z)
3570 };
3571 let q = q35(Vec3Fix::UNIT_Z);
3572 let under = run(q, fi(2), fi(3));
3573 assert!(near(under, fi(2)), "min: {under:?} vs 2");
3574 let over = run(q, Fix128::from_ratio(1, 4), half);
3575 assert!(near(over, half), "max: {over:?} vs 1/2");
3576 let negative = run(q.conjugate(), -half, half);
3577 assert!(near(negative, -half), "negative: {negative:?} vs −1/2");
3578 }
3579
3580 #[test]
3591 fn cone_limit_correction_uses_angular_compliance_and_inertia_sum() {
3592 let half = Fix128::from_ratio(1, 2);
3593 let mut j = ConeTwistJoint::new(
3594 0,
3595 1,
3596 Vec3Fix::ZERO,
3597 Vec3Fix::ZERO,
3598 Vec3Fix::UNIT_Z,
3599 Vec3Fix::UNIT_Z,
3600 )
3601 .with_limits(half, half);
3602 j.angular_compliance = Fix128::from_ratio(1, 64);
3603 let mut b = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3604 set_inv_inertia(&mut b, Fix128::ZERO, half);
3605 b[1].rotation = q35(Vec3Fix::UNIT_X);
3606 let theta = theta35();
3607 assert!(near(tilt_z(&b), theta));
3608 let expected = (theta + Fix128::ONE) / fi(3);
3609 solve_cone_twist_joint(&j, &mut b, DT);
3610 let after = tilt_z(&b);
3611 assert!(near(after, expected), "{after:?} vs {expected:?}");
3612 assert!(near(rel_twist(&b, Vec3Fix::UNIT_Z), Fix128::ZERO));
3614 let rigid = ConeTwistJoint::new(
3616 0,
3617 1,
3618 Vec3Fix::ZERO,
3619 Vec3Fix::ZERO,
3620 Vec3Fix::UNIT_Z,
3621 Vec3Fix::UNIT_Z,
3622 )
3623 .with_limits(half, half);
3624 let mut r = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3625 set_inv_inertia(&mut r, Fix128::ZERO, Fix128::ONE);
3626 r[1].rotation = q35(Vec3Fix::UNIT_X);
3627 solve_cone_twist_joint(&rigid, &mut r, DT);
3628 assert!(near(tilt_z(&r), half));
3629 }
3630
3631 #[test]
3642 fn twist_limit_correction_error_arithmetic() {
3643 let half = Fix128::from_ratio(1, 2);
3644 let mut j = ConeTwistJoint::new(
3645 0,
3646 1,
3647 Vec3Fix::ZERO,
3648 Vec3Fix::ZERO,
3649 Vec3Fix::UNIT_Z,
3650 Vec3Fix::UNIT_Z,
3651 )
3652 .with_limits(half, half);
3653 j.angular_compliance = Fix128::from_ratio(1, 64);
3654 let theta = theta35();
3655 let expected = (theta + Fix128::ONE) / fi(3);
3656 let q = q35(Vec3Fix::UNIT_Z);
3657
3658 let mut pos = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3659 set_inv_inertia(&mut pos, Fix128::ZERO, half);
3660 pos[1].rotation = q;
3661 solve_cone_twist_joint(&j, &mut pos, DT);
3662 let after = rel_twist(&pos, Vec3Fix::UNIT_Z);
3663 assert!(near(after, expected), "positive: {after:?} vs {expected:?}");
3664 assert!(near(tilt_z(&pos), Fix128::ZERO));
3665
3666 let mut neg = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3667 set_inv_inertia(&mut neg, Fix128::ZERO, half);
3668 neg[1].rotation = q.conjugate();
3669 assert!(near(rel_twist(&neg, Vec3Fix::UNIT_Z), -theta));
3670 solve_cone_twist_joint(&j, &mut neg, DT);
3671 let after = rel_twist(&neg, Vec3Fix::UNIT_Z);
3672 assert!(
3673 near(after, -expected),
3674 "negative: {after:?} vs {:?}",
3675 -expected
3676 );
3677
3678 let rigid = ConeTwistJoint::new(
3680 0,
3681 1,
3682 Vec3Fix::ZERO,
3683 Vec3Fix::ZERO,
3684 Vec3Fix::UNIT_Z,
3685 Vec3Fix::UNIT_Z,
3686 )
3687 .with_limits(half, half);
3688 for (rot, limit) in [(q, half), (q.conjugate(), -half)] {
3689 let mut r = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3690 set_inv_inertia(&mut r, Fix128::ZERO, Fix128::ONE);
3691 r[1].rotation = rot;
3692 solve_cone_twist_joint(&rigid, &mut r, DT);
3693 let t = rel_twist(&r, Vec3Fix::UNIT_Z);
3694 assert!(near(t, limit), "{t:?} vs {limit:?}");
3695 }
3696 }
3697
3698 #[test]
3702 fn cone_and_twist_limit_equality_is_a_bitwise_no_op() {
3703 let mk = |cone: Fix128, twist: Fix128| {
3704 ConeTwistJoint::new(
3705 0,
3706 1,
3707 Vec3Fix::ZERO,
3708 Vec3Fix::ZERO,
3709 Vec3Fix::UNIT_Z,
3710 Vec3Fix::UNIT_Z,
3711 )
3712 .with_limits(cone, twist)
3713 };
3714 let mut c = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3716 c[1].rotation = q35(Vec3Fix::UNIT_X);
3717 let a = c[0].rotation.rotate_vec(Vec3Fix::UNIT_Z).normalize();
3718 let bz = c[1].rotation.rotate_vec(Vec3Fix::UNIT_Z).normalize();
3719 let (_, cross_len) = a.cross(bz).normalize_with_length();
3720 let cone = Fix128::atan2(cross_len, a.dot(bz));
3721 let before = c[1].rotation;
3722 solve_cone_twist_joint(&mk(cone, Fix128::PI), &mut c, DT);
3723 assert_eq!(c[1].rotation, before, "cone == limit must not touch B");
3724 let mut t = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3726 t[1].rotation = q35(Vec3Fix::UNIT_Z);
3727 let twist = rel_twist(&t, Vec3Fix::UNIT_Z);
3728 let before = t[1].rotation;
3729 solve_cone_twist_joint(&mk(Fix128::HALF_PI, twist), &mut t, DT);
3730 assert_eq!(t[1].rotation, before, "twist == limit must not touch B");
3731 }
3732
3733 #[test]
3750 fn angular_inverse_mass_is_anisotropic_and_split_by_body() {
3751 let local_axis = Vec3Fix::new(
3752 Fix128::ZERO,
3753 Fix128::from_ratio(24, 25),
3754 Fix128::from_ratio(7, 25),
3755 );
3756 let j = HingeJoint::new(0, 1, Vec3Fix::ZERO, Vec3Fix::ZERO, local_axis, local_axis)
3757 .with_limits(fi(2), fi(3));
3758 let theta = theta35();
3759 let e = fi(2) - theta;
3760 let qa = q35(Vec3Fix::UNIT_X);
3761 let mut b = pair(Vec3Fix::ZERO, 1, Vec3Fix::ZERO, 1);
3762 b[0].inv_inertia = v3i(5, 2, 7);
3763 b[1].inv_inertia = v3i(11, 13, 3);
3764 b[0].rotation = qa;
3765 b[1].rotation = q35(Vec3Fix::UNIT_Z).mul(qa).normalize();
3766 assert!(near_v(qa.rotate_vec(local_axis), Vec3Fix::UNIT_Z));
3767 assert!(near_v(
3768 b[1].rotation.rotate_vec(local_axis),
3769 Vec3Fix::UNIT_Z
3770 ));
3771 assert!(near(rel_twist(&b, Vec3Fix::UNIT_Z), theta));
3772 let w_a = Fix128::from_ratio(1152 + 343, 625);
3773 let w_b = Fix128::from_ratio(7488 + 147, 625);
3774 assert!(near(angular_inverse_mass(&b[0], Vec3Fix::UNIT_Z), w_a));
3775 assert!(near(angular_inverse_mass(&b[1], Vec3Fix::UNIT_Z), w_b));
3776 let a_before = b[0].rotation;
3777 solve_hinge_joint(&j, &mut b, DT);
3778 let rel = rel_twist(&b, Vec3Fix::UNIT_Z);
3779 assert!(near(rel, fi(2)), "rel {rel:?} vs 2");
3780 let a_delta = b[0].rotation.mul(a_before.conjugate());
3781 let a_angle = compute_twist_angle(a_delta, Vec3Fix::UNIT_Z);
3782 let expected_a = -(w_a * e) / (w_a + w_b);
3783 assert!(near(a_angle, expected_a), "A {a_angle:?} vs {expected_a:?}");
3784 assert!(near_v(
3786 b[0].rotation.rotate_vec(local_axis),
3787 Vec3Fix::UNIT_Z
3788 ));
3789 let mut s = pair(Vec3Fix::ZERO, 0, Vec3Fix::ZERO, 1);
3791 s[0].inv_inertia = v3i(5, 2, 7);
3792 assert_eq!(angular_inverse_mass(&s[0], Vec3Fix::UNIT_Z), Fix128::ZERO);
3793 }
3794
3795 #[test]
3802 fn angular_inverse_mass_of_an_isotropic_body_does_not_depend_on_orientation() {
3803 let c = Fix128::from_ratio(5, 2);
3804 let n = Vec3Fix::new(
3805 Fix128::from_ratio(3, 7),
3806 Fix128::from_ratio(-11, 13),
3807 Fix128::from_ratio(1, 3),
3808 );
3809 let expected = n.x * n.x * c + n.y * n.y * c + n.z * n.z * c;
3810 let mut differ = 0;
3811 for i in 0..16_i64 {
3812 let axis = Vec3Fix::new(
3813 Fix128::from_ratio(1 + i, 3),
3814 Fix128::from_ratio(2 - i, 5),
3815 Fix128::from_ratio(3 + 2 * i, 7),
3816 )
3817 .normalize();
3818 for k in 1..=4_i64 {
3819 let q =
3820 QuatFix::from_axis_angle(axis, Fix128::from_ratio(7 * k + i, 9)).normalize();
3821 let mut b = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
3822 b.inv_inertia = Vec3Fix::new(c, c, c);
3823 b.rotation = q;
3824 differ += usize::from(angular_inverse_mass(&b, n) != expected);
3825 }
3826 }
3827 assert_eq!(differ, 0, "{differ}/64 orientations differ from c·(n·n)");
3828 }
3829}