1use crate::bvh::{BroadphaseHybrid, BvhPrimitive, LinearBvh};
21use crate::collider::{Contact, AABB};
22use crate::event::EventCollector;
23use crate::filter::CollisionFilter;
24use crate::force::{apply_force_fields, ForceFieldInstance};
25use crate::joint::{solve_joints, Joint};
26use crate::math::{select_vec3, Fix128, QuatFix, Vec3Fix};
27use crate::sdf_collider::SdfCollider;
28use crate::sleeping::{IslandManager, SleepConfig, SleepData, SleepState};
29
30const W_SUM_EPSILON: Fix128 = Fix128 {
38 hi: 0,
39 lo: 0x0000000001000000,
40};
41
42#[cfg(not(feature = "std"))]
43use alloc::vec;
44#[cfg(not(feature = "std"))]
45use alloc::vec::Vec;
46
47#[cfg(feature = "parallel")]
49use rayon::prelude::*;
50
51mod participants;
52mod world_ccd;
53mod world_snapshot;
54pub use participants::DeclareFieldError;
55pub use world_ccd::WorldCcdConfig;
56pub use world_snapshot::{LawCheck, WorldSnapshotError};
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64#[repr(u8)]
65pub enum BodyType {
66 Dynamic = 0,
68 Static = 1,
70 Kinematic = 2,
72}
73
74const DEFAULT_BODY_FRICTION: Fix128 = Fix128::from_raw(0, 9_223_372_036_854_775_808);
82
83const DEFAULT_BODY_RESTITUTION: Fix128 = Fix128::from_raw(0, 5_534_023_222_112_865_484);
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96#[repr(C, align(64))]
97pub struct RigidBody {
98 pub position: Vec3Fix,
101 pub velocity: Vec3Fix,
103 pub inv_mass: Fix128,
105 pub inv_inertia: Vec3Fix,
107 pub prev_position: Vec3Fix,
109
110 pub rotation: QuatFix,
120 pub angular_velocity: Vec3Fix,
122 pub prev_rotation: QuatFix,
124 pub restitution: Fix128,
129 pub friction: Fix128,
134 pub gravity_scale: Fix128,
136 pub linear_damping: Fix128,
139 pub angular_damping: Fix128,
142 pub is_sensor: bool,
144 pub body_type: BodyType,
146 pub kinematic_target: Option<(Vec3Fix, QuatFix)>,
148}
149
150fn checked_fix_sub(a: Fix128, b: Fix128) -> Option<Fix128> {
153 let s = fix_wide(a).checked_sub(fix_wide(b))?;
154 Some(fix_narrow(s))
155}
156
157fn fix_wide(f: Fix128) -> i128 {
159 (i128::from(f.hi) << 64) | i128::from(f.lo)
160}
161
162fn fix_narrow(s: i128) -> Fix128 {
164 Fix128::from_raw((s >> 64) as i64, s as u64)
165}
166
167fn checked_quat_mul(a: QuatFix, b: QuatFix) -> Option<QuatFix> {
170 let m = |x: Fix128, y: Fix128| x.checked_mul(y);
171 let x = checked_fix_sub(
172 Fix128::checked_add(
173 Fix128::checked_add(m(a.w, b.x)?, m(a.x, b.w)?)?,
174 m(a.y, b.z)?,
175 )?,
176 m(a.z, b.y)?,
177 )?;
178 let y = Fix128::checked_add(
179 Fix128::checked_add(checked_fix_sub(m(a.w, b.y)?, m(a.x, b.z)?)?, m(a.y, b.w)?)?,
180 m(a.z, b.x)?,
181 )?;
182 let z = Fix128::checked_add(
183 checked_fix_sub(
184 Fix128::checked_add(m(a.w, b.z)?, m(a.x, b.y)?)?,
185 m(a.y, b.x)?,
186 )?,
187 m(a.z, b.w)?,
188 )?;
189 let w = checked_fix_sub(
190 checked_fix_sub(checked_fix_sub(m(a.w, b.w)?, m(a.x, b.x)?)?, m(a.y, b.y)?)?,
191 m(a.z, b.z)?,
192 )?;
193 Some(QuatFix::new(x, y, z, w))
194}
195
196#[inline]
201#[must_use]
202pub(crate) fn inv_inertia_is_isotropic(inv_inertia: Vec3Fix) -> bool {
203 inv_inertia.x == inv_inertia.y && inv_inertia.y == inv_inertia.z
204}
205
206#[inline]
217#[must_use]
218pub(crate) fn inv_inertia_world_apply(
219 rotation: QuatFix,
220 inv_inertia: Vec3Fix,
221 v: Vec3Fix,
222) -> Vec3Fix {
223 if inv_inertia_is_isotropic(inv_inertia) {
224 let c = inv_inertia.x;
225 return Vec3Fix::new(v.x * c, v.y * c, v.z * c);
226 }
227 let local = rotation.conjugate().rotate_vec(v);
228 let scaled = Vec3Fix::new(
229 local.x * inv_inertia.x,
230 local.y * inv_inertia.y,
231 local.z * inv_inertia.z,
232 );
233 rotation.rotate_vec(scaled)
234}
235
236fn checked_rotate_vec(q: QuatFix, v: Vec3Fix) -> Option<Vec3Fix> {
238 let qv = QuatFix::new(v.x, v.y, v.z, Fix128::ZERO);
239 let r = checked_quat_mul(checked_quat_mul(q, qv)?, q.conjugate())?;
240 Some(Vec3Fix::new(r.x, r.y, r.z))
241}
242
243impl RigidBody {
244 #[must_use]
263 pub fn new(position: Vec3Fix, mass: Fix128) -> Self {
264 let inv_mass = if mass.is_zero() {
265 Fix128::ZERO
266 } else {
267 Fix128::ONE / mass
268 };
269
270 let inertia = mass * Fix128::from_ratio(2, 5);
272 let inv_inertia = if inertia.is_zero() {
273 Vec3Fix::ZERO
274 } else {
275 let inv_i = Fix128::ONE / inertia;
276 Vec3Fix::new(inv_i, inv_i, inv_i)
277 };
278
279 Self {
280 position,
281 velocity: Vec3Fix::ZERO,
282 inv_mass,
283 inv_inertia,
284 prev_position: position,
285 rotation: QuatFix::IDENTITY,
286 angular_velocity: Vec3Fix::ZERO,
287 prev_rotation: QuatFix::IDENTITY,
288 restitution: DEFAULT_BODY_RESTITUTION,
289 friction: DEFAULT_BODY_FRICTION,
290 gravity_scale: Fix128::ONE,
291 linear_damping: Fix128::ONE,
292 angular_damping: Fix128::ONE,
293 is_sensor: false,
294 body_type: BodyType::Dynamic,
295 kinematic_target: None,
296 }
297 }
298
299 #[inline]
301 #[must_use]
302 pub fn new_dynamic(position: Vec3Fix, mass: Fix128) -> Self {
303 Self::new(position, mass)
304 }
305
306 #[must_use]
308 pub const fn new_static(position: Vec3Fix) -> Self {
309 Self {
310 position,
311 velocity: Vec3Fix::ZERO,
312 inv_mass: Fix128::ZERO,
313 inv_inertia: Vec3Fix::ZERO,
314 prev_position: position,
315 rotation: QuatFix::IDENTITY,
316 angular_velocity: Vec3Fix::ZERO,
317 prev_rotation: QuatFix::IDENTITY,
318 restitution: DEFAULT_BODY_RESTITUTION,
319 friction: DEFAULT_BODY_FRICTION,
320 gravity_scale: Fix128::ZERO,
321 linear_damping: Fix128::ONE,
322 angular_damping: Fix128::ONE,
323 is_sensor: false,
324 body_type: BodyType::Static,
325 kinematic_target: None,
326 }
327 }
328
329 #[must_use]
331 pub const fn new_sensor(position: Vec3Fix) -> Self {
332 Self {
333 position,
334 velocity: Vec3Fix::ZERO,
335 inv_mass: Fix128::ZERO,
336 inv_inertia: Vec3Fix::ZERO,
337 prev_position: position,
338 rotation: QuatFix::IDENTITY,
339 angular_velocity: Vec3Fix::ZERO,
340 prev_rotation: QuatFix::IDENTITY,
341 restitution: Fix128::ZERO,
342 friction: Fix128::ZERO,
343 gravity_scale: Fix128::ZERO,
344 linear_damping: Fix128::ONE,
345 angular_damping: Fix128::ONE,
346 is_sensor: true,
347 body_type: BodyType::Static,
348 kinematic_target: None,
349 }
350 }
351
352 #[must_use]
357 pub const fn new_kinematic(position: Vec3Fix) -> Self {
358 Self {
359 position,
360 velocity: Vec3Fix::ZERO,
361 inv_mass: Fix128::ZERO,
362 inv_inertia: Vec3Fix::ZERO,
363 prev_position: position,
364 rotation: QuatFix::IDENTITY,
365 angular_velocity: Vec3Fix::ZERO,
366 prev_rotation: QuatFix::IDENTITY,
367 restitution: DEFAULT_BODY_RESTITUTION,
368 friction: DEFAULT_BODY_FRICTION,
369 gravity_scale: Fix128::ZERO,
370 linear_damping: Fix128::ONE,
371 angular_damping: Fix128::ONE,
372 is_sensor: false,
373 body_type: BodyType::Kinematic,
374 kinematic_target: None,
375 }
376 }
377
378 #[inline]
380 #[must_use]
381 pub const fn is_static(&self) -> bool {
382 self.inv_mass.is_zero()
383 }
384
385 #[inline]
387 #[must_use]
388 pub fn is_kinematic(&self) -> bool {
389 self.body_type == BodyType::Kinematic
390 }
391
392 #[inline]
394 #[must_use]
395 pub fn is_dynamic(&self) -> bool {
396 self.body_type == BodyType::Dynamic
397 }
398
399 pub fn set_kinematic_target(&mut self, position: Vec3Fix, rotation: QuatFix) {
406 self.kinematic_target = Some((position, rotation.unit_rotation()));
407 }
408
409 pub fn apply_impulse(&mut self, impulse: Vec3Fix) {
411 if !self.is_static() {
412 self.velocity = self.velocity + impulse * self.inv_mass;
413 }
414 }
415
416 #[inline]
426 pub(crate) fn world_inv_inertia_apply(&self, torque: Vec3Fix) -> Vec3Fix {
427 inv_inertia_world_apply(self.rotation, self.inv_inertia, torque)
428 }
429
430 #[must_use]
444 pub(crate) fn checked_world_inv_inertia_apply(&self, torque: Vec3Fix) -> Option<Vec3Fix> {
445 if inv_inertia_is_isotropic(self.inv_inertia) {
446 let c = self.inv_inertia.x;
447 return Some(Vec3Fix::new(
448 torque.x.checked_mul(c)?,
449 torque.y.checked_mul(c)?,
450 torque.z.checked_mul(c)?,
451 ));
452 }
453 let q = self.rotation;
454 let local = checked_rotate_vec(q.conjugate(), torque)?;
455 let scaled = Vec3Fix::new(
456 local.x.checked_mul(self.inv_inertia.x)?,
457 local.y.checked_mul(self.inv_inertia.y)?,
458 local.z.checked_mul(self.inv_inertia.z)?,
459 );
460 checked_rotate_vec(q, scaled)
461 }
462
463 pub fn apply_impulse_at(&mut self, impulse: Vec3Fix, point: Vec3Fix) {
471 if !self.is_static() {
472 self.velocity = self.velocity + impulse * self.inv_mass;
473
474 let r = point - self.position;
475 let torque = r.cross(impulse);
476 self.angular_velocity = self.angular_velocity + self.world_inv_inertia_apply(torque);
477 }
478 }
479
480 #[inline]
485 pub fn add_force(&mut self, force: Vec3Fix, dt: Fix128) {
486 if !self.is_static() {
487 self.velocity = self.velocity + force * self.inv_mass * dt;
488 }
489 }
490
491 #[inline]
498 pub fn add_torque(&mut self, torque: Vec3Fix, dt: Fix128) {
499 if !self.is_static() {
500 self.angular_velocity =
501 self.angular_velocity + self.world_inv_inertia_apply(torque) * dt;
502 }
503 }
504
505 #[inline]
507 pub fn set_velocity(&mut self, velocity: Vec3Fix) {
508 self.velocity = velocity;
509 }
510
511 #[inline]
513 pub fn set_angular_velocity(&mut self, angular_velocity: Vec3Fix) {
514 self.angular_velocity = angular_velocity;
515 }
516
517 #[inline]
519 pub fn set_position(&mut self, position: Vec3Fix) {
520 self.position = position;
521 self.prev_position = position;
522 }
523
524 #[inline]
531 pub fn set_rotation(&mut self, rotation: QuatFix) {
532 let rotation = rotation.unit_rotation();
533 self.rotation = rotation;
534 self.prev_rotation = rotation;
535 }
536
537 #[inline]
542 pub(crate) fn make_rotations_unit(&mut self) {
543 self.rotation = self.rotation.unit_rotation();
544 self.prev_rotation = self.prev_rotation.unit_rotation();
545 if let Some((position, rotation)) = self.kinematic_target {
546 self.kinematic_target = Some((position, rotation.unit_rotation()));
547 }
548 }
549
550 #[inline]
552 #[must_use]
553 pub fn mass(&self) -> Fix128 {
554 if self.inv_mass.is_zero() {
555 Fix128::ZERO } else {
557 Fix128::ONE / self.inv_mass
558 }
559 }
560
561 #[inline]
563 #[must_use]
564 pub fn speed(&self) -> Fix128 {
565 self.velocity.length()
566 }
567
568 #[inline]
570 #[must_use]
571 pub const fn with_linear_damping(mut self, damping: Fix128) -> Self {
572 self.linear_damping = damping;
573 self
574 }
575
576 #[inline]
578 #[must_use]
579 pub const fn with_angular_damping(mut self, damping: Fix128) -> Self {
580 self.angular_damping = damping;
581 self
582 }
583
584 #[inline]
586 #[must_use]
587 pub const fn with_restitution(mut self, restitution: Fix128) -> Self {
588 self.restitution = restitution;
589 self
590 }
591
592 #[inline]
594 #[must_use]
595 pub const fn with_friction(mut self, friction: Fix128) -> Self {
596 self.friction = friction;
597 self
598 }
599
600 #[inline]
602 #[must_use]
603 pub const fn with_gravity_scale(mut self, scale: Fix128) -> Self {
604 self.gravity_scale = scale;
605 self
606 }
607
608 #[inline]
610 #[must_use]
611 pub const fn with_sensor(mut self, is_sensor: bool) -> Self {
612 self.is_sensor = is_sensor;
613 self
614 }
615
616 #[inline]
618 #[must_use]
619 pub const fn with_velocity(mut self, velocity: Vec3Fix) -> Self {
620 self.velocity = velocity;
621 self
622 }
623
624 #[inline]
626 #[must_use]
627 pub const fn with_rotation(mut self, rotation: QuatFix) -> Self {
628 self.rotation = rotation;
629 self.prev_rotation = rotation;
630 self
631 }
632}
633
634impl Default for RigidBody {
635 fn default() -> Self {
637 Self::new(Vec3Fix::ZERO, Fix128::ONE)
638 }
639}
640
641#[derive(Clone, Copy, Debug, PartialEq, Eq)]
647pub struct DistanceConstraint {
648 pub body_a: usize,
650 pub body_b: usize,
652 pub local_anchor_a: Vec3Fix,
654 pub local_anchor_b: Vec3Fix,
656 pub target_distance: Fix128,
658 pub compliance: Fix128,
660 pub cached_lambda: Fix128,
668}
669
670impl DistanceConstraint {
671 #[must_use]
673 pub const fn new(
674 body_a: usize,
675 body_b: usize,
676 anchor_a: Vec3Fix,
677 anchor_b: Vec3Fix,
678 distance: Fix128,
679 ) -> Self {
680 Self {
681 body_a,
682 body_b,
683 local_anchor_a: anchor_a,
684 local_anchor_b: anchor_b,
685 target_distance: distance,
686 compliance: Fix128::ZERO,
687 cached_lambda: Fix128::ZERO,
688 }
689 }
690
691 #[must_use]
693 pub const fn with_compliance(mut self, compliance: Fix128) -> Self {
694 self.compliance = compliance;
695 self
696 }
697}
698
699#[derive(Clone, Copy, Debug, PartialEq, Eq)]
701pub struct ContactConstraint {
702 pub body_a: usize,
704 pub body_b: usize,
706 pub contact: Contact,
708 pub friction: Fix128,
710 pub restitution: Fix128,
712 pub cached_lambda: Fix128,
716}
717
718impl ContactConstraint {
719 #[must_use]
721 pub fn new(body_a: usize, body_b: usize, contact: Contact) -> Self {
722 Self {
723 body_a,
724 body_b,
725 contact,
726 friction: Fix128::from_ratio(3, 10), restitution: Fix128::from_ratio(2, 10), cached_lambda: Fix128::ZERO,
729 }
730 }
731}
732
733#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
776#[non_exhaustive]
777pub enum SolverBackend {
778 #[default]
781 Xpbd,
782 Tgs,
785}
786
787#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
797#[non_exhaustive]
798pub enum Broadphase {
799 #[default]
801 Bvh,
802 DynamicTree,
806 Hybrid,
814}
815
816#[derive(Clone, Copy, Debug, PartialEq, Eq)]
819pub struct BroadphaseStats {
820 pub proxies: usize,
822 pub height: i32,
824}
825
826#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
843#[non_exhaustive]
844pub struct StageWork {
845 pub sleep_scanned: u64,
847 pub parked: u64,
849 pub unparked: u64,
851 pub force_field_bodies: u64,
853 pub integrated: u64,
855 pub broadphase_primitives: u64,
857 pub broadphase_pairs: u64,
859 pub broadphase_box_tests: u64,
862 pub resolution_bodies: u64,
864 pub velocity_bodies: u64,
866 pub damping_bodies: u64,
868 pub sleep_evaluated: u64,
870 pub parked_sleep_updates: u64,
872 pub tree_inserts: u64,
874 pub tree_removes: u64,
876}
877
878#[derive(Clone, Copy, Debug, PartialEq, Eq)]
881struct ParkCache {
882 position: Vec3Fix,
883 rotation: QuatFix,
884 angular_velocity: Vec3Fix,
888 radius: Option<Fix128>,
889 static_kind: bool,
892}
893
894#[derive(Clone, Debug, PartialEq, Eq)]
897struct ParkKey {
898 substep_dt: Fix128,
899 sleep: SleepConfig,
900 generation: u64,
901 sdf_radius: Fix128,
902 sdf: Vec<(Vec3Fix, QuatFix, Fix128, usize, usize)>,
905}
906
907#[derive(Default)]
909struct ParkState {
910 active: bool,
912 parked: Vec<bool>,
914 parked_list: Vec<usize>,
916 awake: Vec<usize>,
918 cache: Vec<Option<ParkCache>>,
919 proxies: Vec<Option<u32>>,
920 proxy_live: usize,
923 tree: crate::dynamic_bvh::DynamicAabbTree,
924 key: Option<ParkKey>,
925 constrained: Vec<bool>,
927 first_substep: bool,
929 frame_dt: Fix128,
931}
932
933impl ParkState {
934 fn clear_counted(&mut self, stats: &mut StageWork) {
936 stats.tree_removes += self.proxy_live as u64;
937 self.clear();
938 }
939
940 fn clear(&mut self) {
942 self.tree = crate::dynamic_bvh::DynamicAabbTree::new();
943 self.proxies.clear();
944 self.proxy_live = 0;
945 self.cache.clear();
946 self.parked.clear();
947 self.parked_list.clear();
948 self.awake.clear();
949 self.key = None;
950 self.active = false;
951 }
952
953 fn remove_proxy(&mut self, i: usize, stats: &mut StageWork) {
955 if let Some(proxy) = self.proxies.get_mut(i).and_then(Option::take) {
956 self.tree.remove(proxy);
957 self.proxy_live -= 1;
958 stats.tree_removes += 1;
959 }
960 }
961
962 fn unpark(&mut self, i: usize, stats: &mut StageWork) {
964 if !self.active || !self.parked.get(i).copied().unwrap_or(false) {
965 return;
966 }
967 self.parked[i] = false;
968 self.remove_proxy(i, stats);
969 if let Err(pos) = self.awake.binary_search(&i) {
970 self.awake.insert(pos, i);
971 }
972 stats.unparked += 1;
973 }
974
975 #[inline]
977 fn is_parked(&self, i: usize) -> bool {
978 self.active && self.parked.get(i).copied().unwrap_or(false)
979 }
980}
981
982const LOG_SERIES_LIMIT_SQ: Fix128 = Fix128::from_raw(0, 1 << 48);
985
986#[inline]
999fn angular_from_rotations(rotation: QuatFix, prev_rotation: QuatFix, inv_dt: Fix128) -> Vec3Fix {
1000 let dq = rotation.mul(prev_rotation.conjugate());
1001 let (v, w) = if dq.w < Fix128::ZERO {
1002 (Vec3Fix::new(-dq.x, -dq.y, -dq.z), -dq.w)
1003 } else {
1004 (Vec3Fix::new(dq.x, dq.y, dq.z), dq.w)
1005 };
1006 let s2 = v.dot(v);
1007 let factor = if s2 < LOG_SERIES_LIMIT_SQ {
1008 let series = Fix128::from_ratio(1, 6)
1009 + s2 * (Fix128::from_ratio(3, 40) + s2 * Fix128::from_ratio(5, 112));
1010 (Fix128::ONE + s2 * series).double()
1011 } else {
1012 let s = s2.sqrt();
1013 Fix128::atan2(s, w).double() / s
1014 };
1015 v * (factor * inv_dt)
1016}
1017
1018#[cfg(feature = "std")]
1021const TGS_KEY_CONTACT: u64 = 0;
1022#[cfg(feature = "std")]
1023const TGS_KEY_DISTANCE: u64 = 1;
1024
1025#[cfg(feature = "std")]
1034fn tgs_cache_key(
1035 tag: u64,
1036 a: u64,
1037 b: u64,
1038 ordinals: &mut std::collections::BTreeMap<(u64, u64), u64>,
1039) -> u64 {
1040 let slot = ordinals.entry((a, b)).or_insert(0);
1041 let ordinal = *slot;
1042 *slot += 1;
1043 let mut h: u64 = 0x9E37_79B9_7F4A_7C15;
1044 for w in [tag, a, b, ordinal] {
1045 h ^= w;
1046 h = h.wrapping_add(0x9E37_79B9_7F4A_7C15);
1047 h = (h ^ (h >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1048 h = (h ^ (h >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1049 h ^= h >> 31;
1050 }
1051 h
1052}
1053
1054#[inline]
1068fn predict_rotation(
1069 body: &mut RigidBody,
1070 dt: Fix128,
1071 overflow: &mut bool,
1072) -> Option<crate::gyroscopic::FreeRotation> {
1073 let split = crate::gyroscopic::split_free_rotation(
1074 body.angular_velocity,
1075 body.rotation,
1076 body.inv_inertia,
1077 dt,
1078 );
1079 match split {
1080 Some(f) => {
1081 body.angular_velocity = f.omega;
1082 body.rotation = f.rotation;
1083 }
1084 None => {
1085 let omega = body.angular_velocity;
1086 if let Some(speed_sq) = omega.checked_length_squared() {
1087 let ang_speed = speed_sq.sqrt();
1090 if !ang_speed.is_zero() {
1091 let axis = omega * (Fix128::ONE / ang_speed);
1092 let delta_rot = QuatFix::from_axis_angle(axis, ang_speed * dt);
1093 body.rotation = delta_rot.mul(body.rotation).normalize();
1094 }
1095 } else {
1096 let turn = omega
1098 .checked_length_scaled()
1099 .and_then(|s| s.checked_mul(dt))
1100 .zip(omega.try_normalize_scaled());
1101 match turn {
1102 Some((angle, axis)) => {
1103 let delta_rot = QuatFix::from_axis_angle(axis, angle);
1104 body.rotation = delta_rot.mul(body.rotation).normalize();
1105 }
1106 None => *overflow = true,
1107 }
1108 }
1109 }
1110 }
1111 split
1112}
1113
1114#[inline]
1132fn derive_linear_velocity(
1133 body: &mut RigidBody,
1134 predicted: Option<Vec3Fix>,
1135 inv_dt: Fix128,
1136) -> bool {
1137 match predicted {
1138 Some(x_pred) if body.position == x_pred => true,
1139 Some(x_pred) => match (body.position - x_pred).checked_scale(inv_dt) {
1140 Some(dv) => {
1141 body.velocity = body.velocity + dv;
1142 true
1143 }
1144 None => false,
1145 },
1146 None => match (body.position - body.prev_position).checked_scale(inv_dt) {
1147 Some(v) => {
1148 body.velocity = v;
1149 true
1150 }
1151 None => false,
1152 },
1153 }
1154}
1155
1156#[inline]
1168fn derived_angular_velocity(
1169 body: &RigidBody,
1170 free: Option<crate::gyroscopic::FreeRotation>,
1171 predicted: Option<QuatFix>,
1172 inv_dt: Fix128,
1173) -> Vec3Fix {
1174 match (free, predicted) {
1175 (Some(f), _) if body.rotation == f.rotation => f.omega,
1176 (Some(f), _) => f.omega + angular_from_rotations(body.rotation, f.rotation, inv_dt),
1177 (None, Some(q)) if body.rotation == q => body.angular_velocity,
1178 (None, Some(q)) => body.angular_velocity + angular_from_rotations(body.rotation, q, inv_dt),
1179 (None, None) => angular_from_rotations(body.rotation, body.prev_rotation, inv_dt),
1180 }
1181}
1182
1183#[cfg(feature = "std")]
1191#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1192#[non_exhaustive]
1193pub struct TgsCacheStats {
1194 pub hits: u64,
1197 pub misses: u64,
1201}
1202
1203#[cfg(feature = "std")]
1204impl TgsCacheStats {
1205 #[must_use]
1211 pub fn new(hits: u64, misses: u64) -> Self {
1212 Self { hits, misses }
1213 }
1214
1215 #[must_use]
1222 pub fn hit_rate(&self) -> f64 {
1223 crate::solver_tgs::ImpulseCacheStats {
1224 hits: self.hits,
1225 misses: self.misses,
1226 }
1227 .hit_rate()
1228 }
1229}
1230
1231#[cfg(feature = "std")]
1232impl From<crate::solver_tgs::ImpulseCacheStats> for TgsCacheStats {
1233 fn from(s: crate::solver_tgs::ImpulseCacheStats) -> Self {
1234 Self {
1235 hits: s.hits,
1236 misses: s.misses,
1237 }
1238 }
1239}
1240
1241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1243pub struct SolverConfig {
1244 pub substeps: usize,
1246 pub iterations: usize,
1249 pub gravity: Vec3Fix,
1251 pub damping: Fix128,
1255 pub warm_start_factor: Fix128,
1265 pub solver_backend: SolverBackend,
1272}
1273
1274pub type PhysicsConfig = SolverConfig;
1276
1277impl Default for SolverConfig {
1278 fn default() -> Self {
1279 Self {
1280 substeps: 8,
1281 iterations: 1,
1287 gravity: Vec3Fix::new(
1288 Fix128::ZERO,
1289 Fix128::from_int(-10), Fix128::ZERO,
1291 ),
1292 damping: Fix128::from_ratio(99, 100), warm_start_factor: Fix128::from_ratio(85, 100), solver_backend: SolverBackend::Xpbd,
1295 }
1296 }
1297}
1298
1299#[derive(Clone, Debug, Default)]
1308pub struct ConstraintBatch {
1309 pub distance_indices: Vec<usize>,
1311 pub contact_indices: Vec<usize>,
1313}
1314
1315#[cfg(feature = "parallel")]
1326struct BodySlicePtr {
1327 ptr: *mut RigidBody,
1328 len: usize,
1329}
1330
1331#[cfg(feature = "parallel")]
1336unsafe impl Send for BodySlicePtr {}
1337#[cfg(feature = "parallel")]
1338unsafe impl Sync for BodySlicePtr {}
1339
1340#[cfg(feature = "parallel")]
1341impl BodySlicePtr {
1342 #[allow(clippy::mut_from_ref)]
1343 #[inline(always)]
1344 unsafe fn get_mut(&self, idx: usize) -> &mut RigidBody {
1349 debug_assert!(idx < self.len);
1350 &mut *self.ptr.add(idx)
1351 }
1352
1353 #[inline(always)]
1354 unsafe fn get(&self, idx: usize) -> &RigidBody {
1359 debug_assert!(idx < self.len);
1360 &*self.ptr.add(idx)
1361 }
1362
1363 #[inline(always)]
1364 unsafe fn borrow(&self, idx: usize, is_static: bool) -> BodyRef<'_> {
1372 if is_static {
1373 BodyRef::Static(self.get(idx))
1374 } else {
1375 BodyRef::Dynamic(self.get_mut(idx))
1376 }
1377 }
1378}
1379
1380#[allow(clippy::too_many_arguments)]
1402#[inline]
1403fn static_friction_correction(
1404 disp_a: Vec3Fix,
1405 disp_b: Vec3Fix,
1406 normal: Vec3Fix,
1407 inv_mass_a: Fix128,
1408 inv_mass_b: Fix128,
1409 inv_w_sum: Fix128,
1410 mu: Fix128,
1411 lambda: Fix128,
1412) -> Option<(Vec3Fix, Vec3Fix)> {
1413 let limit = mu * lambda;
1414 if limit <= Fix128::ZERO {
1415 return None;
1416 }
1417 let dp = disp_a - disp_b;
1418 let dp_t = dp - normal * dp.dot(normal);
1419 let len_sq = dp_t.length_squared();
1420 if len_sq.is_zero() || len_sq >= limit * limit {
1421 return None;
1422 }
1423 Some((
1424 dp_t * (inv_mass_a * inv_w_sum),
1425 dp_t * (inv_mass_b * inv_w_sum),
1426 ))
1427}
1428
1429#[cfg(feature = "parallel")]
1436enum BodyRef<'a> {
1437 Dynamic(&'a mut RigidBody),
1438 Static(&'a RigidBody),
1439}
1440
1441#[cfg(feature = "parallel")]
1442impl BodyRef<'_> {
1443 #[inline(always)]
1444 fn get(&self) -> &RigidBody {
1445 match self {
1446 BodyRef::Dynamic(b) => b,
1447 BodyRef::Static(b) => b,
1448 }
1449 }
1450
1451 #[inline(always)]
1452 fn set_position(&mut self, position: Vec3Fix) {
1453 if let BodyRef::Dynamic(b) = self {
1454 b.position = position;
1455 }
1456 }
1457}
1458
1459#[cfg(feature = "parallel")]
1464struct DistConstraintSlicePtr {
1465 ptr: *mut DistanceConstraint,
1466 len: usize,
1467}
1468
1469#[cfg(feature = "parallel")]
1474unsafe impl Send for DistConstraintSlicePtr {}
1475#[cfg(feature = "parallel")]
1476unsafe impl Sync for DistConstraintSlicePtr {}
1477
1478#[cfg(feature = "parallel")]
1479impl DistConstraintSlicePtr {
1480 #[allow(clippy::mut_from_ref)]
1481 #[inline(always)]
1482 unsafe fn get_mut(&self, idx: usize) -> &mut DistanceConstraint {
1487 debug_assert!(idx < self.len);
1488 &mut *self.ptr.add(idx)
1489 }
1490}
1491
1492#[cfg(feature = "parallel")]
1494struct ContactConstraintSlicePtr {
1495 ptr: *mut ContactConstraint,
1496 len: usize,
1497}
1498
1499#[cfg(feature = "parallel")]
1502unsafe impl Send for ContactConstraintSlicePtr {}
1503#[cfg(feature = "parallel")]
1504unsafe impl Sync for ContactConstraintSlicePtr {}
1505
1506#[cfg(feature = "parallel")]
1507impl ContactConstraintSlicePtr {
1508 #[allow(clippy::mut_from_ref)]
1509 #[inline(always)]
1510 unsafe fn get_mut(&self, idx: usize) -> &mut ContactConstraint {
1515 debug_assert!(idx < self.len);
1516 &mut *self.ptr.add(idx)
1517 }
1518}
1519
1520#[cfg(feature = "std")]
1526pub type PreSolveHook = Box<dyn Fn(usize, usize, &Contact) -> bool + Send + Sync>;
1527
1528#[cfg(feature = "std")]
1534pub trait ContactModifier: Send + Sync {
1535 fn modify_contact(
1540 &self,
1541 body_a: usize,
1542 body_b: usize,
1543 contact: &mut Contact,
1544 friction: &mut Fix128,
1545 restitution: &mut Fix128,
1546 ) -> bool;
1547}
1548
1549#[derive(Clone, Copy, Debug, PartialEq)]
1555pub struct BodyObservation {
1556 pub body_index: usize,
1562 pub position: Vec3Fix,
1564 pub velocity: Vec3Fix,
1566 pub rotation: QuatFix,
1568 pub angular_velocity: Vec3Fix,
1570 pub sleeping: bool,
1572 pub in_contact: bool,
1578}
1579
1580#[derive(Clone, Debug)]
1583pub(crate) struct BodyAttachments {
1584 material: crate::material::MaterialId,
1585 collision_radius: Option<Fix128>,
1586 collider: Option<crate::body_collider::BodyCollider>,
1587 filter: CollisionFilter,
1588}
1589
1590pub struct PhysicsWorld {
1601 pub config: SolverConfig,
1603 pub bodies: Vec<RigidBody>,
1605 pub distance_constraints: Vec<DistanceConstraint>,
1607 pub contact_constraints: Vec<ContactConstraint>,
1609 pub sdf_colliders: Vec<SdfCollider>,
1611 pub sdf_collision_radius: Fix128,
1613 static_colliders: Vec<crate::static_collider::StaticCollider>,
1616 constraint_batches: Vec<ConstraintBatch>,
1618 batches_dirty: bool,
1620 batch_static_bodies: Vec<bool>,
1628 pub contact_cache: crate::contact_cache::ContactCache,
1630 pub material_table: crate::material::MaterialTable,
1632 pub body_materials: Vec<crate::material::MaterialId>,
1634 #[cfg(feature = "std")]
1636 pre_solve_hooks: Vec<PreSolveHook>,
1637 #[cfg(feature = "std")]
1639 contact_modifiers: Vec<Box<dyn ContactModifier>>,
1640 #[cfg(feature = "std")]
1644 contact_discarded: Vec<bool>,
1645 contact_pre_vn: Vec<Fix128>,
1650 free_rotation: Vec<Option<crate::gyroscopic::FreeRotation>>,
1655 predicted_pose: Vec<Option<(Vec3Fix, QuatFix)>>,
1663 #[cfg(feature = "gpu-solver-bridge")]
1675 gpu_solver_bridge: Option<Box<dyn crate::gpu_bridge::GpuSolverBridge + Send + Sync>>,
1676
1677 pub joints: Vec<Joint>,
1680 joint_motors: Vec<crate::motor::JointMotor>,
1683 joint_motors_3d: Vec<(usize, crate::motor::PdController3D)>,
1686 pub force_fields: Vec<ForceFieldInstance>,
1688 pub events: EventCollector,
1690 pub islands: IslandManager,
1692 body_collision_radii: Vec<Option<Fix128>>,
1695 broadphase: Broadphase,
1697 broadphase_tree: crate::dynamic_bvh::DynamicAabbTree,
1699 broadphase_proxies: Vec<Option<u32>>,
1701 broadphase_hybrid: BroadphaseHybrid,
1703 body_colliders: Vec<Option<crate::body_collider::BodyCollider>>,
1707 body_filters: Vec<CollisionFilter>,
1709 overflow_detected: bool,
1718 kinematic_substeps_left: usize,
1724 #[cfg(feature = "std")]
1728 tgs_impulse_cache: crate::solver_tgs::ImpulseCache,
1729 #[cfg(feature = "std")]
1736 body_stable_ids: Vec<u64>,
1737 #[cfg(feature = "std")]
1739 next_body_stable_id: u64,
1740 sleep_skip: bool,
1742 park: ParkState,
1744 park_generation: u64,
1747 stage_work: StageWork,
1749 sdf_contact_log: Vec<crate::sdf_collider::SdfContact>,
1753 sdf_contact_substep: usize,
1755 sdf_generations: Vec<u64>,
1759 #[cfg(feature = "parallel")]
1763 sdf_contact_scratch: Vec<Vec<crate::sdf_collider::SdfContact>>,
1764 #[cfg(feature = "std")]
1769 participants: std::sync::Mutex<Vec<Box<dyn crate::world_participant::Participant>>>,
1770 #[cfg(feature = "std")]
1774 participant_plan: Option<crate::world_participant::ParticipantPlan>,
1775 fault: Option<crate::world_participant::WorldFault>,
1777 fields: crate::world_participant::FieldBoard,
1779 ccd: WorldCcdConfig,
1781}
1782
1783fn fnv1a_fold(hash: &mut u64, bytes: &[u8]) {
1788 for &b in bytes {
1789 *hash ^= u64::from(b);
1790 *hash = hash.wrapping_mul(0x0000_0001_0000_01b3);
1791 }
1792}
1793
1794#[cfg(feature = "std")]
1797fn sdf_contact_of(
1798 collider: Option<&crate::body_collider::BodyCollider>,
1799 body: &RigidBody,
1800 radius: Fix128,
1801 sdf: &crate::sdf_collider::SdfCollider,
1802) -> Option<crate::collider::Contact> {
1803 match collider {
1804 Some(c) => c.sdf_contact(body.position, body.rotation, sdf),
1805 None => crate::sdf_collider::collide_sphere_sdf(body.position, radius, sdf),
1806 }
1807}
1808
1809fn kinematic_substep_pose(
1817 position: Vec3Fix,
1818 rotation: QuatFix,
1819 target_pos: Vec3Fix,
1820 target_rot: QuatFix,
1821 left: usize,
1822) -> (Vec3Fix, QuatFix) {
1823 if left <= 1 {
1824 return (target_pos, target_rot);
1825 }
1826 let frac = Fix128::ONE / Fix128::from_int(left as i64);
1827 (
1828 position + (target_pos - position) * frac,
1829 crate::interpolation::slerp(rotation, target_rot, frac),
1830 )
1831}
1832
1833impl PhysicsWorld {
1834 #[must_use]
1854 pub fn new(config: SolverConfig) -> Self {
1855 Self {
1856 config,
1857 bodies: Vec::new(),
1858 distance_constraints: Vec::new(),
1859 contact_constraints: Vec::new(),
1860 sdf_colliders: Vec::new(),
1861 sdf_collision_radius: Fix128::from_ratio(1, 2), static_colliders: Vec::new(),
1863 constraint_batches: Vec::new(),
1864 batches_dirty: true,
1865 batch_static_bodies: Vec::new(),
1866 contact_cache: crate::contact_cache::ContactCache::new(),
1867 material_table: crate::material::MaterialTable::new(),
1868 body_materials: Vec::new(),
1869 #[cfg(feature = "std")]
1870 pre_solve_hooks: Vec::new(),
1871 #[cfg(feature = "std")]
1872 contact_modifiers: Vec::new(),
1873 #[cfg(feature = "std")]
1874 contact_discarded: Vec::new(),
1875 contact_pre_vn: Vec::new(),
1876 free_rotation: Vec::new(),
1877 predicted_pose: Vec::new(),
1878 #[cfg(feature = "gpu-solver-bridge")]
1879 gpu_solver_bridge: None,
1880 joints: Vec::new(),
1881 joint_motors: Vec::new(),
1882 joint_motors_3d: Vec::new(),
1883 force_fields: Vec::new(),
1884 events: EventCollector::new(),
1885 islands: IslandManager::new(0, SleepConfig::default()),
1886 body_collision_radii: Vec::new(),
1887 broadphase: Broadphase::default(),
1888 broadphase_tree: crate::dynamic_bvh::DynamicAabbTree::new(),
1889 broadphase_proxies: Vec::new(),
1890 broadphase_hybrid: BroadphaseHybrid::new(),
1891 body_colliders: Vec::new(),
1892 body_filters: Vec::new(),
1893 overflow_detected: false,
1894 kinematic_substeps_left: 0,
1895 #[cfg(feature = "std")]
1896 tgs_impulse_cache: crate::solver_tgs::ImpulseCache::new(),
1897 #[cfg(feature = "std")]
1898 body_stable_ids: Vec::new(),
1899 #[cfg(feature = "std")]
1900 next_body_stable_id: 0,
1901 sleep_skip: true,
1902 park: ParkState::default(),
1903 park_generation: 0,
1904 stage_work: StageWork::default(),
1905 sdf_contact_log: Vec::new(),
1906 sdf_contact_substep: 0,
1907 sdf_generations: Vec::new(),
1908 #[cfg(feature = "parallel")]
1909 sdf_contact_scratch: Vec::new(),
1910 #[cfg(feature = "std")]
1911 participants: std::sync::Mutex::new(Vec::new()),
1912 #[cfg(feature = "std")]
1913 participant_plan: None,
1914 fault: None,
1915 fields: crate::world_participant::FieldBoard::new(),
1916 ccd: WorldCcdConfig::new(),
1917 }
1918 }
1919
1920 pub fn reset_world(&mut self) {
1953 *self = Self::new(self.config);
1954 }
1955
1956 #[cfg(feature = "gpu-solver-bridge")]
1975 pub fn set_gpu_solver_bridge(
1976 &mut self,
1977 bridge: Option<Box<dyn crate::gpu_bridge::GpuSolverBridge + Send + Sync>>,
1978 ) {
1979 self.gpu_solver_bridge = bridge;
1980 }
1981
1982 #[cfg(feature = "gpu-solver-bridge")]
1986 pub fn take_gpu_solver_bridge(
1987 &mut self,
1988 ) -> Option<Box<dyn crate::gpu_bridge::GpuSolverBridge + Send + Sync>> {
1989 self.gpu_solver_bridge.take()
1990 }
1991
1992 #[cfg(feature = "gpu-solver-bridge")]
1995 #[must_use]
1996 pub const fn gpu_solver_bridge_installed(&self) -> bool {
1997 self.gpu_solver_bridge.is_some()
1998 }
1999
2000 #[cfg(feature = "std")]
2004 fn sync_body_stable_ids(&mut self) {
2005 let n = self.bodies.len();
2006 self.body_stable_ids.truncate(n);
2007 while self.body_stable_ids.len() < n {
2008 self.body_stable_ids.push(self.next_body_stable_id);
2009 self.next_body_stable_id += 1;
2010 }
2011 }
2012
2013 pub fn add_body(&mut self, mut body: RigidBody) -> usize {
2018 body.make_rotations_unit();
2019 let idx = self.bodies.len();
2020 self.bodies.push(body);
2021 self.body_materials.push(crate::material::DEFAULT_MATERIAL);
2022 self.body_collision_radii.push(None);
2023 self.body_colliders.push(None);
2024 self.body_filters.push(CollisionFilter::DEFAULT);
2025 self.islands.resize(idx + 1);
2026 #[cfg(feature = "std")]
2027 self.sync_body_stable_ids();
2028 idx
2029 }
2030
2031 pub fn add_body_with_radius(&mut self, mut body: RigidBody, radius: Fix128) -> usize {
2039 body.make_rotations_unit();
2040 let idx = self.bodies.len();
2041 self.bodies.push(body);
2042 self.body_materials.push(crate::material::DEFAULT_MATERIAL);
2043 self.body_collision_radii.push(Some(radius));
2044 self.body_colliders.push(None);
2045 self.body_filters.push(CollisionFilter::DEFAULT);
2046 self.islands.resize(idx + 1);
2047 #[cfg(feature = "std")]
2048 self.sync_body_stable_ids();
2049 idx
2050 }
2051
2052 pub fn add_shaped_body(
2068 &mut self,
2069 shape: &crate::shape::Shape,
2070 density: Fix128,
2071 position: Vec3Fix,
2072 ) -> Result<usize, crate::shape::ShapeError> {
2073 let (mass, inertia) = shape.mass_and_inertia(density)?;
2074 let mut body = RigidBody::new_dynamic(position, mass);
2075 body.inv_inertia = Vec3Fix::new(
2076 Fix128::ONE / inertia.x,
2077 Fix128::ONE / inertia.y,
2078 Fix128::ONE / inertia.z,
2079 );
2080 let idx = self.add_body_with_radius(body, shape.bounding_radius());
2081 self.body_colliders[idx] = Some(crate::body_collider::BodyCollider::Shape(*shape));
2082 Ok(idx)
2083 }
2084
2085 pub fn set_body_shape(&mut self, body_idx: usize, shape: &crate::shape::Shape) -> bool {
2095 if body_idx >= self.bodies.len() || body_idx >= self.body_colliders.len() {
2096 return false;
2097 }
2098 self.park_generation = self.park_generation.wrapping_add(1);
2099 self.body_collision_radii[body_idx] = Some(shape.bounding_radius());
2100 self.body_colliders[body_idx] = Some(crate::body_collider::BodyCollider::Shape(*shape));
2101 true
2102 }
2103
2104 #[must_use]
2113 pub fn colliders_overlap(&self, a: usize, b: usize) -> bool {
2114 let (Some(body_a), Some(body_b)) = (self.bodies.get(a), self.bodies.get(b)) else {
2115 return false;
2116 };
2117 let collider = |i: usize| self.body_colliders.get(i).and_then(Option::as_ref);
2118 let radius = |i: usize| self.body_collision_radii.get(i).copied().flatten();
2119 let pose_a = (body_a.position, body_a.rotation);
2120 let pose_b = (body_b.position, body_b.rotation);
2121 match (collider(a), collider(b)) {
2122 (Some(ca), Some(cb)) => crate::body_collider::colliders_meet(ca, pose_a, cb, pose_b),
2123 (Some(ca), None) => radius(b).is_some_and(|rb| {
2124 crate::body_collider::collider_meets_sphere(
2125 ca,
2126 pose_a,
2127 crate::collider::Sphere::new(body_b.position, rb),
2128 )
2129 }),
2130 (None, Some(cb)) => radius(a).is_some_and(|ra| {
2131 crate::body_collider::collider_meets_sphere(
2132 cb,
2133 pose_b,
2134 crate::collider::Sphere::new(body_a.position, ra),
2135 )
2136 }),
2137 (None, None) => match (radius(a), radius(b)) {
2138 (Some(ra), Some(rb)) => {
2139 let reach = ra + rb;
2140 (body_a.position - body_b.position).length_squared() < reach * reach
2141 }
2142 _ => false,
2143 },
2144 }
2145 }
2146
2147 pub fn add_compound_body(
2167 &mut self,
2168 compound: &crate::compound::CompoundShape,
2169 density: Fix128,
2170 position: Vec3Fix,
2171 ) -> Result<usize, crate::shape::ShapeError> {
2172 use crate::shape::ShapeError;
2173 if density <= Fix128::ZERO {
2174 return Err(ShapeError::NonPositiveDensity);
2175 }
2176 if compound.is_empty() {
2177 return Err(ShapeError::DegenerateShape);
2178 }
2179 let props = compound.mass_properties(density);
2180 if props.mass <= Fix128::ZERO {
2181 return Err(ShapeError::DegenerateShape);
2182 }
2183 let (moments, axes) = crate::mass_properties::principal_axes(props.inertia_tensor);
2184 if !(moments.x > Fix128::ZERO && moments.y > Fix128::ZERO && moments.z > Fix128::ZERO) {
2185 return Err(ShapeError::MassNotRepresentable);
2186 }
2187 let frame = crate::body_collider::quat_from_rotation(axes);
2189 let to_principal = frame.conjugate();
2190 let mut stored = crate::compound::CompoundShape::new();
2191 for child in &compound.children {
2192 stored.children.push(crate::compound::CompoundChild {
2193 shape: child.shape.clone(),
2194 local_position: to_principal
2195 .rotate_vec(child.local_position - props.center_of_mass),
2196 local_rotation: to_principal.mul(child.local_rotation),
2197 });
2198 }
2199 let collider = crate::body_collider::BodyCollider::Compound(stored);
2200 let radius = collider.bounding_radius();
2201 let estimate = props.mass.to_f64() * radius.to_f64() * radius.to_f64();
2203 if !(estimate.is_finite() && estimate < (1u64 << 61) as f64) {
2204 return Err(ShapeError::MassNotRepresentable);
2205 }
2206 let mut body = RigidBody::new_dynamic(position, props.mass);
2207 body.rotation = frame;
2208 body.inv_inertia = Vec3Fix::new(
2209 Fix128::ONE / moments.x,
2210 Fix128::ONE / moments.y,
2211 Fix128::ONE / moments.z,
2212 );
2213 let idx = self.add_body_with_radius(body, radius);
2214 self.body_colliders[idx] = Some(collider);
2215 Ok(idx)
2216 }
2217
2218 fn pad_body_tables(&mut self) {
2221 let n = self.bodies.len();
2222 if self.body_materials.len() < n {
2223 self.body_materials
2224 .resize(n, crate::material::DEFAULT_MATERIAL);
2225 }
2226 if self.body_collision_radii.len() < n {
2227 self.body_collision_radii.resize(n, None);
2228 }
2229 if self.body_colliders.len() < n {
2230 self.body_colliders.resize(n, None);
2231 }
2232 if self.body_filters.len() < n {
2233 self.body_filters.resize(n, CollisionFilter::DEFAULT);
2234 }
2235 }
2236
2237 pub(crate) fn remove_body_with_attachments(
2242 &mut self,
2243 idx: usize,
2244 ) -> Option<(RigidBody, BodyAttachments)> {
2245 let attachments = BodyAttachments {
2248 material: self
2249 .body_materials
2250 .get(idx)
2251 .copied()
2252 .unwrap_or(crate::material::DEFAULT_MATERIAL),
2253 collision_radius: self.body_collision_radii.get(idx).copied().flatten(),
2254 collider: self.body_colliders.get(idx).cloned().flatten(),
2255 filter: self.body_filters.get(idx).copied().unwrap_or_default(),
2256 };
2257 let body = self.remove_body(idx)?;
2258 Some((body, attachments))
2259 }
2260
2261 pub(crate) fn add_body_with_attachments(
2264 &mut self,
2265 body: RigidBody,
2266 attachments: BodyAttachments,
2267 ) -> usize {
2268 let idx = self.add_body(body);
2269 self.body_materials[idx] = attachments.material;
2270 self.body_collision_radii[idx] = attachments.collision_radius;
2271 self.body_colliders[idx] = attachments.collider;
2272 self.body_filters[idx] = attachments.filter;
2273 idx
2274 }
2275
2276 pub fn remove_body(&mut self, idx: usize) -> Option<RigidBody> {
2282 if idx >= self.bodies.len() {
2283 return None;
2284 }
2285 let last = self.bodies.len() - 1;
2286
2287 self.distance_constraints
2294 .retain(|c| c.body_a != idx && c.body_b != idx);
2295 self.contact_constraints
2296 .retain(|c| c.body_a != idx && c.body_b != idx);
2297 if !self.joint_motors.is_empty() || !self.joint_motors_3d.is_empty() {
2298 let mut next = 0;
2301 let joint_map: Vec<Option<usize>> = self
2302 .joints
2303 .iter()
2304 .map(|j| {
2305 let (a, b) = j.bodies();
2306 if a != idx && b != idx {
2307 next += 1;
2308 Some(next - 1)
2309 } else {
2310 None
2311 }
2312 })
2313 .collect();
2314 self.remap_motor_joints(|j| joint_map.get(j).copied().flatten());
2315 }
2316 self.joints.retain(|j| {
2317 let (a, b) = j.bodies();
2318 a != idx && b != idx
2319 });
2320
2321 #[cfg(feature = "std")]
2324 self.sync_body_stable_ids();
2325 self.pad_body_tables();
2328 let removed = self.bodies.swap_remove(idx);
2329 self.body_materials.swap_remove(idx);
2330 self.body_collision_radii.swap_remove(idx);
2331 self.body_colliders.swap_remove(idx);
2332 self.body_filters.swap_remove(idx);
2333 #[cfg(feature = "std")]
2334 self.body_stable_ids.swap_remove(idx);
2335
2336 if idx != last {
2338 for c in &mut self.distance_constraints {
2339 if c.body_a == last {
2340 c.body_a = idx;
2341 }
2342 if c.body_b == last {
2343 c.body_b = idx;
2344 }
2345 }
2346 for c in &mut self.contact_constraints {
2347 if c.body_a == last {
2348 c.body_a = idx;
2349 }
2350 if c.body_b == last {
2351 c.body_b = idx;
2352 }
2353 }
2354 self.remap_joint_indices(last, idx);
2355 }
2356
2357 let new_len = self.bodies.len();
2359 self.islands = IslandManager::new(new_len, self.islands.config);
2360 for j in &self.joints {
2361 let (a, b) = j.bodies();
2362 if a < new_len && b < new_len {
2363 self.islands.union(a, b);
2364 }
2365 }
2366
2367 self.park_generation = self.park_generation.wrapping_add(1);
2368 self.batches_dirty = true;
2369 Some(removed)
2370 }
2371
2372 fn remap_joint_indices(&mut self, from: usize, to: usize) {
2374 macro_rules! remap {
2376 ($j:expr) => {
2377 if $j.body_a == from {
2378 $j.body_a = to;
2379 }
2380 if $j.body_b == from {
2381 $j.body_b = to;
2382 }
2383 };
2384 }
2385 for joint in &mut self.joints {
2386 match joint {
2387 Joint::Ball(j) => {
2388 remap!(j);
2389 }
2390 Joint::Hinge(j) => {
2391 remap!(j);
2392 }
2393 Joint::Fixed(j) => {
2394 remap!(j);
2395 }
2396 Joint::Slider(j) => {
2397 remap!(j);
2398 }
2399 Joint::Spring(j) => {
2400 remap!(j);
2401 }
2402 Joint::D6(j) => {
2403 remap!(j);
2404 }
2405 Joint::ConeTwist(j) => {
2406 remap!(j);
2407 }
2408 }
2409 }
2410 }
2411
2412 #[inline]
2414 #[must_use]
2415 pub fn body_count(&self) -> usize {
2416 self.bodies.len()
2417 }
2418
2419 #[must_use]
2421 pub fn active_body_count(&self) -> usize {
2422 self.bodies.len() - self.islands.sleeping_count().min(self.bodies.len())
2423 }
2424
2425 pub fn set_body_material(&mut self, body_idx: usize, material_id: crate::material::MaterialId) {
2427 if body_idx < self.body_materials.len() {
2428 self.body_materials[body_idx] = material_id;
2429 }
2430 }
2431
2432 #[cfg(feature = "std")]
2437 pub fn add_pre_solve_hook(&mut self, hook: PreSolveHook) {
2438 self.pre_solve_hooks.push(hook);
2439 }
2440
2441 #[cfg(feature = "std")]
2443 pub fn clear_pre_solve_hooks(&mut self) {
2444 self.pre_solve_hooks.clear();
2445 }
2446
2447 #[cfg(feature = "std")]
2452 pub fn add_contact_modifier(&mut self, modifier: Box<dyn ContactModifier>) {
2453 self.contact_modifiers.push(modifier);
2454 }
2455
2456 #[cfg(feature = "std")]
2458 pub fn clear_contact_modifiers(&mut self) {
2459 self.contact_modifiers.clear();
2460 }
2461
2462 pub fn begin_frame(&mut self) {
2464 self.contact_cache.begin_frame();
2465 }
2466
2467 pub fn end_frame(&mut self) {
2469 self.contact_cache.end_frame();
2470 }
2471
2472 pub fn add_joint(&mut self, joint: Joint) -> usize {
2480 let (a, b) = joint.bodies();
2481 assert!(
2482 a < self.bodies.len() && b < self.bodies.len(),
2483 "Joint body indices ({}, {}) out of bounds (body count = {})",
2484 a,
2485 b,
2486 self.bodies.len()
2487 );
2488 let idx = self.joints.len();
2489 self.islands.resize(self.bodies.len());
2490 self.islands.union(a, b);
2491 self.joints.push(joint);
2492 idx
2493 }
2494
2495 pub fn remove_joint(&mut self, idx: usize) -> Option<Joint> {
2501 if idx >= self.joints.len() {
2502 return None;
2503 }
2504 let last = self.joints.len() - 1;
2505 self.remap_motor_joints(|j| {
2506 if j == idx {
2507 None
2508 } else if j == last {
2509 Some(idx)
2510 } else {
2511 Some(j)
2512 }
2513 });
2514 Some(self.joints.swap_remove(idx))
2515 }
2516
2517 fn remap_motor_joints(&mut self, map: impl Fn(usize) -> Option<usize>) {
2519 self.joint_motors.retain_mut(|m| match map(m.joint_index) {
2520 Some(j) => {
2521 m.joint_index = j;
2522 true
2523 }
2524 None => false,
2525 });
2526 self.joint_motors_3d
2527 .retain_mut(|(joint, _)| match map(*joint) {
2528 Some(j) => {
2529 *joint = j;
2530 true
2531 }
2532 None => false,
2533 });
2534 }
2535
2536 pub fn add_joint_motor(
2551 &mut self,
2552 joint_index: usize,
2553 controller: crate::motor::PdController,
2554 ) -> usize {
2555 self.joint_motors
2556 .push(crate::motor::JointMotor::new(joint_index, controller));
2557 self.joint_motors.len() - 1
2558 }
2559
2560 #[must_use]
2563 pub fn joint_motor_mut(&mut self, motor: usize) -> Option<&mut crate::motor::JointMotor> {
2564 self.joint_motors.get_mut(motor)
2565 }
2566
2567 pub fn set_joint_motor_velocity_target(&mut self, motor: usize, target: Fix128) -> bool {
2571 match self.joint_motors.get_mut(motor) {
2572 Some(m) => {
2573 m.controller.set_velocity_target(target);
2574 true
2575 }
2576 None => false,
2577 }
2578 }
2579
2580 pub fn disable_joint_motor(&mut self, motor: usize) -> bool {
2583 match self.joint_motors.get_mut(motor) {
2584 Some(m) => {
2585 m.controller.disable();
2586 true
2587 }
2588 None => false,
2589 }
2590 }
2591
2592 pub fn add_joint_motor_3d(
2600 &mut self,
2601 joint_index: usize,
2602 kp: Vec3Fix,
2603 kd: Vec3Fix,
2604 max_torque: Fix128,
2605 ) -> usize {
2606 self.joint_motors_3d.push((
2607 joint_index,
2608 crate::motor::PdController3D::new(kp, kd, max_torque),
2609 ));
2610 self.joint_motors_3d.len() - 1
2611 }
2612
2613 #[must_use]
2615 pub fn joint_motor_3d_mut(
2616 &mut self,
2617 motor: usize,
2618 ) -> Option<&mut crate::motor::PdController3D> {
2619 self.joint_motors_3d.get_mut(motor).map(|(_, c)| c)
2620 }
2621
2622 pub fn set_joint_motor_3d_rotation_target(&mut self, motor: usize, target: QuatFix) -> bool {
2625 match self.joint_motors_3d.get_mut(motor) {
2626 Some((_, c)) => {
2627 c.set_rotation_target(target);
2628 true
2629 }
2630 None => false,
2631 }
2632 }
2633
2634 fn apply_joint_motors(&mut self, dt: Fix128) {
2641 if self.joint_motors.is_empty() && self.joint_motors_3d.is_empty() {
2642 return;
2643 }
2644 let mut asleep: Vec<(usize, Vec3Fix, Vec3Fix)> = Vec::new();
2645 let n = self.bodies.len();
2646 let pairs = self
2647 .joint_motors
2648 .iter()
2649 .map(|m| m.joint_index)
2650 .chain(self.joint_motors_3d.iter().map(|(j, _)| *j));
2651 for j in pairs {
2652 if let Some(joint) = self.joints.get(j) {
2653 let (a, b) = joint.bodies();
2654 for x in [a, b] {
2655 if x < n && self.islands.is_sleeping(x) {
2656 asleep.push((x, self.bodies[x].velocity, self.bodies[x].angular_velocity));
2657 }
2658 }
2659 }
2660 }
2661 crate::motor::apply_motors(&self.joint_motors, &self.joints, &mut self.bodies, dt);
2662 crate::motor::apply_motors_3d(&self.joint_motors_3d, &self.joints, &mut self.bodies, dt);
2663 for (x, v, w) in asleep {
2664 let body = &self.bodies[x];
2665 if (body.velocity != v || body.angular_velocity != w)
2666 && x < self.islands.sleep_data.len()
2667 {
2668 self.islands.wake_island(x);
2669 }
2670 }
2671 }
2672
2673 #[inline]
2675 #[must_use]
2676 pub fn joint_count(&self) -> usize {
2677 self.joints.len()
2678 }
2679
2680 pub fn add_force_field(&mut self, field: ForceFieldInstance) -> usize {
2684 let idx = self.force_fields.len();
2685 self.force_fields.push(field);
2686 idx
2687 }
2688
2689 pub fn remove_force_field(&mut self, idx: usize) -> Option<ForceFieldInstance> {
2691 if idx >= self.force_fields.len() {
2692 return None;
2693 }
2694 Some(self.force_fields.swap_remove(idx))
2695 }
2696
2697 pub fn set_body_collision_radius(&mut self, body_idx: usize, radius: Fix128) {
2701 if body_idx < self.body_collision_radii.len() {
2702 self.park_generation = self.park_generation.wrapping_add(1);
2703 self.body_collision_radii[body_idx] = Some(radius);
2704 }
2705 }
2706
2707 pub fn clear_body_collision_radius(&mut self, body_idx: usize) {
2709 if body_idx < self.body_collision_radii.len() {
2710 self.park_generation = self.park_generation.wrapping_add(1);
2711 self.body_collision_radii[body_idx] = None;
2712 }
2713 }
2714
2715 pub fn set_body_filter(&mut self, body_idx: usize, filter: CollisionFilter) {
2717 if body_idx < self.body_filters.len() {
2718 self.body_filters[body_idx] = filter;
2719 }
2720 }
2721
2722 #[must_use]
2724 pub fn body_filter(&self, body_idx: usize) -> CollisionFilter {
2725 self.body_filters
2726 .get(body_idx)
2727 .copied()
2728 .unwrap_or(CollisionFilter::DEFAULT)
2729 }
2730
2731 #[inline]
2735 #[must_use]
2736 pub fn is_sleeping(&self, body_idx: usize) -> bool {
2737 self.islands.is_sleeping(body_idx)
2738 }
2739
2740 pub fn wake_body(&mut self, body_idx: usize) {
2742 if body_idx >= self.bodies.len() {
2745 return;
2746 }
2747 self.islands.wake_island(body_idx);
2748 }
2749
2750 pub fn set_sleep_config(&mut self, config: SleepConfig) {
2752 self.islands.config = config;
2753 }
2754
2755 pub fn set_sleep_skip(&mut self, enabled: bool) {
2769 self.sleep_skip = enabled;
2770 if !enabled {
2771 self.park.clear();
2772 }
2773 }
2774
2775 #[must_use]
2777 pub const fn sleep_skip(&self) -> bool {
2778 self.sleep_skip
2779 }
2780
2781 #[must_use]
2783 pub const fn stage_work(&self) -> StageWork {
2784 self.stage_work
2785 }
2786
2787 #[must_use]
2802 pub fn observe_body(&self, body_idx: usize) -> Option<BodyObservation> {
2803 let body = self.bodies.get(body_idx)?;
2804 let in_contact = self
2805 .events
2806 .contact_events()
2807 .iter()
2808 .any(|e| e.body_a == body_idx || e.body_b == body_idx);
2809 Some(BodyObservation {
2810 body_index: body_idx,
2811 position: body.position,
2812 velocity: body.velocity,
2813 rotation: body.rotation,
2814 angular_velocity: body.angular_velocity,
2815 sleeping: self.is_sleeping(body_idx),
2816 in_contact,
2817 })
2818 }
2819
2820 #[must_use]
2822 pub fn observe_bodies(&self) -> Vec<BodyObservation> {
2823 (0..self.bodies.len())
2824 .map(|i| {
2825 self.observe_body(i)
2826 .expect("index came from 0..bodies.len(), always in range")
2827 })
2828 .collect()
2829 }
2830
2831 #[inline]
2835 #[must_use]
2836 pub fn contact_events(&self) -> &[crate::event::ContactEvent] {
2837 self.events.contact_events()
2838 }
2839
2840 #[inline]
2842 #[must_use]
2843 pub fn trigger_events(&self) -> &[crate::event::TriggerEvent] {
2844 self.events.trigger_events()
2845 }
2846
2847 #[inline]
2849 pub fn drain_contact_events(&mut self) -> Vec<crate::event::ContactEvent> {
2850 self.events.drain_contact_events()
2851 }
2852
2853 #[inline]
2855 pub fn drain_trigger_events(&mut self) -> Vec<crate::event::TriggerEvent> {
2856 self.events.drain_trigger_events()
2857 }
2858
2859 pub(crate) fn ray_geometry(
2865 &self,
2866 i: usize,
2867 ) -> Option<(Fix128, Option<&crate::body_collider::BodyCollider>)> {
2868 let radius = self.body_collision_radii.get(i).copied().flatten()?;
2869 Some((radius, self.body_colliders.get(i).and_then(Option::as_ref)))
2870 }
2871
2872 pub(crate) fn static_colliders_slice(&self) -> &[crate::static_collider::StaticCollider] {
2874 &self.static_colliders
2875 }
2876
2877 pub(crate) fn contact_constraint_force(
2880 &self,
2881 c: &ContactConstraint,
2882 dt: Fix128,
2883 ) -> Option<Fix128> {
2884 let h = self.substep_dt(dt);
2885 self.contact_force(c, h * h).map(|(_, _, force)| force)
2886 }
2887
2888 #[must_use]
2901 pub fn raycast(
2902 &self,
2903 origin: Vec3Fix,
2904 direction: Vec3Fix,
2905 max_distance: Fix128,
2906 ) -> Option<(usize, Fix128)> {
2907 if direction.length_squared().is_zero() {
2908 return None;
2909 }
2910 let dir_norm = direction.normalize();
2911
2912 let mut primitives = Vec::new();
2914 for i in 0..self.bodies.len() {
2915 if let Some(radius) = self.body_collision_radii.get(i).and_then(|r| *r) {
2916 let pos = self.bodies[i].position;
2917 let half = Vec3Fix::new(radius, radius, radius);
2918 let aabb = crate::collider::AABB::from_center_half(pos, half);
2919 primitives.push(crate::bvh::BvhPrimitive {
2920 aabb,
2921 index: i as u32,
2922 morton: 0,
2923 });
2924 }
2925 }
2926
2927 if primitives.is_empty() {
2928 return None;
2929 }
2930
2931 let endpoint = origin + dir_norm * max_distance;
2933 let ray_min = Vec3Fix::new(
2934 if origin.x < endpoint.x {
2935 origin.x
2936 } else {
2937 endpoint.x
2938 },
2939 if origin.y < endpoint.y {
2940 origin.y
2941 } else {
2942 endpoint.y
2943 },
2944 if origin.z < endpoint.z {
2945 origin.z
2946 } else {
2947 endpoint.z
2948 },
2949 );
2950 let ray_max = Vec3Fix::new(
2951 if origin.x > endpoint.x {
2952 origin.x
2953 } else {
2954 endpoint.x
2955 },
2956 if origin.y > endpoint.y {
2957 origin.y
2958 } else {
2959 endpoint.y
2960 },
2961 if origin.z > endpoint.z {
2962 origin.z
2963 } else {
2964 endpoint.z
2965 },
2966 );
2967 let ray_aabb = crate::collider::AABB {
2968 min: ray_min,
2969 max: ray_max,
2970 };
2971
2972 let bvh = crate::bvh::LinearBvh::build(primitives);
2973 let candidates = bvh.query(&ray_aabb);
2974
2975 let mut best: Option<(usize, Fix128)> = None;
2977 for &prim_idx in &candidates {
2978 let i = prim_idx as usize;
2979 let radius = match self.body_collision_radii.get(i) {
2980 Some(Some(r)) => *r,
2981 _ => continue,
2982 };
2983 let oc = origin - self.bodies[i].position;
2984 let b = oc.dot(dir_norm);
2985 let c = oc.dot(oc) - radius * radius;
2986 let discriminant = b * b - c;
2987 if discriminant < Fix128::ZERO {
2988 continue;
2989 }
2990 let sqrt_d = discriminant.sqrt();
2991 let mut t = -b - sqrt_d;
2993 if t < Fix128::ZERO {
2995 t = -b + sqrt_d;
2996 }
2997 if t < Fix128::ZERO || t > max_distance {
2998 continue;
2999 }
3000 let dominated = match best {
3001 None => true,
3002 Some((_, prev_t)) => t < prev_t,
3003 };
3004 if dominated {
3005 best = Some((i, t));
3006 }
3007 }
3008 best
3009 }
3010
3011 #[must_use]
3028 pub fn combined_material(
3029 &self,
3030 body_a: usize,
3031 body_b: usize,
3032 ) -> crate::material::CombinedMaterial {
3033 let side = |i: usize| -> (crate::material::MaterialId, Option<(Fix128, Fix128)>) {
3034 let Some(&mat) = self.body_materials.get(i) else {
3035 return (crate::material::DEFAULT_MATERIAL, None);
3036 };
3037 if mat != crate::material::DEFAULT_MATERIAL {
3038 return (mat, None);
3039 }
3040 (mat, self.bodies.get(i).map(|b| (b.friction, b.restitution)))
3041 };
3042 let (mat_a, surface_a) = side(body_a);
3043 let (mat_b, surface_b) = side(body_b);
3044 self.material_table
3045 .combine_with_surface(mat_a, surface_a, mat_b, surface_b)
3046 }
3047
3048 pub fn add_distance_constraint(&mut self, constraint: DistanceConstraint) {
3050 self.distance_constraints.push(constraint);
3051 self.batches_dirty = true;
3052 }
3053
3054 pub fn add_contact(&mut self, contact: ContactConstraint) {
3056 let key = crate::contact_cache::BodyPairKey::new(contact.body_a, contact.body_b);
3058 let manifold = self
3059 .contact_cache
3060 .get_or_create(key, contact.friction, contact.restitution);
3061 manifold.add_or_update(
3062 &contact.contact,
3063 contact.contact.point_a,
3064 contact.contact.point_b,
3065 );
3066
3067 self.contact_constraints.push(contact);
3068 self.batches_dirty = true;
3069 }
3070
3071 pub fn add_contact_with_material(&mut self, body_a: usize, body_b: usize, contact: Contact) {
3075 let combined = self.combined_material(body_a, body_b);
3076 let constraint = ContactConstraint {
3077 body_a,
3078 body_b,
3079 contact,
3080 friction: combined.friction,
3081 restitution: combined.restitution,
3082 cached_lambda: Fix128::ZERO,
3083 };
3084 self.add_contact(constraint);
3085 }
3086
3087 pub fn clear_contacts(&mut self) {
3092 self.contact_constraints.clear();
3093 #[cfg(feature = "std")]
3094 self.contact_discarded.clear();
3095 self.batches_dirty = true;
3096 }
3097
3098 pub fn rebuild_batches(&mut self) {
3103 if !self.batches_dirty {
3104 return;
3105 }
3106
3107 self.constraint_batches.clear();
3108
3109 let num_bodies = self.bodies.len();
3115 self.batch_static_bodies.clear();
3116 self.batch_static_bodies
3117 .extend(self.bodies.iter().map(|b| b.inv_mass.is_zero()));
3118
3119 let mut body_colors: Vec<Vec<u64>> = vec![Vec::new(); num_bodies];
3124
3125 for (constraint_idx, constraint) in self.distance_constraints.iter().enumerate() {
3127 let body_a = constraint.body_a;
3128 let body_b = constraint.body_b;
3129
3130 let color =
3132 Self::find_free_color(&body_colors, &self.batch_static_bodies, body_a, body_b);
3133
3134 while self.constraint_batches.len() <= color {
3136 self.constraint_batches.push(ConstraintBatch::default());
3137 }
3138
3139 self.constraint_batches[color]
3141 .distance_indices
3142 .push(constraint_idx);
3143
3144 Self::mark_color(&mut body_colors, &self.batch_static_bodies, body_a, color);
3146 Self::mark_color(&mut body_colors, &self.batch_static_bodies, body_b, color);
3147 }
3148
3149 for (constraint_idx, constraint) in self.contact_constraints.iter().enumerate() {
3151 let body_a = constraint.body_a;
3152 let body_b = constraint.body_b;
3153
3154 let color =
3155 Self::find_free_color(&body_colors, &self.batch_static_bodies, body_a, body_b);
3156
3157 while self.constraint_batches.len() <= color {
3158 self.constraint_batches.push(ConstraintBatch::default());
3159 }
3160
3161 self.constraint_batches[color]
3162 .contact_indices
3163 .push(constraint_idx);
3164
3165 Self::mark_color(&mut body_colors, &self.batch_static_bodies, body_a, color);
3166 Self::mark_color(&mut body_colors, &self.batch_static_bodies, body_b, color);
3167 }
3168
3169 self.batches_dirty = false;
3170
3171 debug_assert!(
3172 self.batches_are_body_disjoint(),
3173 "graph coloring produced a batch with two constraints sharing a dynamic body"
3174 );
3175 }
3176
3177 #[inline(always)]
3180 fn color_word(
3181 body_colors: &[Vec<u64>],
3182 static_bodies: &[bool],
3183 idx: usize,
3184 word: usize,
3185 ) -> u64 {
3186 if static_bodies.get(idx).copied().unwrap_or(false) {
3187 return 0;
3188 }
3189 body_colors
3190 .get(idx)
3191 .and_then(|words| words.get(word))
3192 .copied()
3193 .unwrap_or(0)
3194 }
3195
3196 fn find_free_color(
3204 body_colors: &[Vec<u64>],
3205 static_bodies: &[bool],
3206 body_a: usize,
3207 body_b: usize,
3208 ) -> usize {
3209 let words_a = body_colors.get(body_a).map_or(0, Vec::len);
3210 let words_b = body_colors.get(body_b).map_or(0, Vec::len);
3211 let words = words_a.max(words_b);
3212
3213 for word in 0..words {
3214 let occupied = Self::color_word(body_colors, static_bodies, body_a, word)
3215 | Self::color_word(body_colors, static_bodies, body_b, word);
3216 if occupied != u64::MAX {
3217 return word * 64 + (!occupied).trailing_zeros() as usize;
3218 }
3219 }
3220
3221 words * 64
3222 }
3223
3224 #[inline(always)]
3229 fn mark_color(body_colors: &mut [Vec<u64>], static_bodies: &[bool], idx: usize, color: usize) {
3230 if static_bodies.get(idx).copied().unwrap_or(false) {
3231 return;
3232 }
3233 if let Some(words) = body_colors.get_mut(idx) {
3234 let word = color / 64;
3235 if words.len() <= word {
3236 words.resize(word + 1, 0);
3237 }
3238 words[word] |= 1u64 << (color % 64);
3239 }
3240 }
3241
3242 pub(crate) fn batches_are_body_disjoint(&self) -> bool {
3249 let num_bodies = self.bodies.len();
3250 let mut last_batch: Vec<usize> = vec![usize::MAX; num_bodies];
3252
3253 for (batch_idx, batch) in self.constraint_batches.iter().enumerate() {
3254 let dist_bodies = batch.distance_indices.iter().map(|&i| {
3255 (
3256 self.distance_constraints[i].body_a,
3257 self.distance_constraints[i].body_b,
3258 )
3259 });
3260 let contact_bodies = batch.contact_indices.iter().map(|&i| {
3261 (
3262 self.contact_constraints[i].body_a,
3263 self.contact_constraints[i].body_b,
3264 )
3265 });
3266
3267 for (a, b) in dist_bodies.chain(contact_bodies) {
3268 for (k, idx) in [a, b].into_iter().enumerate() {
3269 if (k == 1 && a == b) || idx >= num_bodies || self.batch_static_bodies[idx] {
3271 continue;
3272 }
3273 if last_batch[idx] == batch_idx {
3274 return false;
3275 }
3276 last_batch[idx] = batch_idx;
3277 }
3278 }
3279 }
3280 true
3281 }
3282
3283 #[must_use]
3285 pub fn num_batches(&self) -> usize {
3286 self.constraint_batches.len()
3287 }
3288
3289 pub fn step(&mut self, dt: Fix128) {
3320 let _ = self.try_step(dt);
3321 }
3322
3323 fn make_body_rotations_unit(&mut self) {
3333 for body in &mut self.bodies {
3334 body.make_rotations_unit();
3335 }
3336 for collider in &mut self.sdf_colliders {
3337 collider.update_cache();
3338 }
3339 }
3340
3341 fn run_step(&mut self, dt: Fix128) {
3343 self.make_body_rotations_unit();
3344 self.wake_on_sdf_shape_change();
3345 self.stage_work = StageWork::default();
3346 self.begin_sdf_contact_log();
3347 let mut frozen = self.participant_flags();
3348
3349 #[cfg(feature = "std")]
3355 if matches!(self.config.solver_backend, SolverBackend::Tgs) {
3356 self.step_tgs(dt, &mut frozen);
3357 return;
3358 }
3359
3360 self.events.begin_frame();
3363
3364 self.islands.resize(self.bodies.len());
3366 self.islands.reset_unions();
3367 for j in &self.joints {
3368 let (a, b) = j.bodies();
3369 if a < self.bodies.len() && b < self.bodies.len() {
3370 self.islands.union(a, b);
3371 }
3372 }
3373
3374 let substep_dt = dt / Fix128::from_int(self.config.substeps as i64);
3375
3376 self.park_begin(dt, substep_dt);
3379
3380 if !self.force_fields.is_empty() {
3382 if self.park.active {
3383 for k in 0..self.park.awake.len() {
3384 let i = self.park.awake[k];
3385 let body = &mut self.bodies[i];
3386 if body.is_static() {
3387 continue;
3388 }
3389 self.stage_work.force_field_bodies += 1;
3390 body.velocity =
3391 crate::force::force_field_velocity(&self.force_fields, i, body, dt);
3392 }
3393 } else {
3394 self.stage_work.force_field_bodies += self.bodies.len() as u64;
3395 apply_force_fields(&self.force_fields, &mut self.bodies, dt);
3396 }
3397 }
3398
3399 let n = self.config.substeps;
3406 for i in 0..n {
3407 self.kinematic_substeps_left = n - i;
3408 self.park.first_substep = i == 0;
3409 self.sdf_contact_substep = i;
3410 let overflow_at_start = self.participants_begin_substep(i, n, substep_dt, &mut frozen);
3413 self.substep(substep_dt);
3414 self.participants_end_substep(overflow_at_start, &frozen);
3415 }
3416 self.kinematic_substeps_left = 0;
3417 self.sdf_contact_substep = 0;
3418 self.park.first_substep = false;
3419
3420 self.apply_frame_damping();
3422
3423 self.update_sleep_states();
3425 self.park.active = false;
3426
3427 self.sync_sdf_colliders();
3431
3432 self.events.end_frame();
3434 }
3435
3436 pub fn step_n(&mut self, n: usize, dt: Fix128) {
3460 for _ in 0..n {
3461 self.step(dt);
3462 }
3463 }
3464
3465 fn update_sleep_states(&mut self) {
3469 if !self.park.active {
3470 self.stage_work.sleep_evaluated += self.bodies.len() as u64;
3471 self.islands.update_sleep(&self.bodies);
3472 return;
3473 }
3474 for k in 0..self.park.awake.len() {
3475 let i = self.park.awake[k];
3476 self.stage_work.sleep_evaluated += 1;
3477 self.islands.update_sleep_body(i, &self.bodies[i]);
3478 }
3479 for k in 0..self.park.parked_list.len() {
3480 let i = self.park.parked_list[k];
3481 if !self.park.parked[i] {
3482 continue; }
3484 self.stage_work.parked_sleep_updates += 1;
3485 self.islands
3486 .update_sleep_idle(i, self.bodies[i].is_static());
3487 }
3488 }
3489
3490 fn park_begin(&mut self, dt: Fix128, substep_dt: Fix128) {
3502 self.park.active = false;
3503 let n = self.bodies.len();
3504 if !self.sleep_skip || self.broadphase != Broadphase::Bvh || n == 0 {
3505 if !self.park.proxies.is_empty() || self.park.key.is_some() {
3506 self.park.clear_counted(&mut self.stage_work);
3507 }
3508 return;
3509 }
3510 let cfg = self.islands.config;
3513 if !(Vec3Fix::ZERO.length() < cfg.linear_threshold) {
3514 self.park.clear_counted(&mut self.stage_work);
3515 return;
3516 }
3517 if self.sdf_colliders.iter().any(|c| c.body_index < n) {
3521 self.park.clear_counted(&mut self.stage_work);
3522 return;
3523 }
3524
3525 let key = ParkKey {
3526 substep_dt,
3527 sleep: cfg,
3528 generation: self.park_generation,
3529 sdf_radius: self.sdf_collision_radius,
3530 sdf: self
3531 .sdf_colliders
3532 .iter()
3533 .map(|c| {
3534 (
3535 c.position,
3536 c.rotation,
3537 c.scale,
3538 c.body_index,
3539 core::ptr::from_ref::<dyn crate::sdf_collider::SdfField>(c.field.as_ref())
3540 .cast::<u8>() as usize,
3541 )
3542 })
3543 .collect(),
3544 };
3545 if self.park.key.as_ref() != Some(&key) || n < self.park.parked.len() {
3547 self.park.clear_counted(&mut self.stage_work);
3548 self.park.key = Some(key);
3549 }
3550
3551 self.park.parked.resize(n, false);
3552 self.park.cache.resize(n, None);
3553 self.park.proxies.resize(n, None);
3554 self.park.constrained.clear();
3555 self.park.constrained.resize(n, false);
3556 for j in &self.joints {
3557 let (a, b) = j.bodies();
3558 for x in [a, b] {
3559 if x < n {
3560 self.park.constrained[x] = true;
3561 }
3562 }
3563 }
3564 for c in &self.distance_constraints {
3565 for x in [c.body_a, c.body_b] {
3566 if x < n {
3567 self.park.constrained[x] = true;
3568 }
3569 }
3570 }
3571
3572 self.park.frame_dt = dt;
3573 self.park.awake.clear();
3574 self.park.parked_list.clear();
3575 let inv_dt = Fix128::ONE / substep_dt;
3576 for i in 0..n {
3577 self.stage_work.sleep_scanned += 1;
3578 let (ok, recached) = self.park_check(i, inv_dt);
3579 self.park.parked[i] = ok;
3580 let radius = self.body_collision_radii.get(i).and_then(|r| *r);
3581 match (ok, radius) {
3582 (true, Some(r)) => {
3583 self.park.parked_list.push(i);
3584 if recached || self.park.proxies[i].is_none() {
3585 self.park.remove_proxy(i, &mut self.stage_work);
3586 let aabb = self.broadphase_box(i, r);
3587 self.park.proxies[i] = Some(self.park.tree.insert(aabb, i as u32));
3588 self.park.proxy_live += 1;
3589 self.stage_work.tree_inserts += 1;
3590 }
3591 }
3592 (true, None) => {
3593 self.park.parked_list.push(i);
3594 self.park.remove_proxy(i, &mut self.stage_work);
3595 }
3596 (false, _) => {
3597 self.park.awake.push(i);
3598 self.park.remove_proxy(i, &mut self.stage_work);
3599 }
3600 }
3601 }
3602 self.stage_work.parked = self.park.parked_list.len() as u64;
3603 self.park.active = true;
3604 }
3605
3606 fn park_check(&mut self, i: usize, inv_dt: Fix128) -> (bool, bool) {
3608 if !self.islands.is_sleeping(i) || self.park.constrained[i] {
3609 return (false, false);
3610 }
3611 let body = &self.bodies[i];
3612 let radius = self.body_collision_radii.get(i).and_then(|r| *r);
3613 match body.body_type {
3614 BodyType::Kinematic => (false, false),
3615 BodyType::Static => {
3616 if !body.is_static() {
3617 return (false, false);
3618 }
3619 let has_collider = self.body_colliders.get(i).is_some_and(Option::is_some);
3621 let c = ParkCache {
3622 position: body.position,
3623 rotation: body.rotation,
3624 angular_velocity: Vec3Fix::ZERO,
3625 radius,
3626 static_kind: true,
3627 };
3628 match self.park.cache[i] {
3629 Some(old)
3630 if old.static_kind
3631 && old.position == c.position
3632 && old.radius == radius
3633 && (old.rotation == c.rotation || !has_collider) =>
3634 {
3635 (true, false)
3636 }
3637 _ => {
3638 self.park.cache[i] = Some(c);
3639 (true, true)
3640 }
3641 }
3642 }
3643 BodyType::Dynamic => {
3644 if body.prev_position != body.position
3645 || body.prev_rotation != body.rotation
3646 || body.velocity != Vec3Fix::ZERO
3647 {
3648 return (false, false);
3649 }
3650 if let Some(c) = self.park.cache[i] {
3651 if !c.static_kind
3652 && c.position == body.position
3653 && c.rotation == body.rotation
3654 && c.radius == radius
3655 {
3656 return (body.angular_velocity == c.angular_velocity, false);
3657 }
3658 }
3659 let omega = angular_from_rotations(body.rotation, body.rotation, inv_dt);
3660 if body.angular_velocity != omega
3661 || !(omega.length() < self.islands.config.angular_threshold)
3662 || !self.resolution_clear(i)
3663 {
3664 self.park.cache[i] = None;
3665 return (false, false);
3666 }
3667 self.park.cache[i] = Some(ParkCache {
3668 position: body.position,
3669 rotation: body.rotation,
3670 angular_velocity: omega,
3671 radius,
3672 static_kind: false,
3673 });
3674 (true, true)
3675 }
3676 }
3677 }
3678
3679 fn resolution_clear(&self, i: usize) -> bool {
3683 let body = &self.bodies[i];
3684 if body.is_static() || body.is_sensor {
3685 return true; }
3687 let radius = self
3688 .body_collision_radii
3689 .get(i)
3690 .and_then(|r| *r)
3691 .unwrap_or(self.sdf_collision_radius);
3692 for collider in &self.static_colliders {
3693 if collider.collide_sphere(body.position, radius).is_some() {
3694 return false;
3695 }
3696 }
3697 #[cfg(feature = "std")]
3698 {
3699 let collider = self.body_colliders.get(i).and_then(Option::as_ref);
3700 for sdf in &self.sdf_colliders {
3701 if sdf.body_index == i {
3703 continue;
3704 }
3705 if sdf_contact_of(collider, body, self.sdf_collision_radius, sdf).is_some() {
3706 return false;
3707 }
3708 }
3709 }
3710 true
3711 }
3712
3713 #[inline]
3716 fn stage_count(&self) -> usize {
3717 if self.park.active {
3718 self.park.awake.len()
3719 } else {
3720 self.bodies.len()
3721 }
3722 }
3723
3724 #[inline]
3726 fn stage_body(&self, k: usize) -> usize {
3727 if self.park.active {
3728 self.park.awake[k]
3729 } else {
3730 k
3731 }
3732 }
3733
3734 #[cfg(feature = "parallel")]
3736 fn unparked_count(&self) -> u64 {
3737 self.stage_count() as u64
3738 }
3739
3740 #[cfg(feature = "std")]
3751 fn advance_kinematic_targets_for_tgs(&mut self, dt: Fix128) {
3752 for body in &mut self.bodies {
3753 if body.body_type != BodyType::Kinematic {
3754 continue;
3755 }
3756 body.prev_position = body.position;
3757 body.prev_rotation = body.rotation;
3758 if let Some((target_pos, target_rot)) = body.kinematic_target {
3759 body.velocity = (target_pos - body.position) * (Fix128::ONE / dt);
3760 body.position = target_pos;
3761 body.rotation = target_rot;
3762 }
3763 }
3764 }
3765 #[cfg(feature = "std")]
3769 fn tgs_static_corrections(&mut self) {
3770 if !self.static_colliders.is_empty() {
3771 self.resolve_static_collisions(true);
3772 }
3773 }
3774
3775 #[cfg(feature = "std")]
3778 fn jointed_bodies(&self) -> Vec<usize> {
3779 let n = self.bodies.len();
3780 let mut out: Vec<usize> = self
3781 .joints
3782 .iter()
3783 .flat_map(|j| {
3784 let (a, b) = j.bodies();
3785 [a, b]
3786 })
3787 .filter(|&i| i < n)
3788 .collect();
3789 out.sort_unstable();
3790 out.dedup();
3791 out
3792 }
3793
3794 #[cfg(feature = "std")]
3803 fn tgs_project_joints(
3804 &mut self,
3805 tgs_bodies: &mut [crate::solver_tgs_hooks_6dof_oriented::Body6DofOrientedState],
3806 jointed: &[usize],
3807 sub_dt: Fix128,
3808 ) {
3809 use crate::solver_tgs_backend::tgs_to_body;
3810 for &i in jointed {
3811 tgs_to_body(&tgs_bodies[i], &mut self.bodies[i]);
3812 }
3813 let before: Vec<(Vec3Fix, QuatFix)> = jointed
3814 .iter()
3815 .map(|&i| (self.bodies[i].position, self.bodies[i].rotation))
3816 .collect();
3817 self.solve_joints_dispatch(sub_dt);
3818 let inv_dt = Fix128::ONE / sub_dt;
3819 let mut overflow = false;
3820 for (&i, (p0, q0)) in jointed.iter().zip(before) {
3821 let body = &mut self.bodies[i];
3822 if body.is_dynamic() {
3823 if body.position != p0 {
3824 match (body.position - p0).checked_scale(inv_dt) {
3825 Some(dv) => body.velocity = body.velocity + dv,
3826 None => overflow = true,
3827 }
3828 }
3829 if body.rotation != q0 {
3830 body.angular_velocity =
3831 body.angular_velocity + angular_from_rotations(body.rotation, q0, inv_dt);
3832 }
3833 }
3834 let state = &mut tgs_bodies[i];
3835 state.position = [body.position.x, body.position.y, body.position.z];
3836 state.orientation = body.rotation;
3837 state.linear_velocity = [body.velocity.x, body.velocity.y, body.velocity.z];
3838 state.angular_velocity = [
3839 body.angular_velocity.x,
3840 body.angular_velocity.y,
3841 body.angular_velocity.z,
3842 ];
3843 }
3844 if overflow {
3845 self.note_rigid_overflow();
3846 }
3847 }
3848
3849 #[cfg(feature = "std")]
3862 fn step_tgs(&mut self, dt: Fix128, frozen: &mut [bool]) {
3863 use crate::solver_tgs::{build_islands, DistanceRef};
3864 use crate::solver_tgs_backend::{body_to_tgs, contact_to_tgs, tgs_to_body};
3865 use crate::solver_tgs_hooks_6dof_oriented::Pgs6DofOrientedConfig;
3866 use crate::solver_tgs_hooks_6dof_oriented_scoped::solve_oriented_islands_serial;
3867
3868 self.events.begin_frame();
3871
3872 self.islands.resize(self.bodies.len());
3877 self.islands.reset_unions();
3878 for j in &self.joints {
3879 let (a, b) = j.bodies();
3880 if a < self.bodies.len() && b < self.bodies.len() {
3881 self.islands.union(a, b);
3882 }
3883 }
3884
3885 if !self.force_fields.is_empty() {
3888 apply_force_fields(&self.force_fields, &mut self.bodies, dt);
3889 }
3890 self.apply_joint_motors(dt);
3893 self.advance_kinematic_targets_for_tgs(dt);
3894
3895 self.clear_contacts();
3898 self.detect_collisions();
3899
3900 #[cfg(feature = "std")]
3911 if !self.sdf_colliders.is_empty() {
3912 self.resolve_sdf_collisions();
3913 }
3914
3915 self.apply_contact_filters();
3921
3922 let n = self.bodies.len();
3925 let mut tgs_bodies: Vec<_> = self
3926 .bodies
3927 .iter()
3928 .enumerate()
3929 .map(|(i, b)| body_to_tgs(b, i as u64))
3930 .collect();
3931 self.sync_body_stable_ids();
3935 let ids = &self.body_stable_ids;
3936 let id_of = |i: usize| ids.get(i).copied().unwrap_or(u64::MAX);
3939 let mut contact_ordinals = std::collections::BTreeMap::new();
3940 let discarded = &self.contact_discarded;
3948 let mut tgs_contacts: Vec<_> = self
3949 .contact_constraints
3950 .iter()
3951 .enumerate()
3952 .filter_map(|(i, c)| {
3953 let (ia, ib) = (id_of(c.body_a), id_of(c.body_b));
3959 let flipped;
3960 let c = if ia > ib {
3961 flipped = ContactConstraint {
3962 body_a: c.body_b,
3963 body_b: c.body_a,
3964 contact: Contact {
3965 depth: c.contact.depth,
3966 normal: -c.contact.normal,
3967 point_a: c.contact.point_b,
3968 point_b: c.contact.point_a,
3969 },
3970 ..*c
3971 };
3972 &flipped
3973 } else {
3974 c
3975 };
3976 let key = tgs_cache_key(
3977 TGS_KEY_CONTACT,
3978 ia.min(ib),
3979 ia.max(ib),
3980 &mut contact_ordinals,
3981 );
3982 if discarded.get(i).copied().unwrap_or(false) {
3983 return None;
3984 }
3985 Some(contact_to_tgs(c, &self.bodies, key))
3986 })
3987 .collect();
3988 let joint_refs: Vec<DistanceRef<'_>> = self
3989 .distance_constraints
3990 .iter()
3991 .map(|joint| DistanceRef { joint })
3992 .collect();
3993 let mut joint_ordinals = std::collections::BTreeMap::new();
3996 let mut tgs_joints: Vec<crate::solver_tgs_hooks_6dof_oriented::JointOriented> = self
3997 .distance_constraints
3998 .iter()
3999 .map(|j| crate::solver_tgs_hooks_6dof_oriented::JointOriented {
4000 body_a: j.body_a,
4001 body_b: j.body_b,
4002 stable_id: tgs_cache_key(
4003 TGS_KEY_DISTANCE,
4004 id_of(j.body_a),
4005 id_of(j.body_b),
4006 &mut joint_ordinals,
4007 ),
4008 local_anchor_a: [j.local_anchor_a.x, j.local_anchor_a.y, j.local_anchor_a.z],
4009 local_anchor_b: [j.local_anchor_b.x, j.local_anchor_b.y, j.local_anchor_b.z],
4010 target_distance: j.target_distance,
4011 accum: Fix128::ZERO,
4012 })
4013 .collect();
4014
4015 let islands = build_islands(&tgs_bodies, &tgs_contacts, &joint_refs).unwrap_or_default();
4022
4023 let cfg = Pgs6DofOrientedConfig {
4024 gravity: [
4025 self.config.gravity.x,
4026 self.config.gravity.y,
4027 self.config.gravity.z,
4028 ],
4029 ..Pgs6DofOrientedConfig::default()
4030 };
4031 let tgs_cfg = crate::solver_tgs::TgsConfig {
4032 substeps: self.config.substeps.max(1) as u32,
4033 velocity_iters: self.config.iterations.max(1) as u32,
4034 ..crate::solver_tgs::TgsConfig::default()
4035 };
4036 let with_participants = !frozen.is_empty();
4041 if self.joints.is_empty() && !with_participants {
4042 solve_oriented_islands_serial(
4043 &mut tgs_bodies,
4044 &mut tgs_contacts,
4045 &mut tgs_joints,
4046 &islands,
4047 &mut self.tgs_impulse_cache,
4048 cfg,
4049 &tgs_cfg,
4050 dt,
4051 );
4052 } else {
4053 let one_substep = crate::solver_tgs::TgsConfig {
4061 substeps: 1,
4062 ..tgs_cfg
4063 };
4064 let substeps = self.config.substeps;
4071 let sub_dt = dt * Fix128::from_f32(1.0 / tgs_cfg.substeps as f32);
4072 let jointed = self.jointed_bodies();
4073 for s in 0..tgs_cfg.substeps {
4074 let overflow_at_start = if with_participants {
4075 for (body, state) in self.bodies.iter_mut().zip(tgs_bodies.iter()) {
4079 tgs_to_body(state, body);
4080 }
4081 let flag =
4082 self.participants_begin_substep(s as usize, substeps, sub_dt, frozen);
4083 for (state, body) in tgs_bodies.iter_mut().zip(self.bodies.iter()) {
4084 state.linear_velocity = [body.velocity.x, body.velocity.y, body.velocity.z];
4085 state.angular_velocity = [
4086 body.angular_velocity.x,
4087 body.angular_velocity.y,
4088 body.angular_velocity.z,
4089 ];
4090 }
4091 flag
4092 } else {
4093 false
4094 };
4095 solve_oriented_islands_serial(
4096 &mut tgs_bodies,
4097 &mut tgs_contacts,
4098 &mut tgs_joints,
4099 &islands,
4100 &mut self.tgs_impulse_cache,
4101 cfg,
4102 &one_substep,
4103 sub_dt,
4104 );
4105 if !self.joints.is_empty() {
4106 self.tgs_project_joints(&mut tgs_bodies, &jointed, sub_dt);
4107 }
4108 if with_participants {
4109 for (body, state) in self.bodies.iter_mut().zip(tgs_bodies.iter()) {
4115 tgs_to_body(state, body);
4116 }
4117 if tgs_bodies.iter().any(|s| s.overflow) {
4119 self.note_rigid_overflow();
4120 }
4121 self.participants_end_substep(overflow_at_start, frozen);
4122 }
4123 }
4124 }
4125 self.tgs_impulse_cache.sweep();
4134 debug_assert_eq!(tgs_bodies.len(), n);
4135 for (body, state) in self.bodies.iter_mut().zip(tgs_bodies.iter()) {
4136 tgs_to_body(state, body);
4137 }
4138 if tgs_bodies.iter().any(|s| s.overflow) {
4142 self.note_rigid_overflow();
4143 }
4144
4145 self.tgs_static_corrections();
4152
4153 self.apply_frame_damping();
4157
4158 self.islands.update_sleep(&self.bodies);
4160
4161 self.sync_sdf_colliders();
4165
4166 self.events.end_frame();
4168 }
4169
4170 #[cfg(feature = "std")]
4178 #[must_use]
4179 pub fn tgs_cache_stats(&self) -> TgsCacheStats {
4180 self.tgs_impulse_cache.stats().into()
4181 }
4182
4183 #[cfg(feature = "std")]
4189 pub fn reset_tgs_cache_stats(&mut self) {
4190 self.tgs_impulse_cache.reset_stats();
4191 }
4192
4193 #[cfg(feature = "parallel")]
4221 pub fn step_parallel(&mut self, dt: Fix128) {
4222 let _ = self.try_step_parallel(dt);
4223 }
4224
4225 #[cfg(feature = "parallel")]
4228 fn run_step_parallel(&mut self, dt: Fix128) {
4229 self.make_body_rotations_unit();
4230 self.wake_on_sdf_shape_change();
4231 self.begin_sdf_contact_log();
4232 let mut frozen = self.participant_flags();
4233
4234 self.events.begin_frame();
4237
4238 self.islands.resize(self.bodies.len());
4240 self.islands.reset_unions();
4241 for j in &self.joints {
4242 let (a, b) = j.bodies();
4243 if a < self.bodies.len() && b < self.bodies.len() {
4244 self.islands.union(a, b);
4245 }
4246 }
4247
4248 if !self.force_fields.is_empty() {
4250 apply_force_fields(&self.force_fields, &mut self.bodies, dt);
4251 }
4252
4253 let substep_dt = dt / Fix128::from_int(self.config.substeps as i64);
4258 let n = self.config.substeps;
4259 for i in 0..n {
4260 self.kinematic_substeps_left = n - i;
4261 self.sdf_contact_substep = i;
4262 let overflow_at_start = self.participants_begin_substep(i, n, substep_dt, &mut frozen);
4263 self.substep_batched(substep_dt);
4264 self.participants_end_substep(overflow_at_start, &frozen);
4265 }
4266 self.kinematic_substeps_left = 0;
4267 self.sdf_contact_substep = 0;
4268
4269 self.apply_frame_damping();
4271
4272 self.islands.update_sleep(&self.bodies);
4274
4275 self.sync_sdf_colliders();
4279
4280 self.events.end_frame();
4282 }
4283
4284 fn substep(&mut self, dt: Fix128) {
4286 self.apply_joint_motors(dt);
4288 self.integrate_positions(dt);
4289 self.reset_lambdas();
4290
4291 let ccd_hits = if self.ccd.is_enabled() {
4294 self.ccd_sweep()
4295 } else {
4296 Vec::new()
4297 };
4298
4299 self.clear_contacts();
4302 self.detect_collisions();
4303 if !ccd_hits.is_empty() {
4304 self.ccd_add_contacts(&ccd_hits);
4305 }
4306
4307 #[cfg(feature = "std")]
4309 if !self.sdf_colliders.is_empty() {
4310 self.resolve_sdf_collisions();
4311 }
4312 if !self.static_colliders.is_empty() {
4313 self.resolve_static_collisions(false);
4314 }
4315
4316 #[cfg(feature = "std")]
4319 self.apply_contact_filters();
4320 for _ in 0..self.config.iterations {
4321 self.solve_distance_constraints(dt);
4322 self.solve_contact_constraints(dt);
4323 }
4324
4325 self.solve_joints_dispatch(dt);
4329
4330 self.update_velocities(dt);
4331 }
4332
4333 #[cfg(feature = "parallel")]
4335 fn substep_batched(&mut self, dt: Fix128) {
4336 self.apply_joint_motors(dt);
4337 self.integrate_positions(dt);
4338 self.reset_lambdas();
4339
4340 let ccd_hits = if self.ccd.is_enabled() {
4342 self.ccd_sweep()
4343 } else {
4344 Vec::new()
4345 };
4346
4347 self.clear_contacts();
4349 self.detect_collisions();
4350 if !ccd_hits.is_empty() {
4351 self.ccd_add_contacts(&ccd_hits);
4352 }
4353 self.rebuild_batches();
4354
4355 #[cfg(feature = "std")]
4357 if !self.sdf_colliders.is_empty() {
4358 self.resolve_sdf_collisions();
4359 }
4360 if !self.static_colliders.is_empty() {
4361 self.resolve_static_collisions(false);
4362 }
4363
4364 #[cfg(feature = "std")]
4367 self.apply_contact_filters();
4368 for _ in 0..self.config.iterations {
4369 self.solve_constraints_batched(dt);
4370 }
4371
4372 self.solve_joints_dispatch(dt);
4376
4377 self.update_velocities(dt);
4378 }
4379
4380 #[inline]
4398 fn integrate_positions(&mut self, dt: Fix128) {
4399 let overflow = core::sync::atomic::AtomicBool::new(false);
4403 let mut free = core::mem::take(&mut self.free_rotation);
4407 free.clear();
4408 free.resize(self.bodies.len(), None);
4409 let mut predicted = core::mem::take(&mut self.predicted_pose);
4410 predicted.clear();
4411 predicted.resize(self.bodies.len(), None);
4412 #[cfg(feature = "parallel")]
4413 {
4414 let gravity = self.config.gravity;
4415 let left = self.kinematic_substeps_left;
4416 let sleep_data = &self.islands.sleep_data;
4417 let parked: &[bool] = if self.park.active {
4418 &self.park.parked
4419 } else {
4420 &[]
4421 };
4422 self.stage_work.integrated += self.unparked_count();
4423 self.bodies
4424 .par_iter_mut()
4425 .zip(free.par_iter_mut())
4426 .zip(predicted.par_iter_mut())
4427 .enumerate()
4428 .for_each(|(i, ((body, free), predicted))| {
4429 if parked.get(i).copied().unwrap_or(false) {
4430 return;
4431 }
4432 match body.body_type {
4433 BodyType::Static => return,
4434 BodyType::Kinematic => {
4435 body.prev_position = body.position;
4436 body.prev_rotation = body.rotation;
4437 if let Some((target_pos, target_rot)) = body.kinematic_target {
4438 let (p, r) = kinematic_substep_pose(
4439 body.position,
4440 body.rotation,
4441 target_pos,
4442 target_rot,
4443 left,
4444 );
4445 body.velocity = (p - body.position) * (Fix128::ONE / dt);
4446 body.position = p;
4447 body.rotation = r;
4448 }
4449 return;
4450 }
4451 BodyType::Dynamic => {}
4452 }
4453
4454 if sleep_data
4456 .get(i)
4457 .is_some_and(super::sleeping::SleepData::is_sleeping)
4458 {
4459 body.prev_position = body.position;
4460 body.prev_rotation = body.rotation;
4461 return;
4462 }
4463
4464 body.prev_position = body.position;
4466 body.prev_rotation = body.rotation;
4467
4468 body.velocity = body.velocity + gravity * body.gravity_scale * dt;
4470
4471 match body
4481 .velocity
4482 .checked_scale(dt)
4483 .and_then(|d| body.position.checked_add(d))
4484 {
4485 Some(p) => body.position = p,
4486 None => overflow.store(true, core::sync::atomic::Ordering::Relaxed),
4487 }
4488
4489 let mut spin_overflow = false;
4491 *free = predict_rotation(body, dt, &mut spin_overflow);
4492 if spin_overflow {
4493 overflow.store(true, core::sync::atomic::Ordering::Relaxed);
4494 }
4495
4496 *predicted = Some((body.position, body.rotation));
4499 });
4500 self.free_rotation = free;
4501 self.predicted_pose = predicted;
4502 }
4503
4504 #[cfg(not(feature = "parallel"))]
4505 {
4506 let count = self.stage_count();
4507 for k in 0..count {
4508 let i = self.stage_body(k);
4509 self.stage_work.integrated += 1;
4510 match self.bodies[i].body_type {
4511 BodyType::Static => continue,
4512 BodyType::Kinematic => {
4513 self.bodies[i].prev_position = self.bodies[i].position;
4514 self.bodies[i].prev_rotation = self.bodies[i].rotation;
4515 if let Some((target_pos, target_rot)) = self.bodies[i].kinematic_target {
4516 let (p, r) = kinematic_substep_pose(
4517 self.bodies[i].position,
4518 self.bodies[i].rotation,
4519 target_pos,
4520 target_rot,
4521 self.kinematic_substeps_left,
4522 );
4523 self.bodies[i].velocity =
4524 (p - self.bodies[i].position) * (Fix128::ONE / dt);
4525 self.bodies[i].position = p;
4526 self.bodies[i].rotation = r;
4527 }
4528 continue;
4529 }
4530 BodyType::Dynamic => {}
4531 }
4532
4533 if self.islands.is_sleeping(i) {
4535 self.bodies[i].prev_position = self.bodies[i].position;
4536 self.bodies[i].prev_rotation = self.bodies[i].rotation;
4537 continue;
4538 }
4539
4540 self.bodies[i].prev_position = self.bodies[i].position;
4542 self.bodies[i].prev_rotation = self.bodies[i].rotation;
4543
4544 let grav = self.config.gravity * self.bodies[i].gravity_scale * dt;
4546 self.bodies[i].velocity = self.bodies[i].velocity + grav;
4547
4548 let body = &mut self.bodies[i];
4557 match body
4558 .velocity
4559 .checked_scale(dt)
4560 .and_then(|d| body.position.checked_add(d))
4561 {
4562 Some(p) => body.position = p,
4563 None => overflow.store(true, core::sync::atomic::Ordering::Relaxed),
4564 }
4565
4566 let mut spin_overflow = false;
4568 free[i] = predict_rotation(&mut self.bodies[i], dt, &mut spin_overflow);
4569 if spin_overflow {
4570 overflow.store(true, core::sync::atomic::Ordering::Relaxed);
4571 }
4572
4573 predicted[i] = Some((self.bodies[i].position, self.bodies[i].rotation));
4576 }
4577 self.free_rotation = free;
4578 self.predicted_pose = predicted;
4579 }
4580
4581 if overflow.load(core::sync::atomic::Ordering::Relaxed) {
4583 self.note_rigid_overflow();
4584 }
4585 }
4586
4587 fn reset_lambdas(&mut self) {
4602 for c in &mut self.distance_constraints {
4603 c.cached_lambda = Fix128::ZERO;
4604 }
4605 }
4606
4607 fn apply_frame_damping(&mut self) {
4619 let damping = self.config.damping;
4620 let count = self.stage_count();
4621 for k in 0..count {
4622 let i = self.stage_body(k);
4623 self.stage_work.damping_bodies += 1;
4624 if self.bodies[i].body_type != BodyType::Dynamic || self.islands.is_sleeping(i) {
4625 continue;
4626 }
4627 let body = &mut self.bodies[i];
4628 body.velocity = body.velocity * damping * body.linear_damping;
4629 body.angular_velocity = body.angular_velocity * damping * body.angular_damping;
4630 }
4631 }
4632
4633 #[inline]
4636 fn update_velocities(&mut self, dt: Fix128) {
4637 let inv_dt = Fix128::ONE / dt;
4638 let overflow = core::sync::atomic::AtomicBool::new(false);
4642
4643 self.contact_pre_vn.clear();
4648 for constraint in &self.contact_constraints {
4649 let relative =
4650 self.bodies[constraint.body_a].velocity - self.bodies[constraint.body_b].velocity;
4651 self.contact_pre_vn
4652 .push(relative.dot(constraint.contact.normal));
4653 }
4654
4655 #[cfg(feature = "parallel")]
4657 {
4658 let parked: &[bool] = if self.park.active {
4659 &self.park.parked
4660 } else {
4661 &[]
4662 };
4663 self.stage_work.velocity_bodies += self.unparked_count();
4664 let free: &[Option<crate::gyroscopic::FreeRotation>] = &self.free_rotation;
4665 let predicted: &[Option<(Vec3Fix, QuatFix)>] = &self.predicted_pose;
4666 self.bodies
4667 .par_iter_mut()
4668 .enumerate()
4669 .for_each(|(i, body)| {
4670 if body.body_type == BodyType::Static || parked.get(i).copied().unwrap_or(false)
4671 {
4672 return;
4673 }
4674 let pose = predicted.get(i).copied().flatten();
4677 if !derive_linear_velocity(body, pose.map(|(x, _)| x), inv_dt) {
4678 overflow.store(true, core::sync::atomic::Ordering::Relaxed);
4679 }
4680 let f = free.get(i).copied().flatten();
4685 body.angular_velocity =
4686 derived_angular_velocity(body, f, pose.map(|(_, q)| q), inv_dt);
4687 });
4688 }
4689
4690 #[cfg(not(feature = "parallel"))]
4691 {
4692 let count = self.stage_count();
4693 for k in 0..count {
4694 let i = self.stage_body(k);
4695 self.stage_work.velocity_bodies += 1;
4696 let body = &mut self.bodies[i];
4697 if body.body_type == BodyType::Static {
4698 continue;
4699 }
4700 let pose = self.predicted_pose.get(i).copied().flatten();
4703 if !derive_linear_velocity(body, pose.map(|(x, _)| x), inv_dt) {
4704 overflow.store(true, core::sync::atomic::Ordering::Relaxed);
4705 }
4706 let f = self.free_rotation.get(i).copied().flatten();
4709 let body = &mut self.bodies[i];
4710 body.angular_velocity =
4711 derived_angular_velocity(body, f, pose.map(|(_, q)| q), inv_dt);
4712 }
4713 }
4714
4715 let restitution_threshold = self.config.gravity.length() * dt * Fix128::from_int(2);
4721 let num_contacts = self.contact_constraints.len();
4722 for i in 0..num_contacts {
4723 let constraint = self.contact_constraints[i];
4724 let body_a = self.bodies[constraint.body_a];
4725 let body_b = self.bodies[constraint.body_b];
4726
4727 if body_a.is_sensor || body_b.is_sensor {
4728 continue;
4729 }
4730
4731 #[cfg(feature = "std")]
4736 if self.contact_discarded.get(i).copied().unwrap_or(false) {
4737 continue;
4738 }
4739
4740 let w_sum = body_a.inv_mass + body_b.inv_mass;
4741 if w_sum < W_SUM_EPSILON {
4742 continue;
4743 }
4744
4745 let n = constraint.contact.normal;
4746 let relative_vel = body_a.velocity - body_b.velocity;
4747 let vn = relative_vel.dot(n);
4748
4749 let pre_vn = self.contact_pre_vn.get(i).copied().unwrap_or(vn);
4758 let restitution = if pre_vn < -restitution_threshold {
4759 constraint.restitution
4760 } else {
4761 Fix128::ZERO
4762 };
4763 let target = -restitution * pre_vn;
4764 let target = if target.is_negative() {
4765 Fix128::ZERO
4766 } else {
4767 target
4768 };
4769 if vn < target {
4770 let delta_vn = target - vn;
4771 let impulse_n = n * delta_vn;
4772 let inv_w = Fix128::ONE / w_sum;
4773 self.bodies[constraint.body_a].velocity =
4774 self.bodies[constraint.body_a].velocity + impulse_n * (body_a.inv_mass * inv_w);
4775 self.bodies[constraint.body_b].velocity =
4776 self.bodies[constraint.body_b].velocity - impulse_n * (body_b.inv_mass * inv_w);
4777 }
4778
4779 let relative_vel2 =
4781 self.bodies[constraint.body_a].velocity - self.bodies[constraint.body_b].velocity;
4782 let vn2 = relative_vel2.dot(n);
4783 let tangent_vel = relative_vel2 - n * vn2;
4784 let tangent_speed_sq = tangent_vel.length_squared();
4785 if tangent_speed_sq > Fix128::ZERO {
4786 let tangent_speed = tangent_speed_sq.sqrt();
4787 let friction = constraint.friction;
4788 let max_friction_impulse = friction * constraint.cached_lambda * inv_dt;
4799 let applied = if tangent_speed < max_friction_impulse {
4800 tangent_speed
4801 } else {
4802 max_friction_impulse
4803 };
4804 let friction_dir = tangent_vel / tangent_speed;
4805 let friction_impulse = friction_dir * applied;
4806 let inv_w = Fix128::ONE / w_sum;
4807 self.bodies[constraint.body_a].velocity = self.bodies[constraint.body_a].velocity
4808 - friction_impulse * (body_a.inv_mass * inv_w);
4809 self.bodies[constraint.body_b].velocity = self.bodies[constraint.body_b].velocity
4810 + friction_impulse * (body_b.inv_mass * inv_w);
4811 }
4812 }
4813
4814 if overflow.load(core::sync::atomic::Ordering::Relaxed) {
4816 self.note_rigid_overflow();
4817 }
4818 }
4819
4820 #[cfg(feature = "std")]
4840 fn apply_contact_filters(&mut self) {
4841 self.contact_discarded.clear();
4842 if self.pre_solve_hooks.is_empty() && self.contact_modifiers.is_empty() {
4843 return;
4844 }
4845 let num = self.contact_constraints.len();
4846 self.contact_discarded.resize(num, false);
4847 for i in 0..num {
4848 let constraint = self.contact_constraints[i];
4849 if self.bodies[constraint.body_a].is_sensor || self.bodies[constraint.body_b].is_sensor
4850 {
4851 continue;
4852 }
4853
4854 let mut contact = constraint.contact;
4855 let mut friction = constraint.friction;
4856 let mut restitution = constraint.restitution;
4857
4858 let mut skip = false;
4859 for hook in &self.pre_solve_hooks {
4860 if !hook(constraint.body_a, constraint.body_b, &contact) {
4861 skip = true;
4862 break;
4863 }
4864 }
4865 if !skip {
4866 for modifier in &self.contact_modifiers {
4867 if !modifier.modify_contact(
4868 constraint.body_a,
4869 constraint.body_b,
4870 &mut contact,
4871 &mut friction,
4872 &mut restitution,
4873 ) {
4874 skip = true;
4875 break;
4876 }
4877 }
4878 }
4879 if skip {
4880 self.contact_discarded[i] = true;
4881 continue;
4882 }
4883
4884 let slot = &mut self.contact_constraints[i];
4885 slot.contact = contact;
4886 slot.friction = friction;
4887 slot.restitution = restitution;
4888 }
4889 }
4890
4891 #[cfg(feature = "parallel")]
4904 fn solve_constraints_batched(&mut self, dt: Fix128) {
4905 let snapshot_stale = self.batch_static_bodies.len() != self.bodies.len()
4911 || self
4912 .bodies
4913 .iter()
4914 .zip(&self.batch_static_bodies)
4915 .any(|(body, &was_static)| body.inv_mass.is_zero() != was_static);
4916 if snapshot_stale {
4917 self.batches_dirty = true;
4918 self.rebuild_batches();
4919 }
4920
4921 let num_batches = self.constraint_batches.len();
4922
4923 let bodies = BodySlicePtr {
4924 ptr: self.bodies.as_mut_ptr(),
4925 len: self.bodies.len(),
4926 };
4927 let dists = DistConstraintSlicePtr {
4928 ptr: self.distance_constraints.as_mut_ptr(),
4929 len: self.distance_constraints.len(),
4930 };
4931 let static_bodies: &[bool] = &self.batch_static_bodies;
4932 #[cfg(feature = "std")]
4935 let discarded: &[bool] = &self.contact_discarded;
4936 #[cfg(not(feature = "std"))]
4937 let discarded: &[bool] = &[];
4938
4939 for batch_idx in 0..num_batches {
4940 {
4942 let indices = &self.constraint_batches[batch_idx].distance_indices;
4943 indices.par_iter().for_each(|&idx| {
4944 unsafe {
4951 let constraint = dists.get_mut(idx);
4952 let body_a =
4953 bodies.borrow(constraint.body_a, static_bodies[constraint.body_a]);
4954 let body_b =
4955 bodies.borrow(constraint.body_b, static_bodies[constraint.body_b]);
4956 Self::solve_distance_pair(body_a, body_b, constraint, dt);
4957 }
4958 });
4959 }
4960
4961 {
4963 let contacts = ContactConstraintSlicePtr {
4964 ptr: self.contact_constraints.as_mut_ptr(),
4965 len: self.contact_constraints.len(),
4966 };
4967 let indices = &self.constraint_batches[batch_idx].contact_indices;
4968 indices.par_iter().for_each(|&idx| {
4969 if discarded.get(idx).copied().unwrap_or(false) {
4970 return;
4971 }
4972 unsafe {
4976 let constraint = contacts.get_mut(idx);
4977 let body_a =
4978 bodies.borrow(constraint.body_a, static_bodies[constraint.body_a]);
4979 let body_b =
4980 bodies.borrow(constraint.body_b, static_bodies[constraint.body_b]);
4981 Self::solve_contact_pair(body_a, body_b, constraint);
4982 }
4983 });
4984 }
4985 }
4986 }
4987
4988 #[cfg(feature = "parallel")]
4994 #[inline(always)]
4995 fn solve_distance_pair(
4996 mut body_a: BodyRef<'_>,
4997 mut body_b: BodyRef<'_>,
4998 constraint: &mut DistanceConstraint,
4999 dt: Fix128,
5000 ) {
5001 let (pos_a, rot_a, inv_mass_a) = {
5002 let a = body_a.get();
5003 (a.position, a.rotation, a.inv_mass)
5004 };
5005 let (pos_b, rot_b, inv_mass_b) = {
5006 let b = body_b.get();
5007 (b.position, b.rotation, b.inv_mass)
5008 };
5009
5010 let anchor_a = pos_a + rot_a.rotate_vec(constraint.local_anchor_a);
5011 let anchor_b = pos_b + rot_b.rotate_vec(constraint.local_anchor_b);
5012
5013 let delta = anchor_b - anchor_a;
5014 let (normal, distance) = delta.normalize_with_length();
5015
5016 if distance.is_zero() {
5017 return;
5018 }
5019
5020 let error = distance - constraint.target_distance;
5021
5022 let compliance_term = constraint.compliance / (dt * dt);
5023 let w_sum = inv_mass_a + inv_mass_b + compliance_term;
5024
5025 if w_sum < W_SUM_EPSILON {
5026 return;
5027 }
5028
5029 let inv_w_sum = Fix128::ONE / w_sum;
5037 let lambda = constraint.cached_lambda;
5038 let dlambda = (error - lambda * compliance_term) * inv_w_sum;
5039 constraint.cached_lambda = lambda + dlambda;
5040 let correction = normal * dlambda;
5041
5042 let delta_a = correction * inv_mass_a;
5046 let delta_b = correction * inv_mass_b;
5047 body_a.set_position(select_vec3(!inv_mass_a.is_zero(), pos_a + delta_a, pos_a));
5048 body_b.set_position(select_vec3(!inv_mass_b.is_zero(), pos_b - delta_b, pos_b));
5049 }
5050
5051 #[cfg(feature = "parallel")]
5057 #[inline(always)]
5058 fn solve_contact_pair(
5059 mut body_a: BodyRef<'_>,
5060 mut body_b: BodyRef<'_>,
5061 constraint: &mut ContactConstraint,
5062 ) {
5063 let (pos_a, prev_a, inv_mass_a, sensor_a) = {
5064 let a = body_a.get();
5065 (a.position, a.prev_position, a.inv_mass, a.is_sensor)
5066 };
5067 let (pos_b, prev_b, inv_mass_b, sensor_b) = {
5068 let b = body_b.get();
5069 (b.position, b.prev_position, b.inv_mass, b.is_sensor)
5070 };
5071
5072 if sensor_a || sensor_b {
5074 return;
5075 }
5076
5077 let contact = constraint.contact;
5078
5079 if contact.depth <= Fix128::ZERO {
5080 return;
5081 }
5082
5083 let w_sum = inv_mass_a + inv_mass_b;
5084 if w_sum < W_SUM_EPSILON {
5085 return;
5086 }
5087
5088 let inv_w_sum = Fix128::ONE / w_sum;
5096 let lambda = constraint.cached_lambda;
5097 let dlambda = contact.depth - lambda;
5098 let (mut pos_a, mut pos_b) = (pos_a, pos_b);
5099 if dlambda > Fix128::ZERO {
5100 constraint.cached_lambda = lambda + dlambda;
5101
5102 let correction = contact.normal * dlambda;
5103 let correction_a = correction * (inv_mass_a * inv_w_sum);
5104 let correction_b = correction * (inv_mass_b * inv_w_sum);
5105
5106 pos_a = select_vec3(!inv_mass_a.is_zero(), pos_a + correction_a, pos_a);
5108 pos_b = select_vec3(!inv_mass_b.is_zero(), pos_b - correction_b, pos_b);
5109 }
5110
5111 if let Some((fa, fb)) = static_friction_correction(
5114 pos_a - prev_a,
5115 pos_b - prev_b,
5116 contact.normal,
5117 inv_mass_a,
5118 inv_mass_b,
5119 inv_w_sum,
5120 constraint.friction,
5121 constraint.cached_lambda,
5122 ) {
5123 pos_a = select_vec3(!inv_mass_a.is_zero(), pos_a - fa, pos_a);
5124 pos_b = select_vec3(!inv_mass_b.is_zero(), pos_b + fb, pos_b);
5125 }
5126
5127 body_a.set_position(pos_a);
5129 body_b.set_position(pos_b);
5130 }
5131
5132 #[inline(always)]
5137 fn solve_distance_constraints(&mut self, dt: Fix128) {
5138 let num_constraints = self.distance_constraints.len();
5139 for i in 0..num_constraints {
5140 let constraint = self.distance_constraints[i];
5141 let body_a = self.bodies[constraint.body_a];
5142 let body_b = self.bodies[constraint.body_b];
5143
5144 let anchor_a = body_a.position + body_a.rotation.rotate_vec(constraint.local_anchor_a);
5146 let anchor_b = body_b.position + body_b.rotation.rotate_vec(constraint.local_anchor_b);
5147
5148 let delta = anchor_b - anchor_a;
5150 let (normal, distance) = delta.normalize_with_length();
5151
5152 if distance.is_zero() {
5153 continue;
5154 }
5155
5156 let error = distance - constraint.target_distance;
5157
5158 let compliance_term = constraint.compliance / (dt * dt);
5160 let w_sum = body_a.inv_mass + body_b.inv_mass + compliance_term;
5161
5162 if w_sum < W_SUM_EPSILON {
5163 continue;
5164 }
5165
5166 let inv_w_sum = Fix128::ONE / w_sum;
5169 let lambda = constraint.cached_lambda;
5170 let dlambda = (error - lambda * compliance_term) * inv_w_sum;
5171 self.distance_constraints[i].cached_lambda = lambda + dlambda;
5172 let correction = normal * dlambda;
5173
5174 let delta_a = correction * body_a.inv_mass;
5176 let delta_b = correction * body_b.inv_mass;
5177 self.bodies[constraint.body_a].position = select_vec3(
5178 !body_a.inv_mass.is_zero(),
5179 self.bodies[constraint.body_a].position + delta_a,
5180 self.bodies[constraint.body_a].position,
5181 );
5182 self.bodies[constraint.body_b].position = select_vec3(
5183 !body_b.inv_mass.is_zero(),
5184 self.bodies[constraint.body_b].position - delta_b,
5185 self.bodies[constraint.body_b].position,
5186 );
5187 }
5188 }
5189
5190 fn solve_contact_constraints(&mut self, _dt: Fix128) {
5216 #[cfg(feature = "gpu-solver-bridge")]
5217 if let Some(mut bridge) = self.gpu_solver_bridge.take() {
5218 self.solve_contact_pass_with_bridge(bridge.as_mut());
5219 self.gpu_solver_bridge = Some(bridge);
5220 return;
5221 }
5222 let num_constraints = self.contact_constraints.len();
5223 for i in 0..num_constraints {
5224 let constraint = self.contact_constraints[i];
5225 let body_a = self.bodies[constraint.body_a];
5226 let body_b = self.bodies[constraint.body_b];
5227
5228 if body_a.is_sensor || body_b.is_sensor {
5230 continue;
5231 }
5232
5233 #[cfg(feature = "std")]
5235 if self.contact_discarded.get(i).copied().unwrap_or(false) {
5236 continue;
5237 }
5238
5239 let contact = constraint.contact;
5242
5243 if contact.depth <= Fix128::ZERO {
5245 continue;
5246 }
5247
5248 let w_sum = body_a.inv_mass + body_b.inv_mass;
5249 if w_sum < W_SUM_EPSILON {
5250 continue;
5251 }
5252
5253 let inv_w_sum = Fix128::ONE / w_sum;
5255 let lambda = constraint.cached_lambda;
5256 let dlambda = contact.depth - lambda;
5257 if dlambda > Fix128::ZERO {
5258 self.contact_constraints[i].cached_lambda = lambda + dlambda;
5259
5260 let correction = contact.normal * dlambda;
5261 let correction_a = correction * (body_a.inv_mass * inv_w_sum);
5262 let correction_b = correction * (body_b.inv_mass * inv_w_sum);
5263
5264 self.bodies[constraint.body_a].position = select_vec3(
5266 !body_a.inv_mass.is_zero(),
5267 self.bodies[constraint.body_a].position + correction_a,
5268 self.bodies[constraint.body_a].position,
5269 );
5270 self.bodies[constraint.body_b].position = select_vec3(
5271 !body_b.inv_mass.is_zero(),
5272 self.bodies[constraint.body_b].position - correction_b,
5273 self.bodies[constraint.body_b].position,
5274 );
5275 }
5276
5277 self.apply_static_friction(i, inv_w_sum);
5280 }
5281 }
5282
5283 fn apply_static_friction(&mut self, i: usize, inv_w_sum: Fix128) {
5289 let constraint = self.contact_constraints[i];
5290 let a = self.bodies[constraint.body_a];
5291 let b = self.bodies[constraint.body_b];
5292 if let Some((fa, fb)) = static_friction_correction(
5293 a.position - a.prev_position,
5294 b.position - b.prev_position,
5295 constraint.contact.normal,
5296 a.inv_mass,
5297 b.inv_mass,
5298 inv_w_sum,
5299 constraint.friction,
5300 constraint.cached_lambda,
5301 ) {
5302 self.bodies[constraint.body_a].position =
5303 select_vec3(!a.inv_mass.is_zero(), a.position - fa, a.position);
5304 self.bodies[constraint.body_b].position =
5305 select_vec3(!b.inv_mass.is_zero(), b.position + fb, b.position);
5306 }
5307 }
5308
5309 #[cfg(feature = "gpu-solver-bridge")]
5359 pub fn solve_contact_constraints_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5360 &mut self,
5361 bridge: &mut B,
5362 ) {
5363 self.apply_contact_filters();
5364 self.solve_contact_pass_with_bridge(bridge);
5365 }
5366
5367 #[cfg(feature = "gpu-solver-bridge")]
5372 fn solve_contact_pass_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5373 &mut self,
5374 bridge: &mut B,
5375 ) {
5376 let num_constraints = self.contact_constraints.len();
5378 let mut filtered: Vec<ContactConstraint> = Vec::with_capacity(num_constraints);
5379 let mut mapping: Vec<usize> = Vec::with_capacity(num_constraints);
5380
5381 for i in 0..num_constraints {
5382 let constraint = self.contact_constraints[i];
5383 let body_a = self.bodies[constraint.body_a];
5384 let body_b = self.bodies[constraint.body_b];
5385
5386 if body_a.is_sensor || body_b.is_sensor {
5387 continue;
5388 }
5389 if self.contact_discarded.get(i).copied().unwrap_or(false) {
5390 continue;
5391 }
5392
5393 let contact = constraint.contact;
5394 let friction = constraint.friction;
5395 let restitution = constraint.restitution;
5396
5397 filtered.push(ContactConstraint {
5398 body_a: constraint.body_a,
5399 body_b: constraint.body_b,
5400 contact,
5401 friction,
5402 restitution,
5403 cached_lambda: constraint.cached_lambda,
5404 });
5405 mapping.push(i);
5406 }
5407
5408 if filtered.is_empty() {
5409 return;
5410 }
5411
5412 let mut positions: Vec<[Fix128; 3]> = Vec::with_capacity(self.bodies.len());
5414 let mut inv_masses: Vec<Fix128> = Vec::with_capacity(self.bodies.len());
5415 for body in &self.bodies {
5416 positions.push([body.position.x, body.position.y, body.position.z]);
5417 inv_masses.push(body.inv_mass);
5418 }
5419
5420 bridge.send_contact_constraints(&filtered);
5422 bridge.send_body_state(&positions, &inv_masses);
5423 bridge.dispatch_contact_solve_iteration(self.config.warm_start_factor);
5424
5425 let mut updated_constraints = filtered;
5426 let mut updated_positions = positions;
5427 bridge.recv_contact_constraints(&mut updated_constraints);
5428 bridge.recv_body_positions(&mut updated_positions);
5429
5430 for (filtered_idx, orig_slot) in mapping.iter().enumerate() {
5432 self.contact_constraints[*orig_slot].cached_lambda =
5433 updated_constraints[filtered_idx].cached_lambda;
5434 }
5435 for (i, pos) in updated_positions.iter().enumerate() {
5436 self.bodies[i].position = Vec3Fix::new(pos[0], pos[1], pos[2]);
5437 }
5438
5439 for &orig_slot in &mapping {
5443 let c = self.contact_constraints[orig_slot];
5444 let w_sum = self.bodies[c.body_a].inv_mass + self.bodies[c.body_b].inv_mass;
5445 if c.contact.depth <= Fix128::ZERO || w_sum < W_SUM_EPSILON {
5446 continue;
5447 }
5448 self.apply_static_friction(orig_slot, Fix128::ONE / w_sum);
5449 }
5450 }
5451
5452 #[cfg(feature = "gpu-solver-bridge")]
5473 pub fn step_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5474 &mut self,
5475 bridge: &mut B,
5476 dt: Fix128,
5477 ) {
5478 let h = Self::divided_substep_width(dt, self.config.substeps);
5484 if !matches!(self.check_step(dt, h), Ok(true)) {
5485 return;
5486 }
5487 self.make_body_rotations_unit();
5488 self.wake_on_sdf_shape_change();
5489 self.begin_sdf_contact_log();
5490 let mut frozen = self.participant_flags();
5491
5492 self.events.begin_frame();
5495
5496 self.islands.resize(self.bodies.len());
5498 self.islands.reset_unions();
5499 for j in &self.joints {
5500 let (a, b) = j.bodies();
5501 if a < self.bodies.len() && b < self.bodies.len() {
5502 self.islands.union(a, b);
5503 }
5504 }
5505
5506 if !self.force_fields.is_empty() {
5508 apply_force_fields(&self.force_fields, &mut self.bodies, dt);
5509 }
5510
5511 let substep_dt = dt / Fix128::from_int(self.config.substeps as i64);
5515 let n = self.config.substeps;
5516 for i in 0..n {
5517 self.kinematic_substeps_left = n - i;
5518 self.sdf_contact_substep = i;
5519 let overflow_at_start = self.participants_begin_substep(i, n, substep_dt, &mut frozen);
5520 self.substep_with_bridge(bridge, substep_dt);
5521 self.participants_end_substep(overflow_at_start, &frozen);
5522 }
5523 self.kinematic_substeps_left = 0;
5524 self.sdf_contact_substep = 0;
5525
5526 self.apply_frame_damping();
5528
5529 self.islands.update_sleep(&self.bodies);
5531
5532 self.sync_sdf_colliders();
5536
5537 self.events.end_frame();
5539 }
5540
5541 #[cfg(feature = "gpu-solver-bridge")]
5568 pub fn substep_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5569 &mut self,
5570 bridge: &mut B,
5571 dt: Fix128,
5572 ) {
5573 self.make_body_rotations_unit();
5576 self.apply_joint_motors(dt);
5577 self.integrate_positions(dt);
5578 self.reset_lambdas();
5579
5580 self.clear_contacts();
5582 self.detect_collisions();
5583
5584 if !self.sdf_colliders.is_empty() {
5586 self.resolve_sdf_collisions();
5587 }
5588 if !self.static_colliders.is_empty() {
5589 self.resolve_static_collisions(false);
5590 }
5591
5592 for iteration in 0..self.config.iterations {
5597 self.solve_distance_constraints(dt);
5598 if iteration == 0 {
5599 self.solve_contact_constraints_with_bridge(bridge);
5600 } else {
5601 self.solve_contact_pass_with_bridge(bridge);
5602 }
5603 }
5604
5605 let forces = if self.joints.is_empty() {
5609 None
5610 } else {
5611 self.bridge_joint_reaction_forces(dt)
5612 };
5613 self.solve_joints_with_bridge(bridge, dt);
5614 if let Some(forces) = forces {
5615 self.break_overloaded_joints(&forces);
5616 }
5617
5618 self.update_velocities(dt);
5619 }
5620
5621 #[cfg(feature = "gpu-solver-bridge")]
5655 pub fn solve_joints_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5656 &mut self,
5657 bridge: &mut B,
5658 dt: Fix128,
5659 ) {
5660 let joints: Vec<crate::joint::Joint> = self
5665 .joints
5666 .iter()
5667 .filter(|j| {
5668 let (a, b) = j.bodies();
5669 a != b
5670 })
5671 .copied()
5672 .collect();
5673 if joints.is_empty() {
5674 return;
5675 }
5676
5677 let mut positions: Vec<[Fix128; 3]> = Vec::with_capacity(self.bodies.len());
5679 let mut inv_masses: Vec<Fix128> = Vec::with_capacity(self.bodies.len());
5680 let mut rotations: Vec<[Fix128; 4]> = Vec::with_capacity(self.bodies.len());
5681 for body in &self.bodies {
5682 positions.push([body.position.x, body.position.y, body.position.z]);
5683 inv_masses.push(body.inv_mass);
5684 rotations.push([
5685 body.rotation.x,
5686 body.rotation.y,
5687 body.rotation.z,
5688 body.rotation.w,
5689 ]);
5690 }
5691
5692 bridge.send_joints(&joints);
5694 bridge.send_body_state(&positions, &inv_masses);
5695 bridge.send_body_rotations(&rotations);
5696 bridge.dispatch_joint_solve_iteration(dt);
5697
5698 let mut updated_positions = positions;
5699 bridge.recv_body_positions(&mut updated_positions);
5700
5701 for (i, pos) in updated_positions.iter().enumerate() {
5703 self.bodies[i].position = crate::math::Vec3Fix::new(pos[0], pos[1], pos[2]);
5704 }
5705 }
5706
5707 fn solve_joints_dispatch(&mut self, dt: Fix128) {
5713 if self.joints.is_empty() {
5714 return;
5715 }
5716 #[cfg(feature = "gpu-solver-bridge")]
5717 if let Some(mut bridge) = self.gpu_solver_bridge.take() {
5718 let forces = self.bridge_joint_reaction_forces(dt);
5719 self.solve_joints_with_bridge(bridge.as_mut(), dt);
5720 self.gpu_solver_bridge = Some(bridge);
5721 if let Some(forces) = forces {
5722 self.break_overloaded_joints(&forces);
5723 }
5724 return;
5725 }
5726 if self.joints.iter().any(|j| j.break_force().is_some()) {
5727 let forces =
5728 crate::joint::solve_joints_with_reaction_forces(&self.joints, &mut self.bodies, dt);
5729 self.break_overloaded_joints(&forces);
5730 } else {
5731 solve_joints(&self.joints, &mut self.bodies, dt);
5732 }
5733 }
5734
5735 #[cfg(feature = "gpu-solver-bridge")]
5742 fn bridge_joint_reaction_forces(&self, dt: Fix128) -> Option<Vec<Fix128>> {
5743 if !self.joints.iter().any(|j| j.break_force().is_some()) {
5744 return None;
5745 }
5746 let mut copy = self.bodies.clone();
5747 Some(crate::joint::solve_joints_with_reaction_forces(
5748 &self.joints,
5749 &mut copy,
5750 dt,
5751 ))
5752 }
5753
5754 fn break_overloaded_joints(&mut self, forces: &[Fix128]) {
5779 let overloaded: Vec<(usize, Fix128)> = self
5780 .joints
5781 .iter()
5782 .zip(forces)
5783 .enumerate()
5784 .filter(|(_, (joint, force))| joint.break_force().is_some_and(|max| **force > max))
5785 .map(|(i, (_, force))| (i, *force))
5786 .collect();
5787 for &(index, force) in overloaded.iter().rev() {
5788 if let Some(joint) = self.remove_joint(index) {
5789 self.events
5790 .report_joint_break(crate::event::JointBreakEvent {
5791 index,
5792 joint,
5793 force,
5794 });
5795 }
5796 }
5797 if !overloaded.is_empty() {
5798 self.islands.resize(self.bodies.len());
5799 self.islands.reset_unions();
5800 for j in &self.joints {
5801 let (a, b) = j.bodies();
5802 if a < self.bodies.len() && b < self.bodies.len() {
5803 self.islands.union(a, b);
5804 }
5805 }
5806 }
5807 }
5808
5809 pub fn add_sdf_collider(&mut self, mut collider: SdfCollider) -> usize {
5821 collider.update_cache();
5822 collider.sync_to_body(&self.bodies);
5823 let idx = self.sdf_colliders.len();
5824 self.sdf_colliders.push(collider);
5825 self.sync_sdf_generations();
5826 idx
5827 }
5828
5829 fn sync_sdf_generations(&mut self) {
5834 let n = self.sdf_colliders.len();
5835 self.sdf_generations.truncate(n);
5836 for c in &self.sdf_colliders[self.sdf_generations.len()..] {
5837 self.sdf_generations.push(c.field.generation());
5838 }
5839 }
5840
5841 fn wake_on_sdf_shape_change(&mut self) {
5849 self.sync_sdf_generations();
5850 let mut changed = false;
5851 for (seen, c) in self.sdf_generations.iter_mut().zip(&self.sdf_colliders) {
5852 let now = c.field.generation();
5853 if *seen != now {
5854 *seen = now;
5855 changed = true;
5856 }
5857 }
5858 if changed {
5859 for i in 0..self.bodies.len() {
5864 if self.islands.is_sleeping(i) {
5865 self.wake_body(i);
5866 }
5867 }
5868 }
5869 }
5870
5871 fn begin_sdf_contact_log(&mut self) {
5874 self.sdf_contact_log.clear();
5875 self.sdf_contact_substep = 0;
5876 }
5877
5878 #[must_use]
5903 pub fn last_step_sdf_contacts(&self) -> &[crate::sdf_collider::SdfContact] {
5904 &self.sdf_contact_log
5905 }
5906
5907 fn sync_sdf_colliders(&mut self) {
5911 crate::sdf_collider::sync_dynamic_sdf_colliders(&mut self.sdf_colliders, &self.bodies);
5912 }
5913
5914 pub fn remove_sdf_collider(&mut self, idx: usize) -> Option<SdfCollider> {
5916 if idx < self.sdf_colliders.len() {
5917 if idx < self.sdf_generations.len() {
5918 self.sdf_generations.remove(idx);
5919 }
5920 Some(self.sdf_colliders.remove(idx))
5921 } else {
5922 None
5923 }
5924 }
5925
5926 pub fn set_sdf_collision_radius(&mut self, radius: Fix128) {
5928 self.sdf_collision_radius = radius;
5929 }
5930
5931 #[cfg(feature = "std")]
5938 fn resolve_sdf_collisions(&mut self) {
5939 self.sync_sdf_colliders();
5942 let sdf_colliders = &self.sdf_colliders;
5943 let colliders = &self.body_colliders;
5944 let collision_radius = self.sdf_collision_radius;
5945 let substep = self.sdf_contact_substep;
5946
5947 let push_out =
5950 |idx: usize, body: &mut RigidBody, log: &mut Vec<crate::sdf_collider::SdfContact>| {
5951 if body.is_static() || body.is_sensor {
5952 return;
5953 }
5954 let collider = colliders.get(idx).and_then(Option::as_ref);
5955 for (collider_index, sdf) in sdf_colliders.iter().enumerate() {
5956 if sdf.body_index == idx {
5958 continue;
5959 }
5960 if let Some(contact) = sdf_contact_of(collider, body, collision_radius, sdf) {
5961 log.push(crate::sdf_collider::SdfContact {
5962 body_index: idx,
5963 collider_index,
5964 point: contact.point_b,
5965 normal: contact.normal,
5966 depth: contact.depth,
5967 approach_speed: -body.velocity.dot(contact.normal),
5968 substep,
5969 collider_position: sdf.position,
5970 collider_rotation: sdf.rotation,
5971 collider_scale: sdf.scale,
5972 });
5973 body.position = body.position + contact.normal * contact.depth;
5974 }
5975 }
5976 };
5977
5978 #[cfg(feature = "parallel")]
5979 {
5980 let parked: &[bool] = if self.park.active {
5981 &self.park.parked
5982 } else {
5983 &[]
5984 };
5985 self.stage_work.resolution_bodies += self.unparked_count();
5986 let scratch = &mut self.sdf_contact_scratch;
5987 if scratch.len() < self.bodies.len() {
5988 scratch.resize_with(self.bodies.len(), Vec::new);
5989 }
5990 self.bodies
5991 .par_iter_mut()
5992 .zip(scratch.par_iter_mut())
5993 .enumerate()
5994 .for_each(|(idx, (body, out))| {
5995 out.clear();
5996 if !parked.get(idx).copied().unwrap_or(false) {
5997 push_out(idx, body, out);
5998 }
5999 });
6000 let log = &mut self.sdf_contact_log;
6001 for out in &scratch[..self.bodies.len()] {
6002 log.extend_from_slice(out);
6003 }
6004 }
6005
6006 #[cfg(not(feature = "parallel"))]
6007 {
6008 let count = if self.park.active {
6009 self.park.awake.len()
6010 } else {
6011 self.bodies.len()
6012 };
6013 for k in 0..count {
6014 let idx = if self.park.active {
6015 self.park.awake[k]
6016 } else {
6017 k
6018 };
6019 self.stage_work.resolution_bodies += 1;
6020 push_out(idx, &mut self.bodies[idx], &mut self.sdf_contact_log);
6021 }
6022 }
6023 }
6024
6025 #[cfg(feature = "std")]
6035 #[must_use]
6036 pub fn sdf_contacts(&self) -> Vec<(usize, crate::collider::Contact)> {
6037 let mut contacts = Vec::new();
6038 for (idx, body) in self.bodies.iter().enumerate() {
6039 if body.is_static() || body.is_sensor {
6040 continue;
6041 }
6042 let collider = self.body_colliders.get(idx).and_then(Option::as_ref);
6043 for sdf in &self.sdf_colliders {
6044 if sdf.body_index == idx {
6045 continue;
6046 }
6047 if let Some(c) = sdf_contact_of(collider, body, self.sdf_collision_radius, sdf) {
6048 contacts.push((idx, c));
6049 }
6050 }
6051 }
6052 contacts
6053 }
6054
6055 #[must_use]
6068 pub fn contact_forces(&self, dt: Fix128) -> Vec<(Vec3Fix, Vec3Fix, Fix128)> {
6069 let h2 = self.substep_dt(dt) * self.substep_dt(dt);
6070 self.contact_constraints
6071 .iter()
6072 .filter_map(|c| self.contact_force(c, h2))
6073 .collect()
6074 }
6075
6076 fn substep_dt(&self, dt: Fix128) -> Fix128 {
6078 dt / Fix128::from_int(self.config.substeps.max(1) as i64)
6079 }
6080
6081 fn contact_force(
6083 &self,
6084 c: &ContactConstraint,
6085 h2: Fix128,
6086 ) -> Option<(Vec3Fix, Vec3Fix, Fix128)> {
6087 let w = self.bodies.get(c.body_a)?.inv_mass + self.bodies.get(c.body_b)?.inv_mass;
6088 if w < W_SUM_EPSILON || h2.is_zero() {
6089 return None;
6090 }
6091 Some((
6092 c.contact.point_a,
6093 c.contact.normal,
6094 c.cached_lambda / (w * h2),
6095 ))
6096 }
6097
6098 #[must_use]
6102 pub fn contact_arrows(&self, dt: Fix128) -> Vec<crate::contact_viz::ContactArrow> {
6103 crate::contact_viz::generate_contact_arrows(&self.contact_forces(dt))
6104 }
6105
6106 #[must_use]
6110 pub fn contact_friction_arrows(&self, dt: Fix128) -> Vec<crate::contact_viz::ContactArrow> {
6111 let h2 = self.substep_dt(dt) * self.substep_dt(dt);
6112 self.contact_constraints
6113 .iter()
6114 .filter_map(|c| Some((self.contact_force(c, h2)?, c.friction)))
6115 .flat_map(|(force, mu)| crate::contact_viz::generate_friction_arrows(&[force], mu))
6116 .collect()
6117 }
6118
6119 #[must_use]
6122 pub fn contact_friction_cones(&self, dt: Fix128) -> Vec<crate::contact_viz::FrictionCone> {
6123 let h2 = self.substep_dt(dt) * self.substep_dt(dt);
6124 self.contact_constraints
6125 .iter()
6126 .filter_map(|c| Some((self.contact_force(c, h2)?, c.friction)))
6127 .flat_map(|(force, mu)| crate::contact_viz::generate_friction_cones(&[force], mu))
6128 .collect()
6129 }
6130
6131 pub fn add_static_collider(
6134 &mut self,
6135 collider: crate::static_collider::StaticCollider,
6136 ) -> usize {
6137 let idx = self.static_colliders.len();
6138 self.park_generation = self.park_generation.wrapping_add(1);
6139 self.static_colliders.push(collider);
6140 idx
6141 }
6142
6143 pub fn remove_static_collider(
6146 &mut self,
6147 idx: usize,
6148 ) -> Option<crate::static_collider::StaticCollider> {
6149 if idx < self.static_colliders.len() {
6150 self.park_generation = self.park_generation.wrapping_add(1);
6151 Some(self.static_colliders.remove(idx))
6152 } else {
6153 None
6154 }
6155 }
6156
6157 #[must_use]
6159 pub fn static_collider_count(&self) -> usize {
6160 self.static_colliders.len()
6161 }
6162
6163 fn resolve_static_collisions(&mut self, remove_approach: bool) {
6176 let default_radius = self.sdf_collision_radius;
6177 let count = self.stage_count();
6178 for k in 0..count {
6179 let i = self.stage_body(k);
6180 self.stage_work.resolution_bodies += 1;
6181 let body = &mut self.bodies[i];
6182 if body.is_static() || body.is_sensor {
6183 continue;
6184 }
6185 let radius = self
6186 .body_collision_radii
6187 .get(i)
6188 .and_then(|r| *r)
6189 .unwrap_or(default_radius);
6190 for collider in &self.static_colliders {
6191 if let Some(contact) = collider.collide_sphere(body.position, radius) {
6192 body.position = body.position + contact.normal * contact.depth;
6193 if remove_approach {
6194 let vn = body.velocity.dot(contact.normal);
6195 if vn < Fix128::ZERO {
6196 body.velocity = body.velocity - contact.normal * vn;
6197 }
6198 }
6199 }
6200 }
6201 }
6202 }
6203
6204 pub fn set_broadphase(&mut self, kind: Broadphase) {
6209 self.park_generation = self.park_generation.wrapping_add(1);
6210 self.broadphase = kind;
6211 self.broadphase_reset();
6212 }
6213
6214 #[must_use]
6216 pub fn broadphase(&self) -> Broadphase {
6217 self.broadphase
6218 }
6219
6220 #[must_use]
6224 pub fn broadphase_stats(&self) -> BroadphaseStats {
6225 BroadphaseStats {
6226 proxies: self.broadphase_tree.proxy_count(),
6227 height: self.broadphase_tree.height(),
6228 }
6229 }
6230
6231 #[must_use]
6234 pub fn broadphase_proxy_aabb(&self, body_idx: usize) -> Option<AABB> {
6235 let proxy = (*self.broadphase_proxies.get(body_idx)?)?;
6236 if self.broadphase_tree.user_data(proxy) as usize != body_idx {
6237 return None;
6238 }
6239 Some(self.broadphase_tree.get_aabb(proxy))
6240 }
6241
6242 fn broadphase_reset(&mut self) {
6246 self.broadphase_tree = crate::dynamic_bvh::DynamicAabbTree::new();
6247 self.broadphase_proxies.clear();
6248 self.broadphase_hybrid = BroadphaseHybrid::new();
6249 }
6250
6251 fn broadphase_box(&self, i: usize, radius: Fix128) -> AABB {
6260 let pos = self.bodies[i].position;
6261 let cube = AABB::from_center_half(pos, Vec3Fix::new(radius, radius, radius));
6262 match self.body_colliders.get(i).and_then(Option::as_ref) {
6263 Some(collider) => {
6264 let tight = collider.world_aabb(pos, self.bodies[i].rotation);
6265 let max = |x: Fix128, y: Fix128| if x > y { x } else { y };
6266 let min = |x: Fix128, y: Fix128| if x < y { x } else { y };
6267 AABB {
6268 min: Vec3Fix::new(
6269 max(tight.min.x, cube.min.x),
6270 max(tight.min.y, cube.min.y),
6271 max(tight.min.z, cube.min.z),
6272 ),
6273 max: Vec3Fix::new(
6274 min(tight.max.x, cube.max.x),
6275 min(tight.max.y, cube.max.y),
6276 min(tight.max.z, cube.max.z),
6277 ),
6278 }
6279 }
6280 None => cube,
6281 }
6282 }
6283
6284 fn broadphase_pairs(&mut self, primitives: Vec<BvhPrimitive>) -> Vec<(u32, u32)> {
6287 match self.broadphase {
6288 Broadphase::Bvh => LinearBvh::build(primitives).find_pairs(),
6289 Broadphase::DynamicTree => {
6290 let n = self.bodies.len();
6291 for proxy in self
6296 .broadphase_proxies
6297 .drain(n.min(self.broadphase_proxies.len())..)
6298 .flatten()
6299 {
6300 self.broadphase_tree.remove(proxy);
6301 }
6302 self.broadphase_proxies.resize(n, None);
6303 let mut present = vec![false; n];
6304 for p in &primitives {
6305 let i = p.index as usize;
6306 present[i] = true;
6307 match self.broadphase_proxies[i] {
6308 Some(proxy) => {
6309 self.broadphase_tree.update(proxy, p.aabb);
6310 }
6311 None => {
6312 self.broadphase_proxies[i] =
6313 Some(self.broadphase_tree.insert(p.aabb, p.index));
6314 }
6315 }
6316 }
6317 for (i, here) in present.iter().enumerate() {
6319 if !here {
6320 if let Some(proxy) = self.broadphase_proxies[i].take() {
6321 self.broadphase_tree.remove(proxy);
6322 }
6323 }
6324 }
6325 self.broadphase_tree.find_pairs()
6326 }
6327 Broadphase::Hybrid => {
6328 let hybrid = &mut self.broadphase_hybrid;
6329 hybrid.clear_dynamic();
6330 for p in &primitives {
6331 let is_static = self.bodies[p.index as usize].is_static();
6332 hybrid.insert_dynamic(p.index, p.aabb, is_static);
6333 }
6334 hybrid.build_dynamic();
6335 let mut pairs = Vec::new();
6336 self.stage_work.broadphase_box_tests += hybrid.query_pairs(&mut pairs);
6337 pairs
6338 }
6339 }
6340 }
6341
6342 fn parked_broadphase_pairs(&mut self, primitives: Vec<BvhPrimitive>) -> Vec<(u32, u32)> {
6349 let mut pairs = Vec::new();
6350 for p in &primitives {
6351 let i = p.index;
6352 self.park.tree.query_callback(&p.aabb, |j| {
6353 pairs.push(if i < j { (i, j) } else { (j, i) });
6354 });
6355 }
6356 if primitives.len() >= 2 {
6357 pairs.extend(LinearBvh::build(primitives).find_pairs());
6358 }
6359 pairs.sort_unstable();
6360 pairs.dedup();
6361 pairs
6362 }
6363
6364 #[allow(clippy::too_many_lines, clippy::items_after_statements)]
6375 fn detect_collisions(&mut self) {
6376 let n = self.bodies.len();
6377 if n < 2 {
6378 return;
6379 }
6380
6381 let mut primitives = Vec::new();
6384 let count = self.stage_count();
6385 for k in 0..count {
6386 let i = self.stage_body(k);
6387 if let Some(radius) = self.body_collision_radii.get(i).and_then(|r| *r) {
6388 primitives.push(BvhPrimitive {
6389 aabb: self.broadphase_box(i, radius),
6390 index: i as u32,
6391 morton: 0,
6392 });
6393 }
6394 }
6395 self.stage_work.broadphase_primitives += primitives.len() as u64;
6396
6397 let parked_with_radius = if self.park.active {
6398 self.park.proxy_live
6399 } else {
6400 0
6401 };
6402 if primitives.len() + parked_with_radius < 2 {
6403 return;
6404 }
6405
6406 let pairs = if self.park.active {
6407 self.parked_broadphase_pairs(primitives)
6408 } else {
6409 self.broadphase_pairs(primitives)
6410 };
6411 self.stage_work.broadphase_pairs += pairs.len() as u64;
6412
6413 let velocity_of = |i: usize| -> Vec3Fix {
6417 let body = &self.bodies[i];
6418 if self.park.first_substep
6419 && !self.force_fields.is_empty()
6420 && self.park.is_parked(i)
6421 && !body.is_static()
6422 {
6423 crate::force::force_field_velocity(&self.force_fields, i, body, self.park.frame_dt)
6424 } else {
6425 body.velocity
6426 }
6427 };
6428
6429 struct ContactInfo {
6431 body_a: usize,
6432 body_b: usize,
6433 contact: Contact,
6434 normal: Vec3Fix,
6435 point: Vec3Fix,
6436 depth: Fix128,
6437 rel_vel: Fix128,
6438 is_sensor: bool,
6439 }
6440 let mut results: Vec<ContactInfo> = Vec::new();
6441 let mut radius_overflow = false;
6442
6443 for (a32, b32) in pairs {
6444 let a = a32 as usize;
6445 let b = b32 as usize;
6446 if a >= n || b >= n {
6447 continue;
6448 }
6449
6450 let radius_a = self
6452 .body_collision_radii
6453 .get(a)
6454 .and_then(|r| *r)
6455 .unwrap_or(Fix128::ZERO);
6456 let radius_b = self
6457 .body_collision_radii
6458 .get(b)
6459 .and_then(|r| *r)
6460 .unwrap_or(Fix128::ZERO);
6461
6462 let delta = self.bodies[a].position - self.bodies[b].position;
6470 let Some(combined_radius) = radius_a.checked_add(radius_b) else {
6473 radius_overflow = true;
6474 continue;
6475 };
6476 let mut scaled: Option<(Vec3Fix, Fix128)> = None;
6490 let collider_a = self.body_colliders.get(a).and_then(Option::as_ref);
6497 let collider_b = self.body_colliders.get(b).and_then(Option::as_ref);
6498 if collider_a.is_none() && collider_b.is_none() {
6499 let squares = delta
6500 .checked_length_squared()
6501 .zip(combined_radius.checked_mul(combined_radius));
6502 match squares {
6503 Some((d2, r2)) => {
6504 if d2 >= r2 || d2.is_zero() {
6505 continue;
6506 }
6507 }
6508 None => {
6509 let Some(dist) = delta.checked_length_scaled() else {
6512 continue;
6513 };
6514 if dist >= combined_radius || dist.is_zero() {
6515 continue;
6516 }
6517 let Some(normal) = delta.try_normalize_scaled() else {
6518 continue;
6519 };
6520 scaled = Some((normal, dist));
6521 }
6522 }
6523 } else if !self
6524 .broadphase_box(a, radius_a)
6525 .intersects(&self.broadphase_box(b, radius_b))
6526 {
6527 continue;
6528 }
6529
6530 let filter_a = self
6532 .body_filters
6533 .get(a)
6534 .copied()
6535 .unwrap_or(CollisionFilter::DEFAULT);
6536 let filter_b = self
6537 .body_filters
6538 .get(b)
6539 .copied()
6540 .unwrap_or(CollisionFilter::DEFAULT);
6541 if !CollisionFilter::can_collide(&filter_a, &filter_b) {
6542 continue;
6543 }
6544
6545 if self.bodies[a].is_static() && self.bodies[b].is_static() {
6547 continue;
6548 }
6549
6550 if self.islands.is_sleeping(a) && self.islands.is_sleeping(b) {
6552 continue;
6553 }
6554
6555 if collider_a.is_some() || collider_b.is_some() {
6558 let pose_a = (self.bodies[a].position, self.bodies[a].rotation);
6559 let pose_b = (self.bodies[b].position, self.bodies[b].rotation);
6560 let hit = match (collider_a, collider_b) {
6561 (Some(ca), Some(cb)) => {
6562 crate::body_collider::contact_between(ca, pose_a, cb, pose_b)
6563 }
6564 (Some(ca), None) => crate::body_collider::contact_with_sphere(
6565 ca,
6566 pose_a,
6567 crate::collider::Sphere::new(pose_b.0, radius_b),
6568 true,
6569 ),
6570 (None, Some(cb)) => crate::body_collider::contact_with_sphere(
6571 cb,
6572 pose_b,
6573 crate::collider::Sphere::new(pose_a.0, radius_a),
6574 false,
6575 ),
6576 (None, None) => None,
6577 };
6578 if let Some(contact) = hit {
6579 if contact.depth > Fix128::ZERO {
6580 let rel_vel = (velocity_of(a) - velocity_of(b)).dot(contact.normal);
6581 results.push(ContactInfo {
6582 body_a: a,
6583 body_b: b,
6584 contact,
6585 normal: contact.normal,
6586 point: contact.point_a,
6587 depth: contact.depth,
6588 rel_vel,
6589 is_sensor: self.bodies[a].is_sensor || self.bodies[b].is_sensor,
6590 });
6591 }
6592 }
6593 continue;
6594 }
6595
6596 let (normal, dist) = scaled.unwrap_or_else(|| delta.normalize_with_length());
6597
6598 if dist < combined_radius && !dist.is_zero() {
6599 let depth = combined_radius - dist;
6600 let point_a = self.bodies[a].position - normal * radius_a;
6601 let point_b = self.bodies[b].position + normal * radius_b;
6602 let rel_vel = (velocity_of(a) - velocity_of(b)).dot(normal);
6604 let is_sensor = self.bodies[a].is_sensor || self.bodies[b].is_sensor;
6605
6606 let contact = Contact {
6607 depth,
6608 normal,
6609 point_a,
6610 point_b,
6611 };
6612
6613 results.push(ContactInfo {
6614 body_a: a,
6615 body_b: b,
6616 contact,
6617 normal,
6618 point: point_a,
6619 depth,
6620 rel_vel,
6621 is_sensor,
6622 });
6623 }
6624 }
6625
6626 if radius_overflow {
6627 self.note_rigid_overflow();
6628 }
6629
6630 for info in results {
6632 self.events.report_contact(
6634 info.body_a,
6635 info.body_b,
6636 info.normal,
6637 info.point,
6638 info.depth,
6639 info.rel_vel,
6640 );
6641
6642 for x in [info.body_a, info.body_b] {
6648 if !self.islands.is_sleeping(x) {
6649 continue;
6650 }
6651 if self.park.is_parked(x) {
6652 self.islands.wake_body(x);
6656 self.park.unpark(x, &mut self.stage_work);
6657 } else {
6658 self.islands.wake_island(x);
6659 }
6660 }
6661
6662 if info.is_sensor {
6663 self.events.report_trigger(info.body_a, info.body_b);
6664 } else {
6665 self.add_contact_with_material(info.body_a, info.body_b, info.contact);
6666 }
6667 }
6668 }
6669
6670 #[inline]
6672 #[must_use]
6673 pub fn get_body(&self, idx: usize) -> Option<&RigidBody> {
6674 self.bodies.get(idx)
6675 }
6676
6677 #[inline]
6683 pub fn get_body_mut(&mut self, idx: usize) -> Option<&mut RigidBody> {
6684 self.bodies.get_mut(idx)
6685 }
6686
6687 #[must_use]
6710 pub const fn overflow_detected(&self) -> bool {
6711 self.overflow_detected
6712 }
6713
6714 #[inline]
6721 pub(crate) fn note_rigid_overflow(&mut self) {
6722 self.overflow_detected = true;
6723 }
6724
6725 pub const STATE_MAGIC: [u8; 4] = *b"APHY";
6730
6731 pub const STATE_VERSION: u16 = 3;
6743
6744 #[must_use]
6779 pub fn serialize_state(&self) -> Vec<u8> {
6780 let mut data = Vec::new();
6781
6782 data.extend_from_slice(&Self::STATE_MAGIC);
6784 data.extend_from_slice(&Self::STATE_VERSION.to_le_bytes());
6785 data.extend_from_slice(&0u16.to_le_bytes());
6786
6787 let count = self.bodies.len() as u32;
6789 data.extend_from_slice(&count.to_le_bytes());
6790
6791 for body in &self.bodies {
6793 data.extend_from_slice(&body.position.x.hi.to_le_bytes());
6795 data.extend_from_slice(&body.position.x.lo.to_le_bytes());
6796 data.extend_from_slice(&body.position.y.hi.to_le_bytes());
6797 data.extend_from_slice(&body.position.y.lo.to_le_bytes());
6798 data.extend_from_slice(&body.position.z.hi.to_le_bytes());
6799 data.extend_from_slice(&body.position.z.lo.to_le_bytes());
6800
6801 data.extend_from_slice(&body.velocity.x.hi.to_le_bytes());
6803 data.extend_from_slice(&body.velocity.x.lo.to_le_bytes());
6804 data.extend_from_slice(&body.velocity.y.hi.to_le_bytes());
6805 data.extend_from_slice(&body.velocity.y.lo.to_le_bytes());
6806 data.extend_from_slice(&body.velocity.z.hi.to_le_bytes());
6807 data.extend_from_slice(&body.velocity.z.lo.to_le_bytes());
6808
6809 data.extend_from_slice(&body.rotation.x.hi.to_le_bytes());
6811 data.extend_from_slice(&body.rotation.x.lo.to_le_bytes());
6812 data.extend_from_slice(&body.rotation.y.hi.to_le_bytes());
6813 data.extend_from_slice(&body.rotation.y.lo.to_le_bytes());
6814 data.extend_from_slice(&body.rotation.z.hi.to_le_bytes());
6815 data.extend_from_slice(&body.rotation.z.lo.to_le_bytes());
6816 data.extend_from_slice(&body.rotation.w.hi.to_le_bytes());
6817 data.extend_from_slice(&body.rotation.w.lo.to_le_bytes());
6818
6819 data.extend_from_slice(&body.angular_velocity.x.hi.to_le_bytes());
6821 data.extend_from_slice(&body.angular_velocity.x.lo.to_le_bytes());
6822 data.extend_from_slice(&body.angular_velocity.y.hi.to_le_bytes());
6823 data.extend_from_slice(&body.angular_velocity.y.lo.to_le_bytes());
6824 data.extend_from_slice(&body.angular_velocity.z.hi.to_le_bytes());
6825 data.extend_from_slice(&body.angular_velocity.z.lo.to_le_bytes());
6826 }
6827
6828 for i in 0..self.bodies.len() {
6834 let sd = self
6835 .islands
6836 .sleep_data
6837 .get(i)
6838 .copied()
6839 .unwrap_or_else(SleepData::new);
6840 data.push(match sd.state {
6841 SleepState::Awake => 0,
6842 SleepState::Sleeping => 1,
6843 });
6844 data.extend_from_slice(&sd.idle_frames.to_le_bytes());
6845 }
6846
6847 data.push(u8::from(self.overflow_detected));
6852
6853 data.extend_from_slice(&self.population_fingerprint().to_le_bytes());
6859
6860 data
6861 }
6862
6863 #[must_use]
6879 pub fn population_fingerprint(&self) -> u64 {
6880 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
6881 for i in 0..self.bodies.len() {
6882 let body = &self.bodies[i];
6883 fnv1a_fold(&mut hash, &body.inv_mass.hi.to_le_bytes());
6884 fnv1a_fold(&mut hash, &body.inv_mass.lo.to_le_bytes());
6885 fnv1a_fold(&mut hash, &body.inv_inertia.x.hi.to_le_bytes());
6886 fnv1a_fold(&mut hash, &body.inv_inertia.x.lo.to_le_bytes());
6887 fnv1a_fold(&mut hash, &body.inv_inertia.y.hi.to_le_bytes());
6888 fnv1a_fold(&mut hash, &body.inv_inertia.y.lo.to_le_bytes());
6889 fnv1a_fold(&mut hash, &body.inv_inertia.z.hi.to_le_bytes());
6890 fnv1a_fold(&mut hash, &body.inv_inertia.z.lo.to_le_bytes());
6891 fnv1a_fold(&mut hash, &[body.body_type as u8]);
6892 fnv1a_fold(&mut hash, &[u8::from(body.is_sensor)]);
6893 match self.body_collision_radii.get(i).copied().flatten() {
6894 Some(r) => {
6895 fnv1a_fold(&mut hash, &[1u8]);
6896 fnv1a_fold(&mut hash, &r.hi.to_le_bytes());
6897 fnv1a_fold(&mut hash, &r.lo.to_le_bytes());
6898 }
6899 None => fnv1a_fold(&mut hash, &[0u8]),
6900 }
6901 let filter = self.body_filter(i);
6902 fnv1a_fold(&mut hash, &filter.layer.to_le_bytes());
6903 fnv1a_fold(&mut hash, &filter.mask.to_le_bytes());
6904 fnv1a_fold(&mut hash, &filter.group.to_le_bytes());
6905 let material = self
6906 .body_materials
6907 .get(i)
6908 .copied()
6909 .unwrap_or(crate::material::DEFAULT_MATERIAL);
6910 fnv1a_fold(&mut hash, &material.to_le_bytes());
6911 }
6912 hash
6913 }
6914
6915 pub fn deserialize_state(&mut self, data: &[u8]) -> bool {
6939 if data.len() < 12 {
6941 return false;
6942 }
6943 if data[0..4] != Self::STATE_MAGIC {
6946 return false;
6947 }
6948 if u16::from_le_bytes([data[4], data[5]]) != Self::STATE_VERSION {
6949 return false;
6950 }
6951 if u16::from_le_bytes([data[6], data[7]]) != 0 {
6956 return false;
6957 }
6958
6959 let count = u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
6960
6961 if count != self.bodies.len() {
6962 return false;
6963 }
6964
6965 let expected = 12 + count * 208 + count * 5 + 1 + 8;
6967 if data.len() < expected {
6968 return false;
6969 }
6970
6971 let fp_offset = expected - 8;
6976 let stored_fingerprint = u64::from_le_bytes([
6977 data[fp_offset],
6978 data[fp_offset + 1],
6979 data[fp_offset + 2],
6980 data[fp_offset + 3],
6981 data[fp_offset + 4],
6982 data[fp_offset + 5],
6983 data[fp_offset + 6],
6984 data[fp_offset + 7],
6985 ]);
6986 if stored_fingerprint != self.population_fingerprint() {
6987 return false;
6988 }
6989
6990 let mut offset = 12;
6992 for body in &mut self.bodies {
6993 if offset + 208 > data.len() {
6994 return false;
6995 }
6996
6997 let read_fix128 = |o: &mut usize| -> Fix128 {
6999 let hi = i64::from_le_bytes([
7000 data[*o],
7001 data[*o + 1],
7002 data[*o + 2],
7003 data[*o + 3],
7004 data[*o + 4],
7005 data[*o + 5],
7006 data[*o + 6],
7007 data[*o + 7],
7008 ]);
7009 *o += 8;
7010 let lo = u64::from_le_bytes([
7011 data[*o],
7012 data[*o + 1],
7013 data[*o + 2],
7014 data[*o + 3],
7015 data[*o + 4],
7016 data[*o + 5],
7017 data[*o + 6],
7018 data[*o + 7],
7019 ]);
7020 *o += 8;
7021 Fix128 { hi, lo }
7022 };
7023
7024 body.position.x = read_fix128(&mut offset);
7025 body.position.y = read_fix128(&mut offset);
7026 body.position.z = read_fix128(&mut offset);
7027
7028 body.velocity.x = read_fix128(&mut offset);
7029 body.velocity.y = read_fix128(&mut offset);
7030 body.velocity.z = read_fix128(&mut offset);
7031
7032 body.rotation.x = read_fix128(&mut offset);
7033 body.rotation.y = read_fix128(&mut offset);
7034 body.rotation.z = read_fix128(&mut offset);
7035 body.rotation.w = read_fix128(&mut offset);
7036
7037 body.angular_velocity.x = read_fix128(&mut offset);
7038 body.angular_velocity.y = read_fix128(&mut offset);
7039 body.angular_velocity.z = read_fix128(&mut offset);
7040 }
7041
7042 let n = self.bodies.len();
7044 while self.body_collision_radii.len() < n {
7045 self.body_collision_radii.push(None);
7046 }
7047 self.body_collision_radii.truncate(n);
7048 self.body_colliders.resize(n, None);
7049 while self.body_filters.len() < n {
7050 self.body_filters.push(CollisionFilter::DEFAULT);
7051 }
7052 self.body_filters.truncate(n);
7053 while self.body_materials.len() < n {
7054 self.body_materials.push(crate::material::DEFAULT_MATERIAL);
7055 }
7056 self.body_materials.truncate(n);
7057 #[cfg(feature = "std")]
7058 self.sync_body_stable_ids();
7059 self.islands = IslandManager::new(n, self.islands.config);
7062
7063 for i in 0..n {
7065 let state = match data[offset] {
7066 0 => SleepState::Awake,
7067 1 => SleepState::Sleeping,
7068 _ => return false,
7071 };
7072 offset += 1;
7073 let idle_frames = u32::from_le_bytes([
7074 data[offset],
7075 data[offset + 1],
7076 data[offset + 2],
7077 data[offset + 3],
7078 ]);
7079 offset += 4;
7080 if let Some(sd) = self.islands.sleep_data.get_mut(i) {
7081 sd.state = state;
7082 sd.idle_frames = idle_frames;
7083 }
7084 }
7085
7086 self.overflow_detected = match data[offset] {
7092 0 => false,
7093 1 => true,
7094 _ => return false,
7096 };
7097
7098 true
7099 }
7100}
7101
7102impl core::fmt::Debug for PhysicsWorld {
7103 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
7104 f.debug_struct("PhysicsWorld")
7105 .field("config", &self.config)
7106 .field("bodies", &self.bodies.len())
7107 .field("distance_constraints", &self.distance_constraints.len())
7108 .field("contact_constraints", &self.contact_constraints.len())
7109 .field("sdf_colliders", &self.sdf_colliders.len())
7110 .field("static_colliders", &self.static_colliders.len())
7111 .field("joints", &self.joints.len())
7112 .field("force_fields", &self.force_fields.len())
7113 .finish_non_exhaustive()
7114 }
7115}
7116
7117#[cfg(all(test, feature = "std"))]
7118mod tests {
7119 use super::*;
7120
7121 #[test]
7128 fn checked_world_inv_inertia_apply_is_the_unchecked_one_in_range() {
7129 let torques = [
7130 Vec3Fix::from_int(3, -1, 2),
7131 Vec3Fix::new(
7132 Fix128::from_ratio(-5, 7),
7133 Fix128::from_ratio(11, 3),
7134 Fix128::from_int(-9),
7135 ),
7136 ];
7137 let rotations = [
7138 QuatFix::from_axis_angle(Vec3Fix::from_int(1, 2, 3), Fix128::from_ratio(7, 10)),
7139 QuatFix::from_axis_angle(Vec3Fix::from_int(-2, 1, 0), Fix128::from_ratio(-13, 5)),
7140 QuatFix::from_axis_angle(Vec3Fix::from_int(0, 0, 1), Fix128::from_ratio(1, 3)),
7141 ];
7142 for q in rotations {
7143 let mut body = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
7144 body.inv_inertia = Vec3Fix::from_int(1, 2, 4);
7145 body.rotation = q;
7146 for tau in torques {
7147 let unchecked = body.world_inv_inertia_apply(tau);
7148 assert_eq!(
7149 body.checked_world_inv_inertia_apply(tau),
7150 Some(unchecked),
7151 "q {q:?} tau {tau:?}"
7152 );
7153 let mut unrotated = body;
7154 unrotated.rotation = QuatFix::IDENTITY;
7155 assert_ne!(
7156 unrotated.world_inv_inertia_apply(tau),
7157 unchecked,
7158 "fixture: the rotation must matter"
7159 );
7160 let mut isotropic = body;
7161 isotropic.inv_inertia = Vec3Fix::from_int(2, 2, 2);
7162 assert_ne!(
7163 isotropic.world_inv_inertia_apply(tau),
7164 unchecked,
7165 "fixture: the anisotropy must matter"
7166 );
7167 }
7168 }
7169 }
7170
7171 #[test]
7175 fn tgs_cache_key_separates_ordinal_kind_and_pair() {
7176 let mut ord = std::collections::BTreeMap::new();
7177 let k0 = tgs_cache_key(TGS_KEY_CONTACT, 3, 7, &mut ord);
7178 let k1 = tgs_cache_key(TGS_KEY_CONTACT, 3, 7, &mut ord);
7179 let other = tgs_cache_key(TGS_KEY_CONTACT, 3, 8, &mut ord);
7180 assert_ne!(k0, k1, "two entries of one pair must not share a key");
7181 let mut again = std::collections::BTreeMap::new();
7182 assert_eq!(tgs_cache_key(TGS_KEY_CONTACT, 3, 7, &mut again), k0);
7183 assert_eq!(tgs_cache_key(TGS_KEY_CONTACT, 3, 7, &mut again), k1);
7184 let mut fresh = std::collections::BTreeMap::new();
7185 assert_ne!(tgs_cache_key(TGS_KEY_DISTANCE, 3, 7, &mut fresh), k0);
7186 let mut fresh = std::collections::BTreeMap::new();
7187 assert_ne!(tgs_cache_key(TGS_KEY_CONTACT, 7, 3, &mut fresh), k0);
7188 assert_ne!(other, k0);
7189 assert_eq!(ord.get(&(3, 7)), Some(&2));
7190 }
7191
7192 #[test]
7193 fn test_rigid_body_creation() {
7194 let body = RigidBody::new(Vec3Fix::ZERO, Fix128::ONE);
7195 assert!(!body.is_static());
7196
7197 let static_body = RigidBody::new_static(Vec3Fix::ZERO);
7198 assert!(static_body.is_static());
7199 }
7200
7201 #[test]
7202 fn test_gravity_integration() {
7203 let config = SolverConfig::default();
7204 let mut world = PhysicsWorld::new(config);
7205
7206 let body = RigidBody::new(Vec3Fix::from_int(0, 10, 0), Fix128::ONE);
7207 world.add_body(body);
7208
7209 for _ in 0..10 {
7211 world.step(Fix128::from_ratio(1, 10));
7212 }
7213
7214 let body = world.get_body(0).unwrap();
7216 assert!(
7217 body.position.y < Fix128::from_int(10),
7218 "Body should have fallen"
7219 );
7220 }
7221
7222 #[test]
7223 fn test_distance_constraint() {
7224 let config = SolverConfig {
7225 substeps: 4,
7226 iterations: 8,
7227 gravity: Vec3Fix::ZERO, ..Default::default()
7229 };
7230 let mut world = PhysicsWorld::new(config);
7231
7232 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
7234 let b = world.add_body(RigidBody::new(Vec3Fix::from_int(5, 0, 0), Fix128::ONE));
7235
7236 world.add_distance_constraint(DistanceConstraint::new(
7237 a,
7238 b,
7239 Vec3Fix::ZERO,
7240 Vec3Fix::ZERO,
7241 Fix128::from_int(3), ));
7243
7244 for _ in 0..100 {
7246 world.step(Fix128::from_ratio(1, 60));
7247 }
7248
7249 let body_b = world.get_body(b).unwrap();
7251 let distance = body_b.position.length();
7252
7253 assert!(
7255 distance < Fix128::from_int(5),
7256 "Constraint should pull body closer"
7257 );
7258 }
7259
7260 #[test]
7261 fn test_state_serialization() {
7262 let config = SolverConfig::default();
7263 let mut world = PhysicsWorld::new(config);
7264
7265 world.add_body(RigidBody::new(Vec3Fix::from_int(1, 2, 3), Fix128::ONE));
7266 world.add_body(RigidBody::new(
7267 Vec3Fix::from_int(4, 5, 6),
7268 Fix128::from_int(2),
7269 ));
7270
7271 let state = world.serialize_state();
7272
7273 let mut world2 = PhysicsWorld::new(config);
7278 world2.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE));
7279 world2.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::from_int(2)));
7280
7281 assert!(world2.deserialize_state(&state));
7282
7283 assert_eq!(world2.bodies[0].position.x.hi, 1);
7284 assert_eq!(world2.bodies[0].position.y.hi, 2);
7285 assert_eq!(world2.bodies[1].position.x.hi, 4);
7286 }
7287
7288 #[test]
7289 fn test_determinism() {
7290 let config = SolverConfig::default();
7292
7293 let run_simulation = || {
7294 let mut world = PhysicsWorld::new(config);
7295 world.add_body(RigidBody::new(Vec3Fix::from_int(0, 10, 0), Fix128::ONE));
7296 world.add_body(RigidBody::new(
7297 Vec3Fix::from_int(5, 10, 0),
7298 Fix128::from_int(2),
7299 ));
7300
7301 for _ in 0..100 {
7302 world.step(Fix128::from_ratio(1, 60));
7303 }
7304
7305 world.serialize_state()
7306 };
7307
7308 let state1 = run_simulation();
7309 let state2 = run_simulation();
7310
7311 assert_eq!(state1, state2, "Simulation must be deterministic");
7312 }
7313
7314 #[test]
7315 fn test_body_material_assignment() {
7316 let config = SolverConfig::default();
7317 let mut world = PhysicsWorld::new(config);
7318
7319 let metal_id = world.material_table.register_metal();
7320 let rubber_id = world.material_table.register_rubber();
7321
7322 let a = world.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE));
7323 let b = world.add_body(RigidBody::new(Vec3Fix::from_int(2, 0, 0), Fix128::ONE));
7324
7325 world.set_body_material(a, metal_id);
7326 world.set_body_material(b, rubber_id);
7327
7328 let combined = world.combined_material(a, b);
7329 assert!(
7330 combined.friction > Fix128::ZERO,
7331 "Combined friction should be positive"
7332 );
7333 }
7334
7335 #[test]
7336 fn test_contact_cache_integration() {
7337 let config = SolverConfig::default();
7338 let mut world = PhysicsWorld::new(config);
7339
7340 let a = world.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE));
7341 let b = world.add_body(RigidBody::new(Vec3Fix::from_int(1, 0, 0), Fix128::ONE));
7342
7343 world.begin_frame();
7344
7345 let contact = Contact {
7346 depth: Fix128::from_ratio(1, 10),
7347 normal: Vec3Fix::UNIT_X,
7348 point_a: Vec3Fix::from_int(1, 0, 0),
7349 point_b: Vec3Fix::from_int(1, 0, 0),
7350 };
7351 world.add_contact_with_material(a, b, contact);
7352
7353 world.end_frame();
7354
7355 let key = crate::contact_cache::BodyPairKey::new(a, b);
7357 let manifold = world.contact_cache.find(&key);
7358 assert!(
7359 manifold.is_some(),
7360 "Contact cache should contain the manifold"
7361 );
7362 }
7363
7364 #[test]
7365 fn test_kinematic_body() {
7366 let config = SolverConfig::default();
7367 let mut world = PhysicsWorld::new(config);
7368
7369 let kinematic = RigidBody::new_kinematic(Vec3Fix::ZERO);
7370 let k_id = world.add_body(kinematic);
7371
7372 world.bodies[k_id].set_kinematic_target(Vec3Fix::from_int(5, 0, 0), QuatFix::IDENTITY);
7374
7375 world.step(Fix128::from_ratio(1, 60));
7376
7377 let pos = world.bodies[k_id].position;
7379 assert_eq!(pos.x.hi, 5, "Kinematic body should move to target");
7380 }
7381
7382 #[test]
7383 fn test_body_type_enum() {
7384 let dynamic = RigidBody::new(Vec3Fix::ZERO, Fix128::ONE);
7385 assert!(dynamic.is_dynamic());
7386 assert!(!dynamic.is_kinematic());
7387
7388 let static_b = RigidBody::new_static(Vec3Fix::ZERO);
7389 assert!(static_b.is_static());
7390 assert!(!static_b.is_dynamic());
7391
7392 let kinematic = RigidBody::new_kinematic(Vec3Fix::ZERO);
7393 assert!(kinematic.is_kinematic());
7394 assert!(kinematic.is_static()); }
7396
7397 #[test]
7398 fn test_per_body_gravity_scale() {
7399 let config = SolverConfig::default();
7400 let mut world = PhysicsWorld::new(config);
7401
7402 let mut no_grav = RigidBody::new(Vec3Fix::from_int(0, 10, 0), Fix128::ONE);
7404 no_grav.gravity_scale = Fix128::ZERO;
7405 let ng_id = world.add_body(no_grav);
7406
7407 let normal = RigidBody::new(Vec3Fix::from_int(5, 10, 0), Fix128::ONE);
7409 let n_id = world.add_body(normal);
7410
7411 for _ in 0..60 {
7412 world.step(Fix128::from_ratio(1, 60));
7413 }
7414
7415 let ng_y = world.bodies[ng_id].position.y;
7417 assert!(
7418 ng_y > Fix128::from_int(9),
7419 "Zero-gravity body should stay near y=10, got {ng_y:?}"
7420 );
7421
7422 let n_y = world.bodies[n_id].position.y;
7424 assert!(
7425 n_y < Fix128::from_int(10),
7426 "Normal gravity body should fall"
7427 );
7428 }
7429
7430 #[test]
7431 fn test_sdf_ground_collision() {
7432 use crate::math::QuatFix;
7433 use crate::sdf_collider::{ClosureSdf, SdfCollider};
7434
7435 let config = SolverConfig::default();
7436 let mut world = PhysicsWorld::new(config);
7437 world.set_sdf_collision_radius(Fix128::from_ratio(1, 2)); let body = RigidBody::new(Vec3Fix::from_int(0, 5, 0), Fix128::ONE);
7441 let body_id = world.add_body(body);
7442
7443 let ground = ClosureSdf::new(|_x, y, _z| y, |_x, _y, _z| (0.0, 1.0, 0.0));
7445 world.add_sdf_collider(SdfCollider::new_static(
7446 Box::new(ground),
7447 Vec3Fix::ZERO,
7448 QuatFix::IDENTITY,
7449 ));
7450
7451 let dt = Fix128::from_ratio(1, 60);
7453 for _ in 0..120 {
7454 world.step(dt);
7455 }
7456
7457 let pos = world.bodies[body_id].position;
7459 let y = pos.y.to_f32();
7460
7461 assert!(y < 5.0, "Body should have fallen from y=5");
7463 assert!(
7464 y > -1.0,
7465 "Body should not have fallen through SDF ground, y={y}"
7466 );
7467 }
7468
7469 #[test]
7470 fn test_warm_start_factor_default() {
7471 let config = SolverConfig::default();
7472 let expected = Fix128::from_ratio(85, 100);
7474 assert_eq!(config.warm_start_factor, expected);
7475 }
7476
7477 #[test]
7478 fn test_warm_start_factor_convergence() {
7479 let make_world = |wsf: Fix128| {
7481 let config = SolverConfig {
7482 substeps: 4,
7483 iterations: 2, gravity: Vec3Fix::ZERO,
7485 warm_start_factor: wsf,
7486 ..Default::default()
7487 };
7488 let mut world = PhysicsWorld::new(config);
7489 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
7490 let b = world.add_body(RigidBody::new(Vec3Fix::from_int(5, 0, 0), Fix128::ONE));
7491 let constraint =
7492 DistanceConstraint::new(a, b, Vec3Fix::ZERO, Vec3Fix::ZERO, Fix128::from_int(3));
7493 world.add_distance_constraint(constraint);
7494 world
7495 };
7496
7497 let mut world_warm = make_world(Fix128::from_ratio(85, 100));
7498 let mut world_cold = make_world(Fix128::ZERO);
7499
7500 let dt = Fix128::from_ratio(1, 60);
7501 for _ in 0..30 {
7502 world_warm.step(dt);
7503 world_cold.step(dt);
7504 }
7505
7506 let dist_warm = (world_warm.bodies[1].position - world_warm.bodies[0].position)
7508 .length()
7509 .to_f32();
7510 let dist_cold = (world_cold.bodies[1].position - world_cold.bodies[0].position)
7511 .length()
7512 .to_f32();
7513
7514 let target = 3.0_f32;
7515 let err_warm = (dist_warm - target).abs();
7516 let err_cold = (dist_cold - target).abs();
7517 assert!(
7519 err_warm <= err_cold + 0.1,
7520 "Warm-start should converge at least as well: err_warm={err_warm}, err_cold={err_cold}"
7521 );
7522 }
7523
7524 #[test]
7525 fn test_contact_warm_start_cached_lambda() {
7526 let contact = Contact {
7528 depth: Fix128::from_ratio(1, 10),
7529 normal: Vec3Fix::UNIT_Y,
7530 point_a: Vec3Fix::ZERO,
7531 point_b: Vec3Fix::ZERO,
7532 };
7533 let cc = ContactConstraint::new(0, 1, contact);
7534 assert_eq!(cc.cached_lambda, Fix128::ZERO);
7535 }
7536
7537 #[test]
7538 fn test_manual_contact_pushes_body_out_by_depth() {
7539 let config = SolverConfig {
7542 substeps: 1,
7543 iterations: 2,
7544 gravity: Vec3Fix::ZERO,
7545 ..Default::default()
7546 };
7547 let mut world = PhysicsWorld::new(config);
7548 let _a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
7549 let _b = world.add_body(RigidBody::new(
7550 Vec3Fix::from_f32(0.0, 0.05, 0.0),
7551 Fix128::ONE,
7552 ));
7553
7554 let contact = Contact {
7558 depth: Fix128::from_ratio(5, 100), normal: -Vec3Fix::UNIT_Y, point_a: Vec3Fix::ZERO,
7561 point_b: Vec3Fix::from_f32(0.0, 0.05, 0.0),
7562 };
7563 world.add_contact(ContactConstraint::new(0, 1, contact));
7564
7565 let dt = Fix128::from_ratio(1, 60);
7566 for _ in 0..world.config.iterations {
7567 world.solve_contact_constraints(dt);
7568 }
7569
7570 let y = world.bodies[1].position.y.to_f32();
7572 assert!(
7573 (y - 0.10).abs() < 1e-5,
7574 "Body should be pushed up by depth once: y={y}"
7575 );
7576 }
7577
7578 #[cfg(feature = "gpu-solver-bridge")]
7592 struct RecordingBridge {
7593 send_contact_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7594 send_body_state_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7595 dispatch_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7596 send_joints_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7599 send_body_rotations_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7600 dispatch_joint_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7601 }
7602
7603 #[cfg(feature = "gpu-solver-bridge")]
7604 impl crate::gpu_bridge::GpuSolverBridge for RecordingBridge {
7605 fn send_island(&mut self, _p: &[[Fix128; 3]], _v: &[[Fix128; 3]]) {}
7606 fn dispatch_iterations(&mut self, _iters: u32, _dt: Fix128) {}
7607 fn recv_island(&self, _p: &mut [[Fix128; 3]], _v: &mut [[Fix128; 3]]) {}
7608 fn assert_bit_exact_vs_cpu(
7609 &self,
7610 _fixture: &crate::gpu_bridge::DiffFixture,
7611 ) -> Result<(), crate::gpu_bridge::GpuDivergence> {
7612 Ok(())
7613 }
7614 fn send_contact_constraints(&mut self, _c: &[ContactConstraint]) {
7615 self.send_contact_count
7616 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7617 }
7618 fn send_body_state(&mut self, _p: &[[Fix128; 3]], _i: &[Fix128]) {
7619 self.send_body_state_count
7620 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7621 }
7622 fn dispatch_contact_solve_iteration(&mut self, _warm_start_factor: Fix128) {
7623 self.dispatch_count
7624 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7625 }
7626 fn recv_contact_constraints(&self, _c: &mut [ContactConstraint]) {}
7627 fn recv_body_positions(&self, _p: &mut [[Fix128; 3]]) {}
7628 fn send_joints(&mut self, _j: &[crate::joint::Joint]) {
7629 self.send_joints_count
7630 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7631 }
7632 fn send_body_rotations(&mut self, _r: &[[Fix128; 4]]) {
7633 self.send_body_rotations_count
7634 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7635 }
7636 fn dispatch_joint_solve_iteration(&mut self, _dt: Fix128) {
7637 self.dispatch_joint_count
7638 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7639 }
7640 }
7641
7642 #[cfg(feature = "gpu-solver-bridge")]
7647 fn build_one_contact_world() -> PhysicsWorld {
7648 let mut world = PhysicsWorld::new(SolverConfig {
7649 gravity: Vec3Fix::ZERO,
7650 ..SolverConfig::default()
7651 });
7652 let body_a = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
7653 let body_b = RigidBody::new_dynamic(
7654 Vec3Fix::new(Fix128::from_ratio(19, 10), Fix128::ZERO, Fix128::ZERO),
7655 Fix128::ONE,
7656 );
7657 world.add_body_with_radius(body_a, Fix128::ONE);
7658 world.add_body_with_radius(body_b, Fix128::ONE);
7659 world
7660 }
7661
7662 #[cfg(feature = "gpu-solver-bridge")]
7666 #[test]
7667 fn physics_world_step_auto_routes_through_bridge_when_attached() {
7668 let send_contact_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7669 let send_body_state_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7670 let dispatch_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7671
7672 let bridge = RecordingBridge {
7673 send_contact_count: std::sync::Arc::clone(&send_contact_count),
7674 send_body_state_count: std::sync::Arc::clone(&send_body_state_count),
7675 dispatch_count: std::sync::Arc::clone(&dispatch_count),
7676 send_joints_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7677 send_body_rotations_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7678 dispatch_joint_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7679 };
7680
7681 let mut world = build_one_contact_world();
7682 world.set_gpu_solver_bridge(Some(Box::new(bridge)));
7683 assert!(world.gpu_solver_bridge_installed());
7684
7685 let iters = world.config.iterations;
7690 let dt = Fix128::from_ratio(1, 60);
7691 world.substep(dt);
7692
7693 assert_eq!(
7694 send_contact_count.load(core::sync::atomic::Ordering::SeqCst),
7695 iters,
7696 "send_contact_constraints must be invoked once per PGS iteration"
7697 );
7698 assert_eq!(
7699 send_body_state_count.load(core::sync::atomic::Ordering::SeqCst),
7700 iters,
7701 "send_body_state must be invoked once per PGS iteration"
7702 );
7703 assert_eq!(
7704 dispatch_count.load(core::sync::atomic::Ordering::SeqCst),
7705 iters,
7706 "dispatch_contact_solve_iteration must fire once per PGS iteration"
7707 );
7708 }
7709
7710 #[cfg(feature = "gpu-solver-bridge")]
7715 #[test]
7716 fn physics_world_step_falls_back_to_cpu_when_bridge_detached() {
7717 let mut world = build_one_contact_world();
7718 assert!(!world.gpu_solver_bridge_installed());
7719
7720 let x_before = world.bodies[0].position.x;
7721 let dt = Fix128::from_ratio(1, 60);
7722 world.substep(dt);
7723 let x_after = world.bodies[0].position.x;
7724
7725 assert_ne!(
7729 x_before, x_after,
7730 "CPU contact solver must adjust position when no bridge is installed"
7731 );
7732 }
7733
7734 #[cfg(feature = "gpu-solver-bridge")]
7739 #[test]
7740 fn physics_world_bridge_attach_detach_lifecycle() {
7741 let mut world = PhysicsWorld::new(SolverConfig::default());
7742 assert!(!world.gpu_solver_bridge_installed());
7743
7744 let bridge = RecordingBridge {
7745 send_contact_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7746 send_body_state_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7747 dispatch_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7748 send_joints_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7749 send_body_rotations_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7750 dispatch_joint_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7751 };
7752 world.set_gpu_solver_bridge(Some(Box::new(bridge)));
7753 assert!(world.gpu_solver_bridge_installed());
7754
7755 let taken = world.take_gpu_solver_bridge();
7756 assert!(taken.is_some(), "first take must return the installed box");
7757 assert!(!world.gpu_solver_bridge_installed());
7758
7759 let taken_again = world.take_gpu_solver_bridge();
7760 assert!(
7761 taken_again.is_none(),
7762 "subsequent take must return None when nothing is installed"
7763 );
7764 }
7765
7766 #[cfg(feature = "gpu-solver-bridge")]
7771 fn build_one_ball_joint_world() -> PhysicsWorld {
7772 use crate::joint::{BallJoint, Joint};
7773 use crate::math::Vec3Fix;
7774 let mut world = PhysicsWorld::new(SolverConfig::default());
7775 let body_a = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
7776 let body_b = RigidBody::new_dynamic(
7777 Vec3Fix::new(Fix128::from_int(1), Fix128::ZERO, Fix128::ZERO),
7778 Fix128::ONE,
7779 );
7780 world.add_body(body_a);
7781 world.add_body(body_b);
7782 world.joints.push(Joint::Ball(BallJoint::new(
7783 0,
7784 1,
7785 Vec3Fix::ZERO,
7786 Vec3Fix::ZERO,
7787 )));
7788 world
7789 }
7790
7791 #[cfg(feature = "gpu-solver-bridge")]
7798 #[test]
7799 fn physics_world_substep_auto_routes_joint_solve_through_bridge() {
7800 let send_joints_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7801 let send_body_rotations_count =
7802 std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7803 let dispatch_joint_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7804
7805 let bridge = RecordingBridge {
7806 send_contact_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7807 send_body_state_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7808 dispatch_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7809 send_joints_count: std::sync::Arc::clone(&send_joints_count),
7810 send_body_rotations_count: std::sync::Arc::clone(&send_body_rotations_count),
7811 dispatch_joint_count: std::sync::Arc::clone(&dispatch_joint_count),
7812 };
7813
7814 let mut world = build_one_ball_joint_world();
7815 world.set_gpu_solver_bridge(Some(Box::new(bridge)));
7816
7817 let dt = Fix128::from_ratio(1, 60);
7818 world.substep(dt);
7819
7820 assert_eq!(
7821 send_joints_count.load(core::sync::atomic::Ordering::SeqCst),
7822 1,
7823 "send_joints must be invoked exactly once per substep"
7824 );
7825 assert_eq!(
7826 send_body_rotations_count.load(core::sync::atomic::Ordering::SeqCst),
7827 1,
7828 "send_body_rotations must be invoked exactly once per substep"
7829 );
7830 assert_eq!(
7831 dispatch_joint_count.load(core::sync::atomic::Ordering::SeqCst),
7832 1,
7833 "dispatch_joint_solve_iteration must fire exactly once per substep (single Baumgarte projection, not PGS)"
7834 );
7835 }
7836
7837 #[cfg(feature = "gpu-solver-bridge")]
7842 #[test]
7843 fn physics_world_substep_falls_back_to_cpu_joint_solve_when_bridge_detached() {
7844 let mut world = build_one_ball_joint_world();
7845 assert!(!world.gpu_solver_bridge_installed());
7846
7847 let a_before = world.bodies[0].position.x;
7848 let b_before = world.bodies[1].position.x;
7849 let dt = Fix128::from_ratio(1, 60);
7850 world.substep(dt);
7851 let a_after = world.bodies[0].position.x;
7852 let b_after = world.bodies[1].position.x;
7853
7854 let a_delta = (a_after - a_before).to_f32();
7860 let b_delta = (b_after - b_before).to_f32();
7861 assert!(
7862 a_delta > 0.0,
7863 "body_a should move in +x toward body_b (delta = {a_delta})"
7864 );
7865 assert!(
7866 b_delta < 0.0,
7867 "body_b should move in -x toward body_a (delta = {b_delta})"
7868 );
7869 }
7870
7871 #[cfg(feature = "gpu-solver-bridge")]
7876 #[test]
7877 fn physics_world_substep_joint_solve_bridge_attach_take_lifecycle() {
7878 let dispatch_joint_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7879 let bridge = RecordingBridge {
7880 send_contact_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7881 send_body_state_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7882 dispatch_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7883 send_joints_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7884 send_body_rotations_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7885 dispatch_joint_count: std::sync::Arc::clone(&dispatch_joint_count),
7886 };
7887
7888 let mut world = build_one_ball_joint_world();
7889 world.set_gpu_solver_bridge(Some(Box::new(bridge)));
7890
7891 let dt = Fix128::from_ratio(1, 60);
7892 world.substep(dt);
7893 assert_eq!(
7894 dispatch_joint_count.load(core::sync::atomic::Ordering::SeqCst),
7895 1,
7896 "first substep must route through bridge"
7897 );
7898
7899 let taken = world.take_gpu_solver_bridge();
7900 assert!(taken.is_some(), "bridge take must return Some");
7901
7902 let a_before = world.bodies[0].position.x;
7905 world.substep(dt);
7906 let a_after = world.bodies[0].position.x;
7907 assert_eq!(
7908 dispatch_joint_count.load(core::sync::atomic::Ordering::SeqCst),
7909 1,
7910 "after take_gpu_solver_bridge, no further bridge dispatch may occur"
7911 );
7912 assert_ne!(
7913 a_before, a_after,
7914 "CPU joint solve should move body_a toward body_b after detach"
7915 );
7916 }
7917
7918 fn hub_world(hub: RigidBody, spokes: usize) -> PhysicsWorld {
7924 let mut world = PhysicsWorld::new(SolverConfig {
7925 gravity: Vec3Fix::ZERO,
7926 ..Default::default()
7927 });
7928 let hub_idx = world.add_body(hub);
7929 for i in 0..spokes {
7930 let spoke = world.add_body(RigidBody::new_dynamic(
7931 Vec3Fix::from_int(i as i64 + 1, 0, 0),
7932 Fix128::ONE,
7933 ));
7934 world.add_distance_constraint(DistanceConstraint::new(
7935 hub_idx,
7936 spoke,
7937 Vec3Fix::ZERO,
7938 Vec3Fix::ZERO,
7939 Fix128::ONE,
7940 ));
7941 }
7942 world
7943 }
7944
7945 #[test]
7946 fn coloring_dynamic_hub_over_64_spokes_gets_one_color_per_constraint() {
7947 for spokes in [65usize, 70, 200] {
7950 let mut world = hub_world(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE), spokes);
7951 world.rebuild_batches();
7952 assert_eq!(
7953 world.num_batches(),
7954 spokes,
7955 "dynamic hub with {spokes} spokes needs {spokes} batches"
7956 );
7957 assert!(world.batches_are_body_disjoint());
7958 }
7959 }
7960
7961 #[test]
7962 fn coloring_static_hub_imposes_no_constraint() {
7963 let mut world = hub_world(RigidBody::new_static(Vec3Fix::ZERO), 200);
7966 world.rebuild_batches();
7967 assert_eq!(world.num_batches(), 1);
7968 assert!(world.batches_are_body_disjoint());
7969 }
7970
7971 #[test]
7972 fn coloring_contacts_and_distances_share_color_space() {
7973 let mut world = PhysicsWorld::new(SolverConfig::default());
7977 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
7978 let b = world.add_body(RigidBody::new_dynamic(
7979 Vec3Fix::from_int(1, 0, 0),
7980 Fix128::ONE,
7981 ));
7982 world.add_distance_constraint(DistanceConstraint::new(
7983 a,
7984 b,
7985 Vec3Fix::ZERO,
7986 Vec3Fix::ZERO,
7987 Fix128::ONE,
7988 ));
7989 world.add_contact(ContactConstraint::new(
7990 a,
7991 b,
7992 Contact {
7993 point_a: Vec3Fix::ZERO,
7994 point_b: Vec3Fix::ZERO,
7995 normal: Vec3Fix::from_int(1, 0, 0),
7996 depth: Fix128::from_ratio(1, 10),
7997 },
7998 ));
7999 world.rebuild_batches();
8000 assert_eq!(world.num_batches(), 2);
8001 assert!(world.batches_are_body_disjoint());
8002 }
8003
8004 #[test]
8005 fn coloring_disjoint_check_detects_violation() {
8006 let mut world = hub_world(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE), 2);
8008 world.rebuild_batches();
8009 assert!(world.batches_are_body_disjoint());
8010 let mut bad = ConstraintBatch::default();
8011 bad.distance_indices.extend([0usize, 1]);
8012 world.constraint_batches = vec![bad];
8013 assert!(!world.batches_are_body_disjoint());
8014 }
8015
8016 #[cfg(feature = "parallel")]
8017 #[test]
8018 fn parallel_step_static_hub_matches_sequential_solver() {
8019 let sub_dt = Fix128::from_ratio(1, 240);
8023 let mut par = hub_world(RigidBody::new_static(Vec3Fix::ZERO), 70);
8024 let mut seq = hub_world(RigidBody::new_static(Vec3Fix::ZERO), 70);
8025 par.rebuild_batches();
8026 seq.rebuild_batches();
8027 assert_eq!(par.num_batches(), 1, "static hub → single batch");
8028 for _ in 0..120 {
8029 par.substep_batched(sub_dt);
8030 seq.substep(sub_dt);
8031 }
8032 for (p, s) in par.bodies.iter().zip(&seq.bodies) {
8033 assert_eq!(p.position, s.position);
8034 assert_eq!(p.velocity, s.velocity);
8035 }
8036 assert!(par.batches_are_body_disjoint());
8037 }
8038
8039 #[cfg(feature = "parallel")]
8040 #[test]
8041 fn parallel_step_rebuilds_when_body_becomes_dynamic_after_coloring() {
8042 let mut world = hub_world(RigidBody::new_static(Vec3Fix::ZERO), 70);
8046 world.rebuild_batches();
8047 assert_eq!(world.num_batches(), 1);
8048 world.bodies[0].inv_mass = Fix128::ONE;
8049 world.solve_constraints_batched(Fix128::from_ratio(1, 60));
8050 assert_eq!(world.num_batches(), 70);
8051 assert!(world.batches_are_body_disjoint());
8052 }
8053
8054 fn quiet_world() -> PhysicsWorld {
8063 let config = SolverConfig {
8064 gravity: Vec3Fix::ZERO,
8065 damping: Fix128::ONE,
8066 ..SolverConfig::default()
8067 };
8068 PhysicsWorld::new(config)
8069 }
8070
8071 fn v3(x: i64, y: i64, z: i64) -> Vec3Fix {
8072 Vec3Fix::from_int(x, y, z)
8073 }
8074
8075 fn r(n: i64, d: i64) -> Fix128 {
8076 Fix128::from_ratio(n, d)
8077 }
8078
8079 #[test]
8080 fn update_velocities_linear_velocity_is_position_delta_times_inv_dt() {
8081 let mut world = quiet_world();
8082 let dyn_idx = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8083 let static_idx = world.add_body(RigidBody::new_static(v3(5, 5, 5)));
8084 let dt = r(1, 4); world.bodies[dyn_idx].prev_position = v3(1, 1, 1);
8088 world.bodies[dyn_idx].position = Vec3Fix::new(
8089 Fix128::ONE + r(1, 4),
8090 Fix128::ONE - r(1, 2),
8091 Fix128::ONE + r(3, 4),
8092 );
8093 world.bodies[static_idx].prev_position = v3(0, 0, 0);
8095 world.bodies[static_idx].position = v3(5, 5, 5);
8096
8097 world.update_velocities(dt);
8098
8099 assert_eq!(world.bodies[dyn_idx].velocity, v3(1, -2, 3));
8100 assert_eq!(world.bodies[static_idx].velocity, Vec3Fix::ZERO);
8101 }
8102
8103 #[test]
8104 fn update_velocities_angular_velocity_from_rotation_delta_positive_w() {
8105 let mut world = quiet_world();
8106 let idx = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8107 let dt = r(1, 4);
8108 let q = QuatFix::from_axis_angle(v3(1, 2, 3), r(1, 2));
8110 world.bodies[idx].prev_rotation = QuatFix::IDENTITY;
8111 world.bodies[idx].rotation = q;
8112
8113 world.update_velocities(dt);
8114
8115 let got = world.bodies[idx].angular_velocity;
8117 let n = crate::det_math::sqrt64(14.0);
8118 let want = [2.0 / n, 4.0 / n, 6.0 / n];
8119 for (g, e) in [got.x, got.y, got.z].iter().zip(want) {
8120 assert!((g.to_f64() - e).abs() < 1e-12, "{} vs {e}", g.to_f64());
8121 }
8122 }
8123
8124 #[test]
8125 fn update_velocities_angular_velocity_negative_w_flips_sign() {
8126 let mut world = quiet_world();
8127 let idx = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8128 let dt = r(1, 4);
8129 let theta = Fix128::PI + Fix128::HALF_PI;
8131 let q = QuatFix::from_axis_angle(v3(1, -2, 3), theta);
8132 assert!(q.w < Fix128::ZERO, "test precondition: w must be negative");
8133 world.bodies[idx].prev_rotation = QuatFix::IDENTITY;
8134 world.bodies[idx].rotation = q;
8135
8136 world.update_velocities(dt);
8137
8138 let got = world.bodies[idx].angular_velocity;
8142 let n = crate::det_math::sqrt64(14.0);
8143 let k = 2.0 * core::f64::consts::PI / n;
8144 let want = [-k, 2.0 * k, -3.0 * k];
8145 for (g, e) in [got.x, got.y, got.z].iter().zip(want) {
8146 assert!((g.to_f64() - e).abs() < 1e-12, "{} vs {e}", g.to_f64());
8147 }
8148 }
8149
8150 #[test]
8151 fn update_velocities_angular_velocity_zero_w_takes_positive_branch() {
8152 let mut world = quiet_world();
8153 let idx = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8154 let dt = r(1, 4);
8155 let q = QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
8157 world.bodies[idx].prev_rotation = QuatFix::IDENTITY;
8158 world.bodies[idx].rotation = q;
8159
8160 world.update_velocities(dt);
8161
8162 let got = world.bodies[idx].angular_velocity;
8164 assert_eq!((got.x, got.y), (Fix128::ZERO, Fix128::ZERO));
8165 let want = 4.0 * core::f64::consts::PI;
8166 assert!(
8167 (got.z.to_f64() - want).abs() < 1e-12,
8168 "{} vs {want}",
8169 got.z.to_f64()
8170 );
8171 }
8172
8173 fn push_contact(
8175 world: &mut PhysicsWorld,
8176 a: usize,
8177 b: usize,
8178 friction: Fix128,
8179 restitution: Fix128,
8180 ) {
8181 let contact = Contact {
8182 depth: r(1, 100),
8183 normal: v3(1, 0, 0),
8184 point_a: Vec3Fix::ZERO,
8185 point_b: Vec3Fix::ZERO,
8186 };
8187 let mut c = ContactConstraint::new(a, b, contact);
8188 c.friction = friction;
8189 c.restitution = restitution;
8190 world.contact_constraints.push(c);
8191 }
8192
8193 fn give_velocity(world: &mut PhysicsWorld, idx: usize, v: Vec3Fix) {
8198 let b = &mut world.bodies[idx];
8199 b.velocity = v;
8200 b.prev_position = b.position - v * r(1, 4);
8201 b.prev_rotation = b.rotation;
8202 }
8203
8204 fn freeze_positions(world: &mut PhysicsWorld) {
8206 for b in &mut world.bodies {
8207 b.prev_position = b.position;
8208 b.prev_rotation = b.rotation;
8209 }
8210 }
8211
8212 #[test]
8213 fn update_velocities_restitution_splits_impulse_by_inverse_mass() {
8214 let mut world = quiet_world();
8215 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8216 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8217 push_contact(&mut world, a, b, Fix128::ZERO, r(1, 2));
8218 freeze_positions(&mut world);
8219 give_velocity(&mut world, a, v3(-1, 0, 0));
8221
8222 world.update_velocities(r(1, 4));
8223
8224 assert_eq!(
8226 world.bodies[a].velocity,
8227 Vec3Fix::new(-r(1, 4), Fix128::ZERO, Fix128::ZERO)
8228 );
8229 assert_eq!(
8230 world.bodies[b].velocity,
8231 Vec3Fix::new(-r(3, 4), Fix128::ZERO, Fix128::ZERO)
8232 );
8233 }
8234
8235 #[test]
8236 fn update_velocities_restitution_skips_separating_contact() {
8237 let mut world = quiet_world();
8238 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8239 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8240 push_contact(&mut world, a, b, Fix128::ZERO, r(1, 2));
8241 freeze_positions(&mut world);
8242 give_velocity(&mut world, a, v3(1, 0, 0));
8244
8245 world.update_velocities(r(1, 4));
8246
8247 assert_eq!(world.bodies[a].velocity, v3(1, 0, 0));
8248 assert_eq!(world.bodies[b].velocity, Vec3Fix::ZERO);
8249 }
8250
8251 #[test]
8252 fn update_velocities_restitution_uses_mass_ratio() {
8253 let mut world = quiet_world();
8254 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8256 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8257 world.bodies[b].inv_mass = Fix128::from_int(3);
8258 push_contact(&mut world, a, b, Fix128::ZERO, Fix128::ZERO);
8259 freeze_positions(&mut world);
8260 give_velocity(&mut world, a, v3(-2, 0, 0)); world.update_velocities(r(1, 4));
8263
8264 assert_eq!(
8266 world.bodies[a].velocity,
8267 Vec3Fix::new(-r(3, 2), Fix128::ZERO, Fix128::ZERO)
8268 );
8269 assert_eq!(
8270 world.bodies[b].velocity,
8271 Vec3Fix::new(-r(3, 2), Fix128::ZERO, Fix128::ZERO)
8272 );
8273 }
8274
8275 #[test]
8276 fn update_velocities_friction_clamped_by_coulomb_limit() {
8277 let mut world = quiet_world();
8278 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8279 let b = world.add_body(RigidBody::new_static(v3(1, 0, 0))); push_contact(&mut world, a, b, r(1, 2), Fix128::ZERO);
8281 world.contact_constraints[0].cached_lambda = Fix128::ONE;
8283 freeze_positions(&mut world);
8284 give_velocity(&mut world, a, v3(4, 3, 0));
8286
8287 world.update_velocities(r(1, 4));
8288
8289 assert_eq!(
8291 world.bodies[a].velocity,
8292 Vec3Fix::new(Fix128::from_int(4), Fix128::ONE, Fix128::ZERO)
8293 );
8294 assert_eq!(world.bodies[b].velocity, Vec3Fix::ZERO);
8295 }
8296
8297 #[test]
8298 fn update_velocities_friction_below_limit_removes_all_tangential_velocity() {
8299 let mut world = quiet_world();
8300 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8301 let b = world.add_body(RigidBody::new_static(v3(1, 0, 0)));
8302 push_contact(&mut world, a, b, Fix128::ONE, Fix128::ZERO);
8303 world.contact_constraints[0].cached_lambda = Fix128::ONE;
8305 freeze_positions(&mut world);
8306 give_velocity(&mut world, a, v3(4, 0, 3));
8307
8308 world.update_velocities(r(1, 4));
8309
8310 assert_eq!(
8312 world.bodies[a].velocity,
8313 Vec3Fix::new(Fix128::from_int(4), Fix128::ZERO, Fix128::ZERO)
8314 );
8315 }
8316
8317 #[test]
8318 fn update_velocities_friction_boundary_equal_to_limit_uses_limit() {
8319 let mut world = quiet_world();
8320 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8321 let b = world.add_body(RigidBody::new_static(v3(1, 0, 0)));
8322 push_contact(&mut world, a, b, Fix128::ONE, Fix128::ZERO);
8323 world.contact_constraints[0].cached_lambda = r(3, 4);
8325 freeze_positions(&mut world);
8326 give_velocity(&mut world, a, v3(3, 3, 0));
8328
8329 world.update_velocities(r(1, 4));
8330
8331 assert_eq!(
8332 world.bodies[a].velocity,
8333 Vec3Fix::new(Fix128::from_int(3), Fix128::ZERO, Fix128::ZERO)
8334 );
8335 }
8336
8337 #[test]
8338 fn update_velocities_sensor_on_either_side_skips_response() {
8339 for sensor_side in 0..2 {
8340 let mut world = quiet_world();
8341 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8342 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8343 world.bodies[if sensor_side == 0 { a } else { b }].is_sensor = true;
8344 push_contact(&mut world, a, b, r(1, 2), r(1, 2));
8345 freeze_positions(&mut world);
8346 give_velocity(&mut world, a, v3(-1, 2, 0));
8347
8348 world.update_velocities(r(1, 4));
8349
8350 assert_eq!(
8351 world.bodies[a].velocity,
8352 v3(-1, 2, 0),
8353 "sensor side {sensor_side}"
8354 );
8355 assert_eq!(
8356 world.bodies[b].velocity,
8357 Vec3Fix::ZERO,
8358 "sensor side {sensor_side}"
8359 );
8360 }
8361 }
8362
8363 #[test]
8364 fn update_velocities_two_static_bodies_are_skipped() {
8365 let mut world = quiet_world();
8366 let a = world.add_body(RigidBody::new_static(v3(0, 0, 0)));
8367 let b = world.add_body(RigidBody::new_static(v3(1, 0, 0)));
8368 push_contact(&mut world, a, b, Fix128::ONE, Fix128::ONE);
8369 freeze_positions(&mut world);
8371 world.bodies[a].velocity = v3(-1, 1, 0);
8372
8373 world.update_velocities(r(1, 4));
8374
8375 assert_eq!(world.bodies[a].velocity, v3(-1, 1, 0));
8376 }
8377
8378 fn sphere_world(positions: &[Vec3Fix]) -> PhysicsWorld {
8382 let mut world = quiet_world();
8383 for &p in positions {
8384 world.add_body_with_radius(RigidBody::new_static(p), Fix128::from_int(2));
8385 }
8386 world
8387 }
8388
8389 #[test]
8390 fn raycast_hit_distance_is_exact_closed_form() {
8391 let world = sphere_world(&[v3(10, 0, 0)]);
8393 let hit = world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100));
8394 assert_eq!(hit, Some((0, Fix128::from_int(8))));
8395 let hit2 = world.raycast(Vec3Fix::ZERO, v3(3, 0, 0), Fix128::from_int(100));
8397 assert_eq!(hit2, Some((0, Fix128::from_int(8))));
8398 }
8399
8400 #[test]
8401 fn raycast_along_every_axis_and_direction() {
8402 for (dir, pos) in [
8404 (v3(1, 0, 0), v3(10, 0, 0)),
8405 (v3(-1, 0, 0), v3(-10, 0, 0)),
8406 (v3(0, 1, 0), v3(0, 10, 0)),
8407 (v3(0, -1, 0), v3(0, -10, 0)),
8408 (v3(0, 0, 1), v3(0, 0, 10)),
8409 (v3(0, 0, -1), v3(0, 0, -10)),
8410 ] {
8411 let world = sphere_world(&[pos]);
8412 assert_eq!(
8413 world.raycast(Vec3Fix::ZERO, dir, Fix128::from_int(100)),
8414 Some((0, Fix128::from_int(8))),
8415 "dir {dir:?}"
8416 );
8417 assert_eq!(
8419 world.raycast(
8420 Vec3Fix::ZERO,
8421 dir * Fix128::from_int(-1),
8422 Fix128::from_int(100)
8423 ),
8424 None
8425 );
8426 }
8427 }
8428
8429 #[test]
8430 fn raycast_origin_on_surface_hits_at_zero_not_far_side() {
8431 let world = sphere_world(&[v3(10, 0, 0)]);
8433 let hit = world.raycast(v3(8, 0, 0), v3(1, 0, 0), Fix128::from_int(100));
8434 assert_eq!(hit, Some((0, Fix128::ZERO)));
8435 }
8436
8437 #[test]
8438 fn raycast_from_inside_sphere_uses_far_intersection() {
8439 let world = sphere_world(&[v3(10, 0, 0)]);
8441 let hit = world.raycast(v3(10, 0, 0), v3(1, 0, 0), Fix128::from_int(100));
8442 assert_eq!(hit, Some((0, Fix128::from_int(2))));
8443 }
8444
8445 #[test]
8446 fn raycast_max_distance_boundary_inclusive() {
8447 let world = sphere_world(&[v3(10, 0, 0)]);
8448 assert_eq!(
8450 world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(8)),
8451 Some((0, Fix128::from_int(8)))
8452 );
8453 assert_eq!(
8455 world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(7)),
8456 None
8457 );
8458 assert_eq!(
8461 world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(9)),
8462 Some((0, Fix128::from_int(8)))
8463 );
8464 }
8465
8466 #[test]
8467 fn raycast_returns_nearest_regardless_of_body_order() {
8468 let near = v3(10, 0, 0);
8470 let far = v3(20, 0, 0);
8471 let w1 = sphere_world(&[far, near]);
8472 assert_eq!(
8473 w1.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8474 Some((1, Fix128::from_int(8)))
8475 );
8476 let w2 = sphere_world(&[near, far]);
8477 assert_eq!(
8478 w2.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8479 Some((0, Fix128::from_int(8)))
8480 );
8481 }
8482
8483 #[test]
8484 fn raycast_misses_sphere_beside_the_ray() {
8485 let world = sphere_world(&[v3(10, 5, 0)]);
8487 assert_eq!(
8488 world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8489 None
8490 );
8491 let tangent = sphere_world(&[v3(10, 2, 0)]);
8493 assert_eq!(
8494 tangent.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8495 Some((0, Fix128::from_int(10)))
8496 );
8497 }
8498
8499 #[test]
8500 fn raycast_zero_direction_and_no_collidable_bodies_return_none() {
8501 let world = sphere_world(&[v3(10, 0, 0)]);
8502 assert_eq!(
8503 world.raycast(Vec3Fix::ZERO, Vec3Fix::ZERO, Fix128::from_int(100)),
8504 None
8505 );
8506 let mut no_radius = quiet_world();
8507 no_radius.add_body(RigidBody::new_static(v3(10, 0, 0)));
8508 assert_eq!(
8509 no_radius.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8510 None
8511 );
8512 let mut cleared = sphere_world(&[v3(10, 0, 0)]);
8514 cleared.clear_body_collision_radius(0);
8515 assert_eq!(
8516 cleared.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8517 None
8518 );
8519 cleared.set_body_collision_radius(0, Fix128::from_int(2));
8521 assert_eq!(
8522 cleared.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8523 Some((0, Fix128::from_int(8)))
8524 );
8525 }
8526
8527 fn dc(a: usize, b: usize) -> DistanceConstraint {
8530 DistanceConstraint {
8531 body_a: a,
8532 body_b: b,
8533 local_anchor_a: Vec3Fix::ZERO,
8534 local_anchor_b: Vec3Fix::ZERO,
8535 target_distance: Fix128::ONE,
8536 compliance: Fix128::ZERO,
8537 cached_lambda: Fix128::ZERO,
8538 }
8539 }
8540
8541 fn three_body_world_with_all_pairs() -> PhysicsWorld {
8542 let mut world = quiet_world();
8543 for i in 0..3 {
8544 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
8545 }
8546 world.add_distance_constraint(dc(0, 1));
8547 world.add_distance_constraint(dc(1, 2));
8548 world.add_distance_constraint(dc(0, 2));
8549 world.contact_constraints.push(ContactConstraint::new(
8550 0,
8551 1,
8552 Contact {
8553 depth: Fix128::ZERO,
8554 normal: v3(1, 0, 0),
8555 point_a: Vec3Fix::ZERO,
8556 point_b: Vec3Fix::ZERO,
8557 },
8558 ));
8559 world.contact_constraints.push(ContactConstraint::new(
8560 1,
8561 2,
8562 Contact {
8563 depth: Fix128::ZERO,
8564 normal: v3(1, 0, 0),
8565 point_a: Vec3Fix::ZERO,
8566 point_b: Vec3Fix::ZERO,
8567 },
8568 ));
8569 world.contact_constraints.push(ContactConstraint::new(
8570 0,
8571 2,
8572 Contact {
8573 depth: Fix128::ZERO,
8574 normal: v3(1, 0, 0),
8575 point_a: Vec3Fix::ZERO,
8576 point_b: Vec3Fix::ZERO,
8577 },
8578 ));
8579 world
8580 }
8581
8582 fn dist_pairs(world: &PhysicsWorld) -> Vec<(usize, usize)> {
8583 world
8584 .distance_constraints
8585 .iter()
8586 .map(|c| (c.body_a, c.body_b))
8587 .collect()
8588 }
8589
8590 fn contact_pairs(world: &PhysicsWorld) -> Vec<(usize, usize)> {
8591 world
8592 .contact_constraints
8593 .iter()
8594 .map(|c| (c.body_a, c.body_b))
8595 .collect()
8596 }
8597
8598 #[test]
8599 fn remove_body_middle_drops_its_constraints_and_remaps_last() {
8600 let mut world = three_body_world_with_all_pairs();
8601 let removed = world.remove_body(1).expect("index 1 exists");
8602 assert_eq!(removed.position, v3(1, 0, 0));
8603 assert_eq!(world.bodies.len(), 2);
8604 assert_eq!(world.bodies[1].position, v3(2, 0, 0));
8606 assert_eq!(dist_pairs(&world), vec![(0, 1)]);
8608 assert_eq!(contact_pairs(&world), vec![(0, 1)]);
8609 assert_eq!(world.body_materials.len(), 2);
8610 assert_eq!(world.body_collision_radii.len(), 2);
8611 assert_eq!(world.body_filters.len(), 2);
8612 }
8613
8614 #[test]
8615 fn remove_body_first_remaps_last_into_slot_zero() {
8616 let mut world = three_body_world_with_all_pairs();
8617 world.remove_body(0).expect("index 0 exists");
8618 assert_eq!(world.bodies[0].position, v3(2, 0, 0));
8619 assert_eq!(world.bodies[1].position, v3(1, 0, 0));
8620 assert_eq!(dist_pairs(&world), vec![(1, 0)]);
8622 assert_eq!(contact_pairs(&world), vec![(1, 0)]);
8623 }
8624
8625 #[test]
8626 fn remove_body_last_needs_no_remap() {
8627 let mut world = three_body_world_with_all_pairs();
8628 world.remove_body(2).expect("index 2 exists");
8629 assert_eq!(world.bodies.len(), 2);
8630 assert_eq!(world.bodies[1].position, v3(1, 0, 0));
8631 assert_eq!(dist_pairs(&world), vec![(0, 1)]);
8632 assert_eq!(contact_pairs(&world), vec![(0, 1)]);
8633 }
8634
8635 #[test]
8636 fn remove_body_out_of_range_is_none_and_leaves_world_untouched() {
8637 let mut world = three_body_world_with_all_pairs();
8638 assert!(world.remove_body(3).is_none());
8639 assert!(world.remove_body(usize::MAX).is_none());
8640 assert_eq!(world.bodies.len(), 3);
8641 assert_eq!(dist_pairs(&world), vec![(0, 1), (1, 2), (0, 2)]);
8642 }
8643
8644 #[test]
8645 fn remove_body_never_leaves_self_or_dangling_references() {
8646 for victim in 0..4 {
8648 let mut world = quiet_world();
8649 for i in 0..4 {
8650 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
8651 }
8652 for a in 0..4 {
8653 for b in (a + 1)..4 {
8654 world.add_distance_constraint(dc(a, b));
8655 }
8656 }
8657 world.remove_body(victim).expect("in range");
8658 let n = world.bodies.len();
8659 assert_eq!(n, 3);
8660 assert_eq!(world.distance_constraints.len(), 3, "victim {victim}");
8662 for c in &world.distance_constraints {
8663 assert!(
8664 c.body_a < n && c.body_b < n,
8665 "victim {victim}: dangling {:?}",
8666 (c.body_a, c.body_b)
8667 );
8668 assert_ne!(c.body_a, c.body_b, "victim {victim}: self constraint");
8669 }
8670 let mut xs: Vec<i64> = world.bodies.iter().map(|b| b.position.x.hi).collect();
8672 xs.sort_unstable();
8673 let mut expected: Vec<i64> = (0..4).filter(|&i| i != victim as i64).collect();
8674 expected.sort_unstable();
8675 assert_eq!(xs, expected);
8676 }
8677 }
8678
8679 #[test]
8680 fn remove_body_with_joint_drops_joint_and_rebuilds_islands() {
8681 let mut world = quiet_world();
8682 for i in 0..3 {
8683 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
8684 }
8685 let j = crate::joint::BallJoint::new(1, 2, Vec3Fix::ZERO, Vec3Fix::ZERO);
8686 world.add_joint(Joint::Ball(j));
8687 let j2 = crate::joint::BallJoint::new(0, 2, Vec3Fix::ZERO, Vec3Fix::ZERO);
8688 world.add_joint(Joint::Ball(j2));
8689 assert_eq!(world.joint_count(), 2);
8690 world.remove_body(1).expect("in range");
8691 assert_eq!(world.joint_count(), 1);
8693 assert_eq!(world.joints[0].bodies(), (0, 1));
8694 assert_eq!(world.islands.find(0), world.islands.find(1));
8696 }
8697
8698 fn near(a: Fix128, b: Fix128) -> bool {
8702 let d = (a - b).abs();
8703 d.hi == 0 && d.lo < (1u64 << 4)
8704 }
8705
8706 fn assert_near_vec(a: Vec3Fix, b: Vec3Fix, what: &str) {
8707 assert!(
8708 near(a.x, b.x) && near(a.y, b.y) && near(a.z, b.z),
8709 "{what}: {a:?} vs {b:?}"
8710 );
8711 }
8712
8713 fn two_spheres(dist_num: i64, dist_den: i64, r_a: Fix128, r_b: Fix128) -> PhysicsWorld {
8715 let mut world = quiet_world();
8716 world.add_body_with_radius(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE), r_a);
8717 world.add_body_with_radius(
8718 RigidBody::new_dynamic(
8719 Vec3Fix::new(r(dist_num, dist_den), Fix128::ZERO, Fix128::ZERO),
8720 Fix128::ONE,
8721 ),
8722 r_b,
8723 );
8724 world
8725 }
8726
8727 #[test]
8728 fn detect_collisions_overlapping_spheres_produce_exact_contact() {
8729 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8733 world.detect_collisions();
8734 assert_eq!(world.contact_constraints.len(), 1);
8735 let c = &world.contact_constraints[0];
8736 assert_eq!((c.body_a, c.body_b), (0, 1));
8737 assert!(
8738 near(c.contact.depth, r(1, 2)),
8739 "depth {:?}",
8740 c.contact.depth
8741 );
8742 assert_near_vec(c.contact.normal, v3(-1, 0, 0), "normal");
8743 assert_near_vec(c.contact.point_a, v3(1, 0, 0), "point_a");
8744 assert_near_vec(
8745 c.contact.point_b,
8746 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO),
8747 "point_b",
8748 );
8749 assert_eq!(world.contact_events().len(), 1);
8751 assert!(world.trigger_events().is_empty());
8752 }
8753
8754 #[test]
8755 fn detect_collisions_asymmetric_radii_split_points_correctly() {
8756 let mut world = two_spheres(2, 1, Fix128::from_int(2), r(1, 2));
8758 world.detect_collisions();
8759 assert_eq!(world.contact_constraints.len(), 1);
8760 let c = world.contact_constraints[0].contact;
8761 assert!(near(c.depth, r(1, 2)), "depth {:?}", c.depth);
8762 assert_near_vec(c.point_a, v3(2, 0, 0), "point_a"); assert_near_vec(
8764 c.point_b,
8765 Vec3Fix::new(r(3, 2), Fix128::ZERO, Fix128::ZERO),
8766 "point_b",
8767 ); }
8769
8770 #[test]
8771 fn detect_collisions_touching_exactly_is_not_a_contact() {
8772 let mut world = two_spheres(2, 1, Fix128::ONE, Fix128::ONE);
8774 world.detect_collisions();
8775 assert!(world.contact_constraints.is_empty());
8776 assert!(world.contact_events().is_empty());
8777 let mut world2 = two_spheres(199, 100, Fix128::ONE, Fix128::ONE);
8779 world2.detect_collisions();
8780 assert_eq!(world2.contact_constraints.len(), 1);
8781 }
8782
8783 #[test]
8784 fn detect_collisions_coincident_centers_are_skipped() {
8785 let mut world = two_spheres(0, 1, Fix128::ONE, Fix128::ONE);
8787 world.detect_collisions();
8788 assert!(world.contact_constraints.is_empty());
8789 }
8790
8791 #[test]
8792 fn detect_collisions_relative_velocity_is_reported_along_normal() {
8793 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8794 world.bodies[0].velocity = v3(2, 7, 0); world.bodies[1].velocity = v3(-1, 0, 0);
8796 world.detect_collisions();
8797 let ev = &world.contact_events()[0];
8798 assert!(
8800 near(ev.relative_velocity, Fix128::from_int(-3)),
8801 "rel_vel {:?}",
8802 ev.relative_velocity
8803 );
8804 assert!(near(ev.depth, r(1, 2)), "depth {:?}", ev.depth);
8805 }
8806
8807 #[test]
8808 fn detect_collisions_sensor_reports_trigger_instead_of_constraint() {
8809 for sensor_side in 0..2 {
8810 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8811 world.bodies[sensor_side].is_sensor = true;
8812 world.detect_collisions();
8813 assert!(world.contact_constraints.is_empty(), "side {sensor_side}");
8814 assert_eq!(world.trigger_events().len(), 1, "side {sensor_side}");
8815 assert_eq!(world.contact_events().len(), 1, "side {sensor_side}");
8816 }
8817 }
8818
8819 #[test]
8820 fn detect_collisions_static_static_pair_is_skipped_but_static_dynamic_is_not() {
8821 let mut world = quiet_world();
8822 world.add_body_with_radius(RigidBody::new_static(Vec3Fix::ZERO), Fix128::ONE);
8823 world.add_body_with_radius(
8824 RigidBody::new_static(Vec3Fix::new(r(3, 2), Fix128::ZERO, Fix128::ZERO)),
8825 Fix128::ONE,
8826 );
8827 world.detect_collisions();
8828 assert!(world.contact_constraints.is_empty());
8829
8830 let mut mixed = quiet_world();
8831 mixed.add_body_with_radius(RigidBody::new_static(Vec3Fix::ZERO), Fix128::ONE);
8832 mixed.add_body_with_radius(
8833 RigidBody::new_dynamic(
8834 Vec3Fix::new(r(3, 2), Fix128::ZERO, Fix128::ZERO),
8835 Fix128::ONE,
8836 ),
8837 Fix128::ONE,
8838 );
8839 mixed.detect_collisions();
8840 assert_eq!(mixed.contact_constraints.len(), 1);
8841 }
8842
8843 #[test]
8844 fn detect_collisions_filter_blocks_same_group_and_masked_layers() {
8845 let mut same_group = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8847 let g = CollisionFilter {
8848 layer: 1,
8849 mask: u32::MAX,
8850 group: 7,
8851 };
8852 same_group.set_body_filter(0, g);
8853 same_group.set_body_filter(1, g);
8854 same_group.detect_collisions();
8855 assert!(same_group.contact_constraints.is_empty());
8856
8857 for side in 0..2 {
8859 let mut masked = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8860 masked.set_body_filter(
8861 side,
8862 CollisionFilter {
8863 layer: 1 << 5,
8864 mask: u32::MAX,
8865 group: 0,
8866 },
8867 );
8868 masked.set_body_filter(
8869 1 - side,
8870 CollisionFilter {
8871 layer: 1,
8872 mask: !(1 << 5),
8873 group: 0,
8874 },
8875 );
8876 masked.detect_collisions();
8877 assert!(masked.contact_constraints.is_empty(), "side {side}");
8878 }
8879
8880 let mut ok = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8882 ok.set_body_filter(
8883 0,
8884 CollisionFilter {
8885 layer: 1,
8886 mask: u32::MAX,
8887 group: 1,
8888 },
8889 );
8890 ok.set_body_filter(
8891 1,
8892 CollisionFilter {
8893 layer: 1,
8894 mask: u32::MAX,
8895 group: 2,
8896 },
8897 );
8898 ok.detect_collisions();
8899 assert_eq!(ok.contact_constraints.len(), 1);
8900 }
8901
8902 #[test]
8903 fn detect_collisions_both_sleeping_skipped_one_sleeping_wakes() {
8904 let mut both = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8905 both.islands.sleep_data[0].state = crate::sleeping::SleepState::Sleeping;
8906 both.islands.sleep_data[1].state = crate::sleeping::SleepState::Sleeping;
8907 both.detect_collisions();
8908 assert!(both.contact_constraints.is_empty());
8909
8910 let mut one = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8911 one.islands.sleep_data[0].state = crate::sleeping::SleepState::Sleeping;
8912 one.detect_collisions();
8913 assert_eq!(one.contact_constraints.len(), 1);
8914 assert!(!one.islands.is_sleeping(0));
8916 }
8917
8918 #[test]
8919 fn detect_collisions_needs_two_collidable_bodies() {
8920 let mut single = quiet_world();
8922 single.add_body_with_radius(
8923 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE),
8924 Fix128::ONE,
8925 );
8926 single.detect_collisions();
8927 assert!(single.contact_constraints.is_empty());
8928
8929 let mut one_radius = quiet_world();
8930 one_radius.add_body_with_radius(
8931 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE),
8932 Fix128::ONE,
8933 );
8934 one_radius.add_body(RigidBody::new_dynamic(
8935 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO),
8936 Fix128::ONE,
8937 ));
8938 one_radius.detect_collisions();
8939 assert!(one_radius.contact_constraints.is_empty());
8940 }
8941
8942 #[test]
8943 fn detect_collisions_uses_combined_material_of_pair() {
8944 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8945 world.detect_collisions();
8946 let c = world.contact_constraints[0];
8947 let combined = world.combined_material(0, 1);
8948 assert_eq!(c.friction, combined.friction);
8949 assert_eq!(c.restitution, combined.restitution);
8950 }
8951
8952 #[test]
8955 fn solve_distance_constraints_rigid_splits_error_by_inverse_mass() {
8956 let mut world = quiet_world();
8959 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8960 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
8961 world.bodies[b].inv_mass = Fix128::from_int(3);
8962 let mut c = dc(a, b);
8963 c.target_distance = Fix128::from_int(2);
8964 world.add_distance_constraint(c);
8965
8966 world.solve_distance_constraints(r(1, 4));
8967
8968 assert_eq!(
8969 world.bodies[a].position,
8970 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
8971 );
8972 assert_eq!(
8973 world.bodies[b].position,
8974 Vec3Fix::new(r(5, 2), Fix128::ZERO, Fix128::ZERO)
8975 );
8976 assert_eq!(world.distance_constraints[0].cached_lambda, r(1, 2));
8977 }
8978
8979 #[test]
8980 fn solve_distance_constraints_static_side_does_not_move() {
8981 let mut world = quiet_world();
8983 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
8984 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
8985 world.add_distance_constraint(dc(a, b)); world.solve_distance_constraints(r(1, 4));
8988
8989 assert_eq!(world.bodies[a].position, Vec3Fix::ZERO);
8990 assert_eq!(world.bodies[b].position, v3(1, 0, 0));
8991 }
8992
8993 #[test]
8994 fn solve_distance_constraints_negative_error_pushes_apart() {
8995 let mut world = quiet_world();
8997 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8998 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8999 let mut c = dc(a, b);
9000 c.target_distance = Fix128::from_int(3);
9001 world.add_distance_constraint(c);
9002
9003 world.solve_distance_constraints(r(1, 4));
9004
9005 assert_eq!(world.bodies[a].position, v3(-1, 0, 0));
9006 assert_eq!(world.bodies[b].position, v3(2, 0, 0));
9007 }
9008
9009 #[test]
9010 fn solve_distance_constraints_compliance_softens_correction() {
9011 let mut world = quiet_world();
9014 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9015 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
9016 let mut c = dc(a, b);
9017 c.target_distance = Fix128::from_int(2);
9018 c.compliance = r(1, 16);
9019 world.add_distance_constraint(c);
9020
9021 world.solve_distance_constraints(r(1, 4));
9022
9023 assert_eq!(world.bodies[b].position, v3(3, 0, 0));
9024 assert_eq!(world.distance_constraints[0].cached_lambda, Fix128::ONE);
9025 }
9026
9027 #[test]
9028 fn solve_distance_constraints_accumulates_lambda_xpbd() {
9029 let mut world = quiet_world();
9033 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9034 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
9035 let mut c = dc(a, b);
9036 c.target_distance = Fix128::from_int(2);
9037 c.compliance = r(1, 16);
9038 c.cached_lambda = Fix128::ONE;
9039 world.add_distance_constraint(c);
9040
9041 world.solve_distance_constraints(r(1, 4));
9042
9043 assert_eq!(
9044 world.bodies[b].position,
9045 Vec3Fix::new(r(7, 2), Fix128::ZERO, Fix128::ZERO)
9046 );
9047 assert_eq!(world.distance_constraints[0].cached_lambda, r(3, 2));
9048 }
9049
9050 #[test]
9051 fn reset_lambdas_zeroes_distance_lambda() {
9052 let mut world = quiet_world();
9053 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9054 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
9055 let mut c = dc(a, b);
9056 c.cached_lambda = Fix128::from_int(3);
9057 world.add_distance_constraint(c);
9058 world.reset_lambdas();
9059 assert_eq!(world.distance_constraints[0].cached_lambda, Fix128::ZERO);
9060 }
9061
9062 #[test]
9063 fn xpbd_compliant_extension_is_iteration_independent() {
9064 let run = |iters: usize| {
9067 let mut world = quiet_world();
9068 world.config.gravity = v3(0, -10, 0);
9069 world.config.damping = Fix128::ONE;
9070 world.config.substeps = 4;
9071 world.config.iterations = iters;
9072 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9073 let b = world.add_body(RigidBody::new_dynamic(v3(0, -1, 0), Fix128::ONE));
9074 world.bodies[b].linear_damping = r(9, 10); let mut c = dc(a, b);
9076 c.target_distance = Fix128::ONE;
9077 c.compliance = r(1, 100);
9078 world.add_distance_constraint(c);
9079 for _ in 0..600 {
9080 world.step(r(1, 60));
9081 }
9082 (-world.bodies[b].position.y - Fix128::ONE).to_f64()
9083 };
9084 let ext: Vec<f64> = [1usize, 2, 4, 8, 16].iter().map(|&i| run(i)).collect();
9085 for (i, e) in ext.iter().enumerate() {
9086 assert!(
9087 (e - 0.1).abs() < 0.01,
9088 "iterations index {i}: extension {e} m, expected 0.1 ± 0.01 (all: {ext:?})"
9089 );
9090 }
9091 }
9092
9093 #[test]
9094 fn solve_distance_constraints_anchor_offsets_are_rotated_into_world() {
9095 let mut world = quiet_world();
9098 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9099 let b = world.add_body(RigidBody::new_dynamic(v3(0, 2, 0), Fix128::ONE));
9100 world.bodies[b].rotation =
9101 QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
9102 let mut c = dc(a, b);
9103 c.local_anchor_b = v3(0, 1, 0);
9104 world.add_distance_constraint(c);
9105
9106 world.solve_distance_constraints(r(1, 4));
9107
9108 assert_eq!(world.bodies[b].position, v3(0, 2, 0));
9109 let mut unrotated = quiet_world();
9111 let a2 = unrotated.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9112 let b2 = unrotated.add_body(RigidBody::new_dynamic(v3(0, 2, 0), Fix128::ONE));
9113 let mut c2 = dc(a2, b2);
9114 c2.local_anchor_b = v3(0, 1, 0);
9115 unrotated.add_distance_constraint(c2);
9116 unrotated.solve_distance_constraints(r(1, 4));
9117 assert_near_vec(unrotated.bodies[b2].position, v3(0, 0, 0), "unrotated B");
9118 }
9120
9121 #[test]
9122 fn solve_distance_constraints_skips_coincident_and_double_static() {
9123 let mut world = quiet_world();
9124 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9125 let b = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE)); world.add_distance_constraint(dc(a, b));
9127 world.solve_distance_constraints(r(1, 4));
9128 assert_eq!(world.bodies[a].position, Vec3Fix::ZERO);
9129 assert_eq!(world.bodies[b].position, Vec3Fix::ZERO);
9130 assert_eq!(world.distance_constraints[0].cached_lambda, Fix128::ZERO);
9131
9132 let mut statics = quiet_world();
9133 let s1 = statics.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9134 let s2 = statics.add_body(RigidBody::new_static(v3(5, 0, 0)));
9135 statics.add_distance_constraint(dc(s1, s2));
9136 statics.solve_distance_constraints(r(1, 4));
9137 assert_eq!(statics.bodies[s2].position, v3(5, 0, 0));
9138 assert_eq!(statics.distance_constraints[0].cached_lambda, Fix128::ZERO);
9139 }
9140
9141 fn contact_world(inv_a: Fix128, inv_b: Fix128, depth: Fix128) -> PhysicsWorld {
9144 let mut world = quiet_world();
9145 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9146 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
9147 world.bodies[a].inv_mass = inv_a;
9148 world.bodies[b].inv_mass = inv_b;
9149 let contact = Contact {
9150 depth,
9151 normal: v3(1, 0, 0),
9152 point_a: Vec3Fix::ZERO,
9153 point_b: Vec3Fix::ZERO,
9154 };
9155 world
9156 .contact_constraints
9157 .push(ContactConstraint::new(a, b, contact));
9158 world
9159 }
9160
9161 #[test]
9162 fn solve_contact_constraints_pushes_apart_by_depth_weighted_by_inverse_mass() {
9163 let mut world = contact_world(Fix128::ONE, Fix128::from_int(3), Fix128::ONE);
9165 world.solve_contact_constraints(r(1, 4));
9166 assert_eq!(
9167 world.bodies[0].position,
9168 Vec3Fix::new(r(1, 4), Fix128::ZERO, Fix128::ZERO)
9169 );
9170 assert_eq!(
9171 world.bodies[1].position,
9172 Vec3Fix::new(r(1, 4), Fix128::ZERO, Fix128::ZERO)
9173 );
9174 assert_eq!(world.contact_constraints[0].cached_lambda, Fix128::ONE);
9175 }
9176
9177 #[test]
9178 fn solve_contact_constraints_zero_or_negative_depth_is_skipped() {
9179 for depth in [Fix128::ZERO, -r(1, 2)] {
9180 let mut world = contact_world(Fix128::ONE, Fix128::ONE, depth);
9181 world.solve_contact_constraints(r(1, 4));
9182 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO, "depth {depth:?}");
9183 assert_eq!(world.bodies[1].position, v3(1, 0, 0), "depth {depth:?}");
9184 assert_eq!(world.contact_constraints[0].cached_lambda, Fix128::ZERO);
9185 }
9186 }
9187
9188 #[test]
9189 fn solve_contact_constraints_static_side_absorbs_nothing() {
9190 let mut world = contact_world(Fix128::ZERO, Fix128::ONE, r(1, 2));
9192 world.solve_contact_constraints(r(1, 4));
9193 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO);
9194 assert_eq!(
9195 world.bodies[1].position,
9196 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9197 );
9198 let mut both = contact_world(Fix128::ZERO, Fix128::ZERO, r(1, 2));
9200 both.solve_contact_constraints(r(1, 4));
9201 assert_eq!(both.bodies[1].position, v3(1, 0, 0));
9202 assert_eq!(both.contact_constraints[0].cached_lambda, Fix128::ZERO);
9203 }
9204
9205 #[test]
9206 fn solve_contact_constraints_accumulates_lambda_and_pushes_depth_once() {
9207 let mut world = contact_world(Fix128::ONE, Fix128::ONE, r(1, 2));
9209 world.contact_constraints[0].cached_lambda = Fix128::ONE;
9210 world.solve_contact_constraints(r(1, 4));
9211 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO);
9212 assert_eq!(world.contact_constraints[0].cached_lambda, Fix128::ONE);
9213
9214 let mut edge = contact_world(Fix128::ONE, Fix128::ONE, r(1, 2));
9216 edge.contact_constraints[0].cached_lambda = r(1, 2);
9217 edge.solve_contact_constraints(r(1, 4));
9218 assert_eq!(edge.bodies[0].position, Vec3Fix::ZERO);
9219
9220 let mut partial = contact_world(Fix128::ONE, Fix128::ONE, r(1, 2));
9222 partial.contact_constraints[0].cached_lambda = r(1, 4);
9223 partial.solve_contact_constraints(r(1, 4));
9224 assert_eq!(
9225 partial.bodies[0].position,
9226 Vec3Fix::new(r(1, 8), Fix128::ZERO, Fix128::ZERO)
9227 );
9228 assert_eq!(partial.contact_constraints[0].cached_lambda, r(1, 2));
9229
9230 let mut iters = contact_world(Fix128::ONE, Fix128::ONE, r(1, 2));
9232 for _ in 0..4 {
9233 iters.solve_contact_constraints(r(1, 4));
9234 }
9235 assert_eq!(
9236 iters.bodies[0].position,
9237 Vec3Fix::new(r(1, 4), Fix128::ZERO, Fix128::ZERO)
9238 );
9239 assert_eq!(
9240 iters.bodies[1].position,
9241 Vec3Fix::new(r(3, 4), Fix128::ZERO, Fix128::ZERO)
9242 );
9243 }
9244
9245 #[test]
9246 fn solve_contact_constraints_sensor_skips_response() {
9247 for side in 0..2 {
9248 let mut world = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9249 world.bodies[side].is_sensor = true;
9250 world.solve_contact_constraints(r(1, 4));
9251 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO, "side {side}");
9252 assert_eq!(world.bodies[1].position, v3(1, 0, 0), "side {side}");
9253 }
9254 }
9255
9256 #[cfg(feature = "std")]
9257 #[test]
9258 fn solve_contact_constraints_pre_solve_hook_can_discard_and_modifier_can_rescale() {
9259 let mut vetoed = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9261 vetoed.add_pre_solve_hook(Box::new(|_a, _b, _c| false));
9262 vetoed.apply_contact_filters();
9263 vetoed.solve_contact_constraints(r(1, 4));
9264 assert_eq!(vetoed.bodies[0].position, Vec3Fix::ZERO);
9265
9266 let mut allowed = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9268 allowed.add_pre_solve_hook(Box::new(|_a, _b, _c| true));
9269 allowed.apply_contact_filters();
9270 allowed.solve_contact_constraints(r(1, 4));
9271 assert_eq!(
9272 allowed.bodies[0].position,
9273 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9274 );
9275
9276 struct Halve;
9278 impl ContactModifier for Halve {
9279 fn modify_contact(
9280 &self,
9281 _a: usize,
9282 _b: usize,
9283 c: &mut Contact,
9284 _f: &mut Fix128,
9285 _r: &mut Fix128,
9286 ) -> bool {
9287 c.depth = c.depth.half();
9288 true
9289 }
9290 }
9291 let mut halved = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9292 halved.add_contact_modifier(Box::new(Halve));
9293 halved.apply_contact_filters();
9294 halved.solve_contact_constraints(r(1, 4));
9295 assert_eq!(
9296 halved.bodies[0].position,
9297 Vec3Fix::new(r(1, 4), Fix128::ZERO, Fix128::ZERO)
9298 );
9299
9300 struct Discard;
9302 impl ContactModifier for Discard {
9303 fn modify_contact(
9304 &self,
9305 _a: usize,
9306 _b: usize,
9307 _c: &mut Contact,
9308 _f: &mut Fix128,
9309 _r: &mut Fix128,
9310 ) -> bool {
9311 false
9312 }
9313 }
9314 let mut discarded = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9315 discarded.add_contact_modifier(Box::new(Discard));
9316 discarded.apply_contact_filters();
9317 discarded.solve_contact_constraints(r(1, 4));
9318 assert_eq!(discarded.bodies[0].position, Vec3Fix::ZERO);
9319 }
9320
9321 fn loaded_body() -> RigidBody {
9325 let mut b = RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE);
9326 b.inv_mass = Fix128::from_int(2);
9327 b.inv_inertia = v3(1, 2, 4);
9328 b.velocity = v3(10, 20, 30);
9329 b.angular_velocity = v3(-1, -2, -3);
9330 b
9331 }
9332
9333 #[test]
9334 fn add_force_scales_by_inverse_mass_and_dt() {
9335 let mut b = loaded_body();
9336 b.add_force(v3(4, -8, 16), r(1, 4)); assert_eq!(b.velocity, v3(12, 16, 38));
9338 assert_eq!(b.angular_velocity, v3(-1, -2, -3));
9339 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9340 st.add_force(v3(4, 4, 4), Fix128::ONE);
9341 assert_eq!(st.velocity, Vec3Fix::ZERO);
9342 }
9343
9344 #[test]
9345 fn add_torque_scales_each_axis_by_inverse_inertia_and_dt() {
9346 let mut b = loaded_body();
9347 b.add_torque(v3(8, 8, 8), r(1, 4)); assert_eq!(b.angular_velocity, v3(1, 2, 5));
9349 assert_eq!(b.velocity, v3(10, 20, 30));
9350 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9351 st.add_torque(v3(8, 8, 8), Fix128::ONE);
9352 assert_eq!(st.angular_velocity, Vec3Fix::ZERO);
9353 }
9354
9355 #[test]
9356 fn apply_impulse_scales_by_inverse_mass_only() {
9357 let mut b = loaded_body();
9358 b.apply_impulse(v3(1, 2, 3)); assert_eq!(b.velocity, v3(12, 24, 36));
9360 assert_eq!(b.angular_velocity, v3(-1, -2, -3));
9361 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9362 st.apply_impulse(v3(1, 1, 1));
9363 assert_eq!(st.velocity, Vec3Fix::ZERO);
9364 }
9365
9366 #[test]
9367 fn apply_impulse_at_adds_lever_arm_torque() {
9368 let mut b = loaded_body();
9371 b.apply_impulse_at(v3(0, 3, 0), v3(2, 2, 3));
9372 assert_eq!(b.velocity, v3(10, 26, 30));
9373 assert_eq!(b.angular_velocity, v3(-1, -2, 9));
9374
9375 let mut c = loaded_body();
9377 c.apply_impulse_at(v3(0, 0, 5), v3(1, 3, 3));
9378 assert_eq!(c.angular_velocity, v3(4, -2, -3));
9379 assert_eq!(c.velocity, v3(10, 20, 40));
9380
9381 let mut d = loaded_body();
9383 d.apply_impulse_at(v3(7, 0, 0), v3(1, 2, 4));
9384 assert_eq!(d.angular_velocity, v3(-1, 12, -3));
9385
9386 let mut e = loaded_body();
9388 e.apply_impulse_at(v3(7, 0, 0), v3(1, 2, 3));
9389 assert_eq!(e.angular_velocity, v3(-1, -2, -3));
9390 assert_eq!(e.velocity, v3(24, 20, 30));
9391
9392 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9393 st.apply_impulse_at(v3(1, 1, 1), v3(1, 0, 0));
9394 assert_eq!(st.velocity, Vec3Fix::ZERO);
9395 assert_eq!(st.angular_velocity, Vec3Fix::ZERO);
9396 }
9397
9398 fn snapshot_world() -> PhysicsWorld {
9401 let mut world = quiet_world();
9402 let mut a = RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE);
9403 a.velocity = v3(4, 5, 6);
9404 a.angular_velocity = v3(7, 8, 9);
9405 a.rotation = QuatFix::from_axis_angle(v3(1, 1, 0), r(1, 3));
9406 world.add_body(a);
9407 let mut b = RigidBody::new_dynamic(v3(-1, -2, -3), Fix128::ONE);
9408 b.velocity = Vec3Fix::new(r(1, 3), r(-2, 7), r(5, 11));
9409 world.add_body(b);
9410 world
9411 }
9412
9413 #[test]
9414 fn deserialize_state_roundtrip_restores_every_field_bit_exact() {
9415 let src = snapshot_world();
9416 let bytes = src.serialize_state();
9417 assert_eq!(bytes.len(), 12 + 2 * 208 + 2 * 5 + 1 + 8);
9419 let mut dst = quiet_world();
9420 dst.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9421 dst.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9422 assert!(dst.deserialize_state(&bytes));
9423 for i in 0..2 {
9424 assert_eq!(dst.bodies[i].position, src.bodies[i].position, "pos {i}");
9425 assert_eq!(dst.bodies[i].velocity, src.bodies[i].velocity, "vel {i}");
9426 assert_eq!(dst.bodies[i].rotation, src.bodies[i].rotation, "rot {i}");
9427 assert_eq!(
9428 dst.bodies[i].angular_velocity, src.bodies[i].angular_velocity,
9429 "ang {i}"
9430 );
9431 }
9432 assert_eq!(dst.body_collision_radii.len(), 2);
9434 assert_eq!(dst.body_filters.len(), 2);
9435 assert_eq!(dst.body_materials.len(), 2);
9436 }
9437
9438 #[test]
9439 fn deserialize_state_every_single_byte_flip_changes_some_field() {
9440 let src = snapshot_world();
9450 let bytes = src.serialize_state();
9451 let body_end = 12 + 2 * 208;
9452 let flag_pos = bytes.len() - 9;
9453
9454 for pos in 0..12 {
9456 let mut bad = bytes.clone();
9457 bad[pos] ^= 0x01;
9458 let mut dst = snapshot_world();
9459 assert!(
9460 !dst.deserialize_state(&bad),
9461 "header byte {pos} を壊しても受け付けている (silent に無視される byte がある)"
9462 );
9463 }
9464
9465 {
9470 let pos = flag_pos;
9471 let mut bad = bytes.clone();
9472 bad[pos] ^= 0x01;
9473 let mut dst = snapshot_world();
9474 assert!(dst.deserialize_state(&bad), "flag byte {pos}");
9475 assert_ne!(
9476 dst.overflow_detected(),
9477 src.overflow_detected(),
9478 "flag byte {pos} を壊しても overflow_detected が変わらない"
9479 );
9480 }
9481
9482 for pos in bytes.len() - 8..bytes.len() {
9486 let mut bad = bytes.clone();
9487 bad[pos] ^= 0x01;
9488 let mut dst = snapshot_world();
9489 assert!(
9490 !dst.deserialize_state(&bad),
9491 "fingerprint byte {pos} を壊しても受け付けている"
9492 );
9493 assert_eq!(dst.bodies[0].position, src.bodies[0].position);
9495 }
9496
9497 for pos in body_end..flag_pos {
9499 let mut bad = bytes.clone();
9500 bad[pos] ^= 0x01;
9501 let mut dst = snapshot_world();
9502 assert!(dst.deserialize_state(&bad), "sleep byte {pos}");
9503 let i = (pos - body_end) / 5;
9504 assert_ne!(
9505 dst.islands.sleep_data[i], src.islands.sleep_data[i],
9506 "sleep byte {pos} は body {i} の sleep_data を変えるべき"
9507 );
9508 }
9509
9510 for pos in 12..body_end {
9512 let mut bad = bytes.clone();
9513 bad[pos] ^= 0x01;
9514 let mut dst = snapshot_world();
9515 assert!(dst.deserialize_state(&bad), "byte {pos}");
9516 let body = (pos - 12) / 208;
9517 let field = ((pos - 12) % 208) / 48; let changed_body = (0..2)
9519 .filter(|&i| {
9520 let (s, d) = (&src.bodies[i], &dst.bodies[i]);
9521 s.position != d.position
9522 || s.velocity != d.velocity
9523 || s.rotation != d.rotation
9524 || s.angular_velocity != d.angular_velocity
9525 })
9526 .collect::<Vec<_>>();
9527 assert_eq!(
9528 changed_body,
9529 vec![body],
9530 "byte {pos} must change only body {body}"
9531 );
9532 let (s, d) = (&src.bodies[body], &dst.bodies[body]);
9533 let off = (pos - 12) % 208;
9534 match off {
9535 0..=47 => assert!(
9536 s.position != d.position && s.velocity == d.velocity,
9537 "byte {pos} → position"
9538 ),
9539 48..=95 => assert!(
9540 s.velocity != d.velocity && s.position == d.position,
9541 "byte {pos} → velocity"
9542 ),
9543 96..=159 => assert!(
9544 s.rotation != d.rotation && s.angular_velocity == d.angular_velocity,
9545 "byte {pos} → rotation"
9546 ),
9547 _ => assert!(
9548 s.angular_velocity != d.angular_velocity && s.rotation == d.rotation,
9549 "byte {pos} → angular"
9550 ),
9551 }
9552 let _ = field;
9553 }
9554 }
9555
9556 #[test]
9557 fn deserialize_state_rejects_short_or_mismatched_input() {
9558 let src = snapshot_world();
9559 let bytes = src.serialize_state();
9560 let mut dst = snapshot_world();
9561 assert!(!dst.deserialize_state(&bytes[..11])); assert!(!dst.deserialize_state(&bytes[..12 + 208 + 100])); assert!(
9564 !dst.deserialize_state(&bytes[..bytes.len() - 1]),
9565 "末尾 (v3 の fingerprint) が 1 byte 欠けても拒否する (長さ検査が body 数から一意に決まる)"
9566 );
9567 let mut legacy = 2u32.to_le_bytes().to_vec();
9570 legacy.extend_from_slice(&bytes[12..12 + 2 * 208]);
9571 assert!(
9572 !dst.deserialize_state(&legacy),
9573 "旧 format の blob は magic 不一致で拒否されるべき"
9574 );
9575 let mut v1 = bytes.clone();
9578 v1[4] = 1;
9579 v1.truncate(v1.len() - 1);
9580 assert!(
9581 !dst.deserialize_state(&v1),
9582 "v1 blob は version 不一致で拒否されるべき"
9583 );
9584 let mut one_body = quiet_world();
9585 one_body.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9586 assert!(!one_body.deserialize_state(&bytes)); assert_eq!(one_body.bodies[0].position, Vec3Fix::ZERO);
9589 let mut exact = quiet_world();
9591 exact.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9592 let mut one = PhysicsWorld::STATE_MAGIC.to_vec();
9593 one.extend_from_slice(&PhysicsWorld::STATE_VERSION.to_le_bytes());
9594 one.extend_from_slice(&0u16.to_le_bytes());
9595 one.extend_from_slice(&1u32.to_le_bytes());
9596 one.extend_from_slice(&bytes[12..12 + 208]);
9597 one.extend_from_slice(&bytes[12 + 2 * 208..12 + 2 * 208 + 5]);
9598 one.push(0); one.extend_from_slice(&exact.population_fingerprint().to_le_bytes()); assert!(exact.deserialize_state(&one));
9601 assert_eq!(exact.bodies[0].position, v3(1, 2, 3));
9602 }
9603
9604 #[test]
9607 fn integrate_positions_applies_gravity_damping_and_predicts_position() {
9608 let mut world = quiet_world();
9609 world.config.gravity = v3(0, -8, 0);
9610 world.config.damping = r(1, 2);
9611 let i = world.add_body(RigidBody::new_dynamic(v3(1, 1, 1), Fix128::ONE));
9612 world.bodies[i].velocity = v3(4, 0, 0);
9613 world.bodies[i].gravity_scale = r(1, 2);
9614 world.bodies[i].linear_damping = r(1, 2);
9615 let dt = r(1, 4);
9616
9617 world.integrate_positions(dt);
9618
9619 let b = &world.bodies[i];
9622 assert_eq!(
9623 b.velocity,
9624 Vec3Fix::new(Fix128::from_int(4), -Fix128::ONE, Fix128::ZERO)
9625 );
9626 assert_eq!(
9627 b.position,
9628 Vec3Fix::new(Fix128::from_int(2), r(3, 4), Fix128::ONE)
9629 );
9630 assert_eq!(b.prev_position, v3(1, 1, 1));
9631
9632 world.apply_frame_damping();
9634 assert_eq!(
9635 world.bodies[i].velocity,
9636 Vec3Fix::new(Fix128::ONE, -r(1, 4), Fix128::ZERO)
9637 );
9638 }
9639
9640 #[test]
9641 fn integrate_positions_angular_damping_and_rotation_prediction() {
9642 let mut world = quiet_world();
9643 world.config.damping = Fix128::ONE;
9644 let i = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9645 world.bodies[i].angular_velocity = v3(0, 0, 2);
9646 world.bodies[i].angular_damping = r(1, 2); let dt = r(1, 4);
9648
9649 world.integrate_positions(dt);
9650
9651 let b = &world.bodies[i];
9652 assert_eq!(b.angular_velocity, v3(0, 0, 2));
9654 let expected = QuatFix::from_axis_angle(v3(0, 0, 1), r(1, 2))
9656 .mul(QuatFix::IDENTITY)
9657 .normalize();
9658 assert_eq!(b.rotation, expected);
9659 assert_eq!(b.prev_rotation, QuatFix::IDENTITY);
9660 assert!(b.rotation != QuatFix::IDENTITY);
9661 world.apply_frame_damping();
9662 assert_eq!(world.bodies[i].angular_velocity, v3(0, 0, 1));
9663 }
9664
9665 #[test]
9666 fn apply_frame_damping_skips_static_kinematic_and_sleeping() {
9667 let mut world = quiet_world();
9668 world.config.damping = r(1, 2);
9669 let st = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9670 let mut kin = RigidBody::new_static(Vec3Fix::ZERO);
9671 kin.body_type = BodyType::Kinematic;
9672 let ki = world.add_body(kin);
9673 let dy = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9674 for &i in &[st, ki, dy] {
9675 world.bodies[i].velocity = v3(2, 0, 0);
9676 world.bodies[i].angular_velocity = v3(0, 4, 0);
9677 }
9678 world.apply_frame_damping();
9679 assert_eq!(world.bodies[st].velocity, v3(2, 0, 0));
9680 assert_eq!(world.bodies[ki].velocity, v3(2, 0, 0));
9681 assert_eq!(world.bodies[dy].velocity, v3(1, 0, 0));
9682 assert_eq!(world.bodies[dy].angular_velocity, v3(0, 2, 0));
9683 }
9684
9685 #[test]
9686 fn free_fall_is_substep_independent_with_frame_damping() {
9687 let fall = |substeps: usize| {
9690 let mut world = quiet_world();
9691 world.config.gravity = v3(0, -10, 0);
9692 world.config.damping = Fix128::ONE;
9693 world.config.substeps = substeps;
9694 let b = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9695 for _ in 0..60 {
9696 world.step(r(1, 60));
9697 }
9698 world.bodies[b].position.y.to_f64()
9699 };
9700 let ys: Vec<f64> = [1usize, 2, 4, 8, 16].iter().map(|&n| fall(n)).collect();
9701 for y in &ys {
9702 assert!(
9704 (y + 5.0).abs() < 0.1,
9705 "free fall y = {y}, expected -5.0 ± 0.1 (all: {ys:?})"
9706 );
9707 }
9708 let mut world = PhysicsWorld::new(PhysicsConfig::default());
9710 let b = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9711 for _ in 0..60 {
9712 world.step(r(1, 60));
9713 }
9714 let y = world.bodies[b].position.y.to_f64();
9715 assert!(y < -3.5, "default config fell only to y = {y}");
9716 }
9717
9718 #[test]
9719 fn integrate_positions_static_kinematic_and_sleeping_paths() {
9720 let mut world = quiet_world();
9721 world.config.gravity = v3(0, -8, 0);
9722 let st = world.add_body(RigidBody::new_static(v3(0, 5, 0)));
9723 let mut kin = RigidBody::new_static(v3(0, 0, 0));
9724 kin.body_type = BodyType::Kinematic;
9725 kin.set_kinematic_target(v3(2, 0, 0), QuatFix::IDENTITY);
9726 let ki = world.add_body(kin);
9727 let sl = world.add_body(RigidBody::new_dynamic(v3(9, 9, 9), Fix128::ONE));
9728 world.islands.sleep_data[sl].state = crate::sleeping::SleepState::Sleeping;
9729 let dt = r(1, 4);
9730
9731 world.integrate_positions(dt);
9732
9733 assert_eq!(world.bodies[st].position, v3(0, 5, 0));
9735 assert_eq!(world.bodies[st].velocity, Vec3Fix::ZERO);
9736 assert_eq!(world.bodies[ki].position, v3(2, 0, 0));
9738 assert_eq!(world.bodies[ki].velocity, v3(8, 0, 0));
9739 assert_eq!(world.bodies[ki].prev_position, Vec3Fix::ZERO);
9740 assert_eq!(world.bodies[sl].position, v3(9, 9, 9));
9742 assert_eq!(world.bodies[sl].prev_position, v3(9, 9, 9));
9743 assert_eq!(world.bodies[sl].velocity, Vec3Fix::ZERO);
9744 }
9745
9746 #[test]
9749 fn step_free_fall_matches_closed_form_over_substeps() {
9750 let mut world = quiet_world();
9753 world.config.gravity = v3(0, -8, 0);
9754 world.config.damping = Fix128::ONE;
9755 world.config.substeps = 4;
9756 world.config.iterations = 1;
9757 let i = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9758
9759 world.step(Fix128::ONE);
9760
9761 assert_eq!(world.bodies[i].velocity, v3(0, -8, 0));
9762 assert_eq!(world.bodies[i].position, v3(0, -5, 0));
9763 let mut single = quiet_world();
9765 single.config.gravity = v3(0, -8, 0);
9766 single.config.damping = Fix128::ONE;
9767 single.config.substeps = 1;
9768 let j = single.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9769 single.step(Fix128::ONE);
9770 assert_eq!(single.bodies[j].position, v3(0, -8, 0));
9771 }
9772
9773 #[test]
9776 fn combined_material_uses_registered_pair_override_and_ignores_out_of_range() {
9777 let mut world = quiet_world();
9778 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9779 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
9780 let metal = world.material_table.register_metal();
9781 world
9782 .material_table
9783 .set_pair_override(0, metal, r(1, 8), r(7, 8));
9784 world.set_body_material(b, metal);
9785 let c = world.combined_material(a, b);
9786 assert_eq!((c.friction, c.restitution), (r(1, 8), r(7, 8)));
9787 let c2 = world.combined_material(b, a);
9789 assert_eq!((c2.friction, c2.restitution), (r(1, 8), r(7, 8)));
9790 let d = world.combined_material(a, 99);
9792 let dd = world.material_table.combine(0, 0);
9793 assert_eq!((d.friction, d.restitution), (dd.friction, dd.restitution));
9794 world.set_body_material(99, metal);
9796 assert_eq!(world.body_materials.len(), 2);
9797 }
9798
9799 #[test]
9800 fn body_setters_ignore_out_of_range_and_apply_in_range() {
9801 let mut world = quiet_world();
9802 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9803 world.set_body_collision_radius(a, r(3, 2));
9804 assert_eq!(world.body_collision_radii[a], Some(r(3, 2)));
9805 world.set_body_collision_radius(5, Fix128::ONE);
9806 assert_eq!(world.body_collision_radii.len(), 1);
9807 world.clear_body_collision_radius(5);
9808 assert_eq!(world.body_collision_radii[a], Some(r(3, 2)));
9809 world.clear_body_collision_radius(a);
9810 assert_eq!(world.body_collision_radii[a], None);
9811
9812 let f = CollisionFilter {
9813 layer: 4,
9814 mask: 8,
9815 group: 2,
9816 };
9817 world.set_body_filter(a, f);
9818 assert_eq!(world.body_filters[a], f);
9819 world.set_body_filter(5, CollisionFilter::DEFAULT);
9820 assert_eq!(world.body_filters[a], f);
9821 }
9822
9823 #[test]
9824 fn active_body_count_excludes_sleeping() {
9825 let mut world = quiet_world();
9826 for i in 0..3 {
9827 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
9828 }
9829 assert_eq!(world.active_body_count(), 3);
9830 world.islands.sleep_data[1].state = crate::sleeping::SleepState::Sleeping;
9831 assert_eq!(world.active_body_count(), 2);
9832 world.islands.sleep_data[0].state = crate::sleeping::SleepState::Sleeping;
9833 world.islands.sleep_data[2].state = crate::sleeping::SleepState::Sleeping;
9834 assert_eq!(world.active_body_count(), 0);
9835 }
9836
9837 #[cfg(feature = "std")]
9840 #[test]
9841 fn clear_pre_solve_hooks_restores_normal_contact_response() {
9842 let mut world = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9844 world.add_pre_solve_hook(Box::new(|_a, _b, _c| false));
9845 world.add_pre_solve_hook(Box::new(|_a, _b, _c| false));
9846 assert_eq!(world.pre_solve_hooks.len(), 2);
9847 world.apply_contact_filters();
9848 world.solve_contact_constraints(r(1, 4));
9849 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO);
9850
9851 world.clear_pre_solve_hooks();
9853 assert!(world.pre_solve_hooks.is_empty());
9854 world.apply_contact_filters();
9855 world.solve_contact_constraints(r(1, 4));
9856 assert_eq!(
9857 world.bodies[0].position,
9858 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9859 );
9860 assert_eq!(
9861 world.bodies[1].position,
9862 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9863 );
9864 }
9865
9866 #[cfg(feature = "std")]
9867 #[test]
9868 fn clear_contact_modifiers_restores_normal_contact_response() {
9869 struct Discard;
9870 impl ContactModifier for Discard {
9871 fn modify_contact(
9872 &self,
9873 _a: usize,
9874 _b: usize,
9875 _c: &mut Contact,
9876 _f: &mut Fix128,
9877 _r: &mut Fix128,
9878 ) -> bool {
9879 false
9880 }
9881 }
9882 let mut world = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9883 world.add_contact_modifier(Box::new(Discard));
9884 assert_eq!(world.contact_modifiers.len(), 1);
9885 world.apply_contact_filters();
9886 world.solve_contact_constraints(r(1, 4));
9887 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO);
9888
9889 world.clear_contact_modifiers();
9890 assert!(world.contact_modifiers.is_empty());
9891 world.apply_contact_filters();
9892 world.solve_contact_constraints(r(1, 4));
9893 assert_eq!(
9894 world.bodies[0].position,
9895 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9896 );
9897 }
9898
9899 #[test]
9900 fn set_angular_velocity_overwrites_and_drives_rotation_prediction() {
9901 let mut world = quiet_world();
9902 let i = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9903 world.bodies[i].angular_velocity = v3(5, 5, 5);
9904 world.bodies[i].set_angular_velocity(v3(0, 0, 2));
9905 assert_eq!(world.bodies[i].angular_velocity, v3(0, 0, 2));
9906 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9908 st.set_angular_velocity(v3(1, 0, 0));
9909 assert_eq!(st.angular_velocity, v3(1, 0, 0));
9910
9911 world.integrate_positions(r(1, 4));
9913 let expected = QuatFix::from_axis_angle(v3(0, 0, 1), r(1, 2))
9914 .mul(QuatFix::IDENTITY)
9915 .normalize();
9916 assert_eq!(world.bodies[i].rotation, expected);
9917 }
9918
9919 #[test]
9920 fn with_angular_damping_is_stored_and_applied_once_per_frame_by_step() {
9921 let body = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_angular_damping(r(1, 2));
9922 assert_eq!(body.angular_damping, r(1, 2));
9923 assert_eq!(body.linear_damping, Fix128::ONE);
9924
9925 for substeps in [1usize, 4] {
9928 let mut undamped = quiet_world();
9929 undamped.config.substeps = substeps;
9930 let u = undamped.add_body(
9931 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_velocity(v3(3, 0, 0)),
9932 );
9933 undamped.bodies[u].set_angular_velocity(v3(0, 0, 2));
9934
9935 let mut damped = quiet_world();
9936 damped.config.substeps = substeps;
9937 let d = damped.add_body(
9938 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE)
9939 .with_velocity(v3(3, 0, 0))
9940 .with_angular_damping(r(1, 2)),
9941 );
9942 damped.bodies[d].set_angular_velocity(v3(0, 0, 2));
9943
9944 undamped.step(r(1, 4));
9945 damped.step(r(1, 4));
9946
9947 let expected = undamped.bodies[u].angular_velocity * Fix128::ONE * r(1, 2);
9948 assert_eq!(
9949 damped.bodies[d].angular_velocity, expected,
9950 "substeps {substeps}"
9951 );
9952 assert_eq!(
9954 damped.bodies[d].velocity, undamped.bodies[u].velocity,
9955 "substeps {substeps}"
9956 );
9957 let ratio = damped.bodies[d].angular_velocity.z.to_f64()
9958 / undamped.bodies[u].angular_velocity.z.to_f64();
9959 assert!(
9960 (ratio - 0.5).abs() < 1e-9,
9961 "substeps {substeps}: ratio {ratio}"
9962 );
9963 }
9964 }
9965
9966 #[test]
9967 fn with_sensor_stores_flag_and_sensor_body_skips_contact_response() {
9968 let sensor = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_sensor(true);
9969 assert!(sensor.is_sensor);
9970 assert!(!sensor.with_sensor(false).is_sensor);
9971
9972 let mut world = quiet_world();
9974 let a =
9975 world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_sensor(true));
9976 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
9977 let contact = Contact {
9978 depth: Fix128::ONE,
9979 normal: v3(1, 0, 0),
9980 point_a: Vec3Fix::ZERO,
9981 point_b: Vec3Fix::ZERO,
9982 };
9983 world
9984 .contact_constraints
9985 .push(ContactConstraint::new(a, b, contact));
9986 world.solve_contact_constraints(r(1, 4));
9987 assert_eq!(world.bodies[a].position, Vec3Fix::ZERO);
9988 assert_eq!(world.bodies[b].position, v3(1, 0, 0));
9989
9990 let mut auto = quiet_world();
9992 auto.add_body_with_radius(
9993 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_sensor(true),
9994 Fix128::ONE,
9995 );
9996 auto.add_body_with_radius(
9997 RigidBody::new_dynamic(
9998 Vec3Fix::new(r(3, 2), Fix128::ZERO, Fix128::ZERO),
9999 Fix128::ONE,
10000 ),
10001 Fix128::ONE,
10002 );
10003 auto.detect_collisions();
10004 assert!(auto.contact_constraints.is_empty());
10005 assert_eq!(auto.trigger_events().len(), 1);
10006 }
10007
10008 #[test]
10009 fn raycast_far_intersection_off_center_and_max_distance_cull_inside_aabb() {
10010 let world = sphere_world(&[v3(10, 0, 0)]);
10012 assert_eq!(
10013 world.raycast(v3(9, 0, 0), v3(1, 0, 0), Fix128::from_int(100)),
10014 Some((0, Fix128::from_int(3)))
10015 );
10016 let mut big = quiet_world();
10018 big.add_body_with_radius(RigidBody::new_static(v3(10, 0, 0)), Fix128::from_int(3));
10019 assert_eq!(big.raycast(v3(0, 2, 0), v3(1, 0, 0), r(15, 2)), None);
10020 assert!(big
10021 .raycast(v3(0, 2, 0), v3(1, 0, 0), Fix128::from_int(8))
10022 .is_some());
10023 }
10024
10025 #[test]
10033 fn rigid_body_setters_and_mass_speed_report_exact_values() {
10034 let mut body = RigidBody::new(v3(1, 1, 1), Fix128::from_int(4));
10035 assert_eq!(body.inv_mass, r(1, 4));
10036 assert_eq!(body.mass(), Fix128::from_int(4));
10037 assert_eq!(RigidBody::new_static(Vec3Fix::ZERO).mass(), Fix128::ZERO);
10038
10039 body.set_velocity(v3(3, -4, 0));
10040 assert_eq!(body.velocity, v3(3, -4, 0));
10041 assert_eq!(body.speed(), Fix128::from_int(5));
10042
10043 body.set_position(v3(7, -8, 9));
10044 assert_eq!(body.position, v3(7, -8, 9));
10045 assert_eq!(body.prev_position, v3(7, -8, 9));
10046
10047 let q = QuatFix::from_axis_angle(v3(0, 1, 0), r(1, 3));
10048 assert_ne!(q, QuatFix::IDENTITY);
10049 body.set_rotation(q);
10050 assert_eq!(body.rotation, q);
10051 assert_eq!(body.prev_rotation, q);
10052 }
10053
10054 #[test]
10059 fn apply_impulse_at_and_add_torque_multiply_x_by_inverse_inertia() {
10060 let mut body = RigidBody::new(v3(5, 5, 5), Fix128::ONE);
10061 body.inv_inertia = v3(4, 4, 4);
10062 body.apply_impulse_at(v3(0, 0, 2), v3(5, 6, 5));
10063 assert_eq!(body.velocity, v3(0, 0, 2));
10064 assert_eq!(body.angular_velocity, v3(8, 0, 0));
10065
10066 let mut spun = RigidBody::new(Vec3Fix::ZERO, Fix128::ONE);
10067 spun.inv_inertia = v3(4, 4, 4);
10068 spun.add_torque(v3(2, 0, 0), r(1, 2));
10069 assert_eq!(spun.angular_velocity, v3(4, 0, 0));
10070 }
10071
10072 #[test]
10078 fn body_count_and_get_body_accessors_track_the_body_vector() {
10079 let mut world = quiet_world();
10080 assert_eq!(world.body_count(), 0);
10081 assert!(world.get_body(0).is_none());
10082 assert!(world.get_body_mut(0).is_none());
10083
10084 world.add_body(RigidBody::new_dynamic(v3(3, 4, 5), Fix128::ONE));
10085 world.add_body(RigidBody::new_dynamic(v3(6, 7, 8), Fix128::ONE));
10086 assert_eq!(world.body_count(), 2);
10087 assert_eq!(world.get_body(0).map(|b| b.position), Some(v3(3, 4, 5)));
10088 assert_eq!(world.get_body(1).map(|b| b.position), Some(v3(6, 7, 8)));
10089 assert!(world.get_body(2).is_none());
10090
10091 world
10092 .get_body_mut(1)
10093 .expect("body 1 exists")
10094 .set_velocity(v3(-1, -2, -3));
10095 assert_eq!(world.bodies[1].velocity, v3(-1, -2, -3));
10096 assert!(world.get_body_mut(2).is_none());
10097
10098 assert!(world.remove_body(0).is_some());
10099 assert_eq!(world.body_count(), 1);
10100 }
10101
10102 #[test]
10109 fn index_equal_to_body_count_is_out_of_range_for_every_setter() {
10110 let mut world = quiet_world();
10111 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10112 let len = world.body_count();
10113 let metal = world.material_table.register_metal();
10114
10115 world.set_body_material(len, metal);
10116 world.set_body_collision_radius(len, Fix128::ONE);
10117 world.clear_body_collision_radius(len);
10118 world.set_body_filter(len, CollisionFilter::NONE);
10119 assert_eq!(
10120 world.body_materials,
10121 vec![crate::material::DEFAULT_MATERIAL]
10122 );
10123 assert_eq!(world.body_collision_radii, vec![None]);
10124 assert_eq!(world.body_filters, vec![CollisionFilter::DEFAULT]);
10125
10126 world.set_body_material(a, metal);
10127 let default_metal = world
10128 .material_table
10129 .combine(crate::material::DEFAULT_MATERIAL, metal);
10130 let default_default = world.material_table.combine(0, 0);
10131 assert_ne!(default_metal.friction, default_default.friction);
10132 for (x, y, want) in [
10133 (len, a, default_metal),
10134 (a, len, default_metal),
10135 (len, len, default_default),
10136 ] {
10137 let c = world.combined_material(x, y);
10138 assert_eq!(
10139 (c.friction, c.restitution),
10140 (want.friction, want.restitution),
10141 "({x}, {y}) must treat the out-of-range side as the default material"
10142 );
10143 }
10144 let custom = CollisionFilter {
10145 layer: 1 << 3,
10146 mask: 1 << 9,
10147 group: 5,
10148 };
10149 world.set_body_filter(a, custom);
10150 assert_eq!(world.body_filter(a), custom);
10151 assert_eq!(world.body_filter(len), CollisionFilter::DEFAULT);
10152 }
10153
10154 #[test]
10159 fn contact_cache_frame_lifecycle_ages_and_prunes_manifolds() {
10160 let mut world = quiet_world();
10161 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10162 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10163 push_contact(&mut world, a, b, r(1, 2), r(1, 2));
10164 let c = world.contact_constraints.pop().expect("pushed");
10165 world.add_contact(c);
10166 assert_eq!(world.contact_constraints.len(), 1);
10167 assert_eq!(world.contact_cache.manifold_count(), 1);
10168 assert_eq!(world.contact_cache.manifolds[0].stale_frames, 0);
10169
10170 for _ in 0..4 {
10171 world.begin_frame();
10172 }
10173 assert_eq!(world.contact_cache.manifolds[0].stale_frames, 4);
10174 world.end_frame();
10175 assert_eq!(world.contact_cache.manifold_count(), 0);
10176
10177 world.clear_contacts();
10178 assert!(world.contact_constraints.is_empty());
10179 }
10180
10181 #[test]
10185 fn add_and_remove_joint_roundtrip() {
10186 use crate::joint::{BallJoint, Joint};
10187 let mut world = quiet_world();
10188 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10189 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10190 let j0 = world.add_joint(Joint::Ball(BallJoint::new(
10191 a,
10192 b,
10193 Vec3Fix::ZERO,
10194 Vec3Fix::ZERO,
10195 )));
10196 let j1 = world.add_joint(Joint::Ball(BallJoint::new(
10197 b,
10198 a,
10199 v3(1, 0, 0),
10200 Vec3Fix::ZERO,
10201 )));
10202 assert_eq!((j0, j1), (0, 1));
10203 assert_eq!(world.joint_count(), 2);
10204 assert!(world.remove_joint(2).is_none());
10205 assert_eq!(world.joint_count(), 2);
10206 let removed = world.remove_joint(0).expect("joint 0 exists");
10207 assert_eq!(removed.bodies(), (a, b));
10208 assert_eq!(world.joint_count(), 1);
10209 assert_eq!(world.joints[0].bodies(), (b, a));
10210 assert!(world.remove_joint(1).is_none());
10211 }
10212
10213 #[test]
10220 fn force_field_add_remove_and_step_applies_directional_force() {
10221 use crate::force::{ForceField, ForceFieldInstance};
10222 let mut world = quiet_world();
10223 let body = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10224 let wind = ForceFieldInstance::new(ForceField::Directional {
10225 direction: Vec3Fix::UNIT_X,
10226 strength: Fix128::from_int(2),
10227 });
10228 let drag = ForceFieldInstance::new(ForceField::Drag {
10229 coefficient: Fix128::ONE,
10230 });
10231 assert_eq!(world.add_force_field(wind), 0);
10232 assert_eq!(world.add_force_field(drag), 1);
10233 assert_eq!(world.force_fields.len(), 2);
10234 assert!(world.remove_force_field(2).is_none());
10235 assert!(matches!(
10236 world.remove_force_field(1).map(|f| f.field),
10237 Some(ForceField::Drag { .. })
10238 ));
10239 assert!(world.remove_force_field(1).is_none());
10240 assert_eq!(world.force_fields.len(), 1);
10241
10242 world.step(r(1, 4));
10243 assert_eq!(
10244 world.bodies[body].velocity,
10245 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
10246 );
10247 assert_eq!(
10248 world.bodies[body].position,
10249 Vec3Fix::new(r(1, 8), Fix128::ZERO, Fix128::ZERO)
10250 );
10251 }
10252
10253 #[test]
10257 fn park_proxy_live_count_tracks_the_tree() {
10258 let mut w = PhysicsWorld::new(PhysicsConfig {
10259 gravity: Vec3Fix::ZERO,
10260 ..PhysicsConfig::default()
10261 });
10262 let r = Fix128::from_ratio(1, 2);
10263 for i in 0..20 {
10264 w.add_body_with_radius(
10265 RigidBody::new_dynamic(Vec3Fix::from_int(i * 3, 0, 0), Fix128::ONE),
10266 r,
10267 );
10268 w.islands.sleep_data[i as usize].state = SleepState::Sleeping;
10269 }
10270 let check = |w: &PhysicsWorld, ctx: &str| {
10271 assert_eq!(w.park.proxy_live, w.park.tree.proxy_count(), "{ctx}");
10272 assert_eq!(
10273 w.park.proxy_live,
10274 w.park.proxies.iter().flatten().count(),
10275 "{ctx}"
10276 );
10277 };
10278 w.step(Fix128::from_ratio(1, 60));
10279 check(&w, "parked");
10280 assert_eq!(w.park.proxy_live, 20);
10281 w.wake_body(0);
10283 w.bodies[0].set_position(Vec3Fix::new(
10284 Fix128::from_ratio(22, 10),
10285 Fix128::ZERO,
10286 Fix128::ZERO,
10287 ));
10288 w.step(Fix128::from_ratio(1, 60));
10289 check(&w, "contact");
10290 assert!(w.stage_work().unparked > 0);
10291 w.wake_body(7);
10292 w.step(Fix128::from_ratio(1, 60));
10293 check(&w, "manual wake");
10294 w.remove_body(3);
10295 w.step(Fix128::from_ratio(1, 60));
10296 check(&w, "remove");
10297 w.set_sleep_skip(false);
10298 check(&w, "off");
10299 assert_eq!(w.park.proxy_live, 0);
10300 }
10301
10302 #[test]
10307 fn is_sleeping_and_wake_body_reflect_island_state() {
10308 let mut world = quiet_world();
10309 let floor = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10310 let mover = world.add_body(RigidBody::new_dynamic(v3(0, 5, 0), Fix128::ONE));
10311 world.bodies[mover].velocity = v3(5, 0, 0);
10312 world.step(r(1, 4));
10313 assert!(world.is_sleeping(floor));
10314 assert!(!world.is_sleeping(mover));
10315 assert!(!world.is_sleeping(99));
10316 world.wake_body(floor);
10317 assert!(!world.is_sleeping(floor));
10318 assert!(!world.is_sleeping(mover));
10319 }
10320
10321 #[test]
10324 fn set_sleep_config_changes_frames_to_sleep() {
10325 let cfg = SleepConfig {
10326 frames_to_sleep: 2,
10327 ..SleepConfig::default()
10328 };
10329 let mut quick = quiet_world();
10330 let idle = quick.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10331 quick.set_sleep_config(cfg);
10332 assert_eq!(quick.islands.config, cfg);
10333 quick.step(r(1, 4));
10334 assert!(!quick.is_sleeping(idle));
10335 quick.step(r(1, 4));
10336 assert!(quick.is_sleeping(idle));
10337
10338 let mut slow = quiet_world();
10339 let idle2 = slow.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10340 slow.step(r(1, 4));
10341 slow.step(r(1, 4));
10342 assert!(!slow.is_sleeping(idle2));
10343 }
10344
10345 #[test]
10349 fn drain_events_return_the_events_and_empty_the_collector() {
10350 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
10351 world.detect_collisions();
10352 assert_eq!(world.contact_events().len(), 1);
10353 let drained = world.drain_contact_events();
10354 assert_eq!(drained.len(), 1);
10355 assert_eq!((drained[0].body_a, drained[0].body_b), (0, 1));
10356 assert_eq!(drained[0].event_type, crate::event::ContactEventType::Begin);
10357 assert!(world.contact_events().is_empty());
10358 assert!(world.drain_contact_events().is_empty());
10359
10360 let mut sensor = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
10361 sensor.bodies[1].is_sensor = true;
10362 sensor.detect_collisions();
10363 let triggers = sensor.drain_trigger_events();
10364 assert_eq!(triggers.len(), 1);
10365 assert!(triggers[0].entered);
10366 assert!(sensor.trigger_events().is_empty());
10367 assert!(sensor.drain_trigger_events().is_empty());
10368 }
10369
10370 #[test]
10374 fn step_rebuilds_joint_islands_from_scratch() {
10375 use crate::joint::{BallJoint, Joint};
10376 let mut world = quiet_world();
10377 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10378 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10379 let c = world.add_body(RigidBody::new_dynamic(v3(9, 0, 0), Fix128::ONE));
10380 world.add_joint(Joint::Ball(BallJoint::new(
10381 a,
10382 b,
10383 Vec3Fix::ZERO,
10384 Vec3Fix::ZERO,
10385 )));
10386 world.islands.reset_unions();
10388 assert_ne!(world.islands.find(a), world.islands.find(b));
10389 world.step(r(1, 4));
10390 assert_eq!(world.islands.find(a), world.islands.find(b));
10391 assert_ne!(world.islands.find(a), world.islands.find(c));
10392 }
10393
10394 #[test]
10399 fn integrate_positions_kinematic_velocity_is_target_minus_position_over_dt() {
10400 let mut world = quiet_world();
10401 let k = world.add_body(RigidBody::new_kinematic(v3(1, 2, 3)));
10402 world.bodies[k].set_kinematic_target(v3(5, 2, 3), QuatFix::IDENTITY);
10403 world.integrate_positions(r(1, 4));
10404 assert_eq!(world.bodies[k].velocity, v3(16, 0, 0));
10405 assert_eq!(world.bodies[k].position, v3(5, 2, 3));
10406 assert_eq!(world.bodies[k].prev_position, v3(1, 2, 3));
10407 }
10408
10409 #[cfg(feature = "std")]
10417 #[test]
10418 fn advance_kinematic_targets_for_tgs_matches_integrate_positions_closed_form() {
10419 let mut world = quiet_world();
10420 let k = world.add_body(RigidBody::new_kinematic(v3(1, 2, 3)));
10421 world.bodies[k].set_kinematic_target(v3(5, 2, 3), QuatFix::IDENTITY);
10422 world.advance_kinematic_targets_for_tgs(r(1, 4));
10423 assert_eq!(world.bodies[k].velocity, v3(16, 0, 0));
10424 assert_eq!(world.bodies[k].position, v3(5, 2, 3));
10425 assert_eq!(world.bodies[k].prev_position, v3(1, 2, 3));
10426 }
10427
10428 #[cfg(feature = "std")]
10431 #[test]
10432 fn advance_kinematic_targets_for_tgs_leaves_dynamic_bodies_untouched() {
10433 let mut world = quiet_world();
10434 let d = world.add_body(RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE));
10435 world.bodies[d].velocity = v3(7, 8, 9);
10436 world.advance_kinematic_targets_for_tgs(r(1, 4));
10437 assert_eq!(world.bodies[d].position, v3(1, 2, 3));
10438 assert_eq!(world.bodies[d].velocity, v3(7, 8, 9));
10439 }
10440
10441 #[cfg(feature = "std")]
10446 #[test]
10447 fn advance_kinematic_targets_for_tgs_without_a_target_stays_at_rest() {
10448 let mut world = quiet_world();
10449 let k = world.add_body(RigidBody::new_kinematic(v3(1, 2, 3)));
10450 world.advance_kinematic_targets_for_tgs(r(1, 4));
10451 assert_eq!(world.bodies[k].position, v3(1, 2, 3));
10452 assert_eq!(world.bodies[k].velocity, Vec3Fix::ZERO);
10453 assert_eq!(world.bodies[k].prev_position, v3(1, 2, 3));
10454 }
10455
10456 #[test]
10467 fn update_velocities_friction_and_restitution_split_between_two_dynamic_bodies() {
10468 let mut world = quiet_world();
10469 let a = world.add_body(RigidBody::new_dynamic(v3(0, 1, 0), Fix128::ONE));
10470 let b = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10471 world.bodies[b].inv_mass = Fix128::from_int(3);
10472 let contact = Contact {
10473 depth: r(1, 100),
10474 normal: v3(0, 1, 0),
10475 point_a: Vec3Fix::ZERO,
10476 point_b: Vec3Fix::ZERO,
10477 };
10478 let mut c = ContactConstraint::new(a, b, contact);
10479 c.friction = r(1, 2);
10480 c.restitution = r(1, 2);
10481 c.cached_lambda = r(1, 4);
10483 world.contact_constraints.push(c);
10484 freeze_positions(&mut world);
10485 give_velocity(&mut world, a, v3(3, -4, 0));
10486 give_velocity(&mut world, b, v3(1, -2, 0));
10487
10488 world.update_velocities(r(1, 4));
10489
10490 assert_near_vec(
10491 world.bodies[a].velocity,
10492 Vec3Fix::new(r(23, 8), r(-13, 4), Fix128::ZERO),
10493 "A",
10494 );
10495 assert_near_vec(
10496 world.bodies[b].velocity,
10497 Vec3Fix::new(r(11, 8), r(-17, 4), Fix128::ZERO),
10498 "B",
10499 );
10500 }
10501
10502 fn half_epsilon() -> Fix128 {
10505 Fix128 {
10506 hi: 0,
10507 lo: W_SUM_EPSILON.lo >> 1,
10508 }
10509 }
10510
10511 #[test]
10516 fn update_velocities_w_sum_epsilon_boundary() {
10517 for (inv_mass, expected_y) in [(W_SUM_EPSILON, 2), (half_epsilon(), -4)] {
10518 let mut world = quiet_world();
10519 let a = world.add_body(RigidBody::new_dynamic(v3(0, 1, 0), Fix128::ONE));
10520 let b = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10521 world.bodies[a].inv_mass = inv_mass;
10522 let contact = Contact {
10523 depth: r(1, 100),
10524 normal: v3(0, 1, 0),
10525 point_a: Vec3Fix::ZERO,
10526 point_b: Vec3Fix::ZERO,
10527 };
10528 let mut c = ContactConstraint::new(a, b, contact);
10529 c.friction = Fix128::ZERO;
10530 c.restitution = r(1, 2);
10531 world.contact_constraints.push(c);
10532 freeze_positions(&mut world);
10533 give_velocity(&mut world, a, v3(0, -4, 0));
10534
10535 world.update_velocities(r(1, 4));
10536
10537 assert_eq!(
10538 world.bodies[a].velocity,
10539 v3(0, expected_y, 0),
10540 "inv_mass {inv_mass:?}"
10541 );
10542 }
10543 }
10544
10545 #[test]
10551 fn solve_distance_constraints_w_sum_epsilon_boundary() {
10552 for (inv_mass, expected) in [(W_SUM_EPSILON, v3(0, 1, 0)), (half_epsilon(), v3(0, 4, 0))] {
10553 let mut world = quiet_world();
10554 let a = world.add_body(RigidBody::new_dynamic(v3(0, 4, 0), Fix128::ONE));
10555 let b = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10556 world.bodies[a].inv_mass = inv_mass;
10557 world.add_distance_constraint(dc(a, b));
10558 world.solve_distance_constraints(r(1, 4));
10559 assert_near_vec(world.bodies[a].position, expected, "A position");
10560 assert_eq!(world.bodies[b].position, Vec3Fix::ZERO);
10561 }
10562 }
10563
10564 #[test]
10568 fn solve_contact_constraints_w_sum_epsilon_boundary() {
10569 for (inv_mass, expected) in [
10570 (W_SUM_EPSILON, v3(1, 0, 0)),
10571 (half_epsilon(), Vec3Fix::ZERO),
10572 ] {
10573 let mut world = contact_world(inv_mass, Fix128::ZERO, Fix128::ONE);
10574 world.solve_contact_constraints(r(1, 4));
10575 assert_eq!(world.bodies[0].position, expected, "inv_mass {inv_mass:?}");
10576 assert_eq!(world.bodies[1].position, v3(1, 0, 0));
10577 }
10578 }
10579
10580 #[test]
10587 fn solve_distance_constraints_anchor_a_offset_and_inverse_mass_scaling() {
10588 let mut world = quiet_world();
10589 let a = world.add_body(RigidBody::new_dynamic(v3(0, 2, 0), Fix128::ONE));
10590 let b = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10591 world.bodies[a].inv_mass = Fix128::from_int(2);
10592 let mut c = dc(a, b);
10593 c.local_anchor_a = v3(0, 1, 0);
10594 world.add_distance_constraint(c);
10595
10596 world.solve_distance_constraints(r(1, 4));
10597
10598 assert_near_vec(world.bodies[a].position, Vec3Fix::ZERO, "A");
10599 assert_eq!(world.bodies[b].position, Vec3Fix::ZERO);
10600 assert_eq!(world.distance_constraints[0].cached_lambda, Fix128::ONE);
10601 }
10602
10603 #[test]
10610 fn detect_collisions_three_bodies_squared_distance_cull_uses_squared_radius() {
10611 let mut world = quiet_world();
10612 let radius = r(3, 2);
10613 world.add_body_with_radius(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE), radius);
10614 world.add_body_with_radius(RigidBody::new_dynamic(v3(2, 1, 1), Fix128::ONE), radius);
10615 world.add_body_with_radius(RigidBody::new_dynamic(v3(50, 50, 50), Fix128::ONE), radius);
10616 world.detect_collisions();
10617 assert_eq!(world.contact_constraints.len(), 1);
10618 let c = world.contact_constraints[0];
10619 assert_eq!((c.body_a, c.body_b), (0, 1));
10620 let expected_depth = Fix128::from_int(3) - Fix128::from_int(6).sqrt();
10621 assert!(
10622 near(c.contact.depth, expected_depth),
10623 "depth {:?} vs {expected_depth:?}",
10624 c.contact.depth
10625 );
10626 assert_eq!(world.contact_events().len(), 1);
10627 }
10628
10629 #[test]
10634 fn remove_sdf_collider_boundary_and_sdf_radius_setter() {
10635 use crate::sdf_collider::{ClosureSdf, SdfCollider};
10636 let mut world = quiet_world();
10637 let ground = ClosureSdf::new(|_x, y, _z| y, |_x, _y, _z| (0.0, 1.0, 0.0));
10638 assert_eq!(
10639 world.add_sdf_collider(SdfCollider::new_static(
10640 Box::new(ground),
10641 Vec3Fix::ZERO,
10642 QuatFix::IDENTITY,
10643 )),
10644 0
10645 );
10646 assert!(world.remove_sdf_collider(1).is_none());
10647 assert_eq!(world.sdf_colliders.len(), 1);
10648 assert!(world.remove_sdf_collider(0).is_some());
10649 assert!(world.sdf_colliders.is_empty());
10650 assert!(world.remove_sdf_collider(0).is_none());
10651
10652 world.set_sdf_collision_radius(r(3, 4));
10653 assert_eq!(world.sdf_collision_radius, r(3, 4));
10654 }
10655
10656 #[test]
10659 fn deserialize_state_accepts_header_only_snapshot_of_empty_world() {
10660 let src = quiet_world();
10661 let bytes = src.serialize_state();
10662 assert_eq!(bytes.len(), 21);
10664 let mut dst = quiet_world();
10665 assert!(dst.deserialize_state(&bytes));
10666 assert_eq!(dst.body_count(), 0);
10667 }
10668
10669 #[test]
10674 fn deserialize_state_extends_short_parallel_arrays_to_body_count() {
10675 let mut world = quiet_world();
10676 world.add_body(RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE));
10677 world.set_body_collision_radius(0, r(1, 2));
10678 world
10679 .bodies
10680 .push(RigidBody::new_dynamic(v3(4, 5, 6), Fix128::ONE));
10681 world
10682 .bodies
10683 .push(RigidBody::new_dynamic(v3(7, 8, 9), Fix128::ONE));
10684 assert_eq!(world.body_collision_radii.len(), 1);
10685 let bytes = world.serialize_state();
10686 assert!(world.deserialize_state(&bytes));
10687 assert_eq!(world.body_collision_radii, vec![Some(r(1, 2)), None, None]);
10688 assert_eq!(world.body_filters, vec![CollisionFilter::DEFAULT; 3]);
10689 assert_eq!(
10690 world.body_materials,
10691 vec![crate::material::DEFAULT_MATERIAL; 3]
10692 );
10693 assert_eq!(world.islands.sleep_data.len(), 3);
10694 assert_eq!(world.bodies[2].position, v3(7, 8, 9));
10695 }
10696
10697 #[test]
10700 fn observe_body_returns_none_out_of_range_and_fields_for_in_range() {
10701 let mut world = quiet_world();
10702 let mut a = RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE);
10703 a.velocity = v3(4, 5, 6);
10704 a.angular_velocity = v3(7, 8, 9);
10705 world.add_body(a);
10706
10707 assert!(world.observe_body(1).is_none(), "範囲外 index は None");
10708
10709 let obs = world.observe_body(0).expect("範囲内 index は Some");
10710 assert_eq!(obs.body_index, 0);
10711 assert_eq!(obs.position, v3(1, 2, 3));
10712 assert_eq!(obs.velocity, v3(4, 5, 6));
10713 assert_eq!(obs.angular_velocity, v3(7, 8, 9));
10714 assert!(!obs.sleeping);
10715 assert!(!obs.in_contact, "接触していない body は in_contact = false");
10716 }
10717
10718 #[test]
10719 fn observe_bodies_returns_one_entry_per_body_in_index_order() {
10720 let mut world = quiet_world();
10721 world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10722 world.add_body(RigidBody::new_dynamic(v3(2, 0, 0), Fix128::ONE));
10723 world.add_body(RigidBody::new_dynamic(v3(3, 0, 0), Fix128::ONE));
10724
10725 let obs = world.observe_bodies();
10726 assert_eq!(obs.len(), 3);
10727 for (i, o) in obs.iter().enumerate() {
10728 assert_eq!(o.body_index, i);
10729 assert_eq!(o.position, world.bodies[i].position);
10730 }
10731 }
10732
10733 #[test]
10734 fn observe_body_reports_in_contact_from_this_frame_events() {
10735 let mut world = PhysicsWorld::new(PhysicsConfig {
10738 gravity: Vec3Fix::ZERO,
10739 ..PhysicsConfig::default()
10740 });
10741 let radius = Fix128::from_int(2);
10742 let a = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
10743 world.add_body_with_radius(a, radius);
10744 let b = RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE);
10745 world.add_body_with_radius(b, radius);
10746 let c = RigidBody::new_dynamic(v3(100, 100, 100), Fix128::ONE);
10747 world.add_body_with_radius(c, Fix128::from_ratio(1, 10));
10748
10749 world.step(Fix128::from_ratio(1, 60));
10750
10751 assert!(world.observe_body(0).unwrap().in_contact);
10752 assert!(world.observe_body(1).unwrap().in_contact);
10753 assert!(
10754 !world.observe_body(2).unwrap().in_contact,
10755 "遠くの body は接触していないはず"
10756 );
10757 }
10758
10759 #[test]
10765 fn batches_are_body_disjoint_checks_second_body_and_self_constraints() {
10766 let mut world = quiet_world();
10767 for i in 0..3i64 {
10768 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
10769 }
10770 world.add_distance_constraint(dc(1, 0));
10771 world.add_distance_constraint(dc(2, 0));
10772 world.rebuild_batches();
10773 assert_eq!(world.num_batches(), 2);
10774 assert!(world.batches_are_body_disjoint());
10775
10776 let mut shared_b = ConstraintBatch::default();
10777 shared_b.distance_indices.extend([0usize, 1]);
10778 world.constraint_batches = vec![shared_b];
10779 assert!(!world.batches_are_body_disjoint());
10780
10781 world.distance_constraints.push(dc(1, 1));
10782 let mut self_only = ConstraintBatch::default();
10783 self_only.distance_indices.push(2);
10784 world.constraint_batches = vec![self_only];
10785 assert!(world.batches_are_body_disjoint());
10786
10787 let mut self_and_other = ConstraintBatch::default();
10788 self_and_other.distance_indices.extend([2usize, 0]);
10789 world.constraint_batches = vec![self_and_other];
10790 assert!(!world.batches_are_body_disjoint());
10791 }
10792
10793 #[test]
10800 fn raycast_ties_keep_first_candidate_and_nearer_later_candidate_replaces() {
10801 let mut tie = quiet_world();
10802 tie.add_body_with_radius(
10803 RigidBody::new_static(Vec3Fix::new(Fix128::from_int(5), r(1, 2), Fix128::ZERO)),
10804 Fix128::ONE,
10805 );
10806 tie.add_body_with_radius(
10807 RigidBody::new_static(Vec3Fix::new(Fix128::from_int(5), r(-1, 2), Fix128::ZERO)),
10808 Fix128::ONE,
10809 );
10810 let hit = tie
10811 .raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(20))
10812 .expect("both spheres straddle the ray");
10813 assert_eq!(hit.0, 1);
10814 let expected_t = Fix128::from_int(5) - r(3, 4).sqrt();
10816 assert!(near(hit.1, expected_t), "t {:?} vs {expected_t:?}", hit.1);
10817
10818 let far_first = sphere_world(&[v3(5, 0, 0), v3(10, 0, 0)]);
10819 assert_eq!(
10820 far_first.raycast(v3(20, 0, 0), v3(-1, 0, 0), Fix128::from_int(100)),
10821 Some((1, Fix128::from_int(8)))
10822 );
10823 }
10824
10825 #[test]
10828 fn physics_world_debug_lists_type_and_counts() {
10829 let mut world = quiet_world();
10830 world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10831 world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10832 let text = format!("{world:?}");
10833 assert!(text.starts_with("PhysicsWorld"), "{text}");
10834 assert!(text.contains("bodies: 2"), "{text}");
10835 assert!(text.contains("joints: 0"), "{text}");
10836 }
10837
10838 fn half_unit_box() -> crate::shape::Shape {
10842 crate::shape::Shape::Box {
10843 half_extents: Vec3Fix::from_int(1, 1, 1),
10844 }
10845 }
10846
10847 #[test]
10855 fn colliders_overlap_follows_the_closed_form_gaps() {
10856 let mut world = quiet_world();
10857 let shaped = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10858 assert!(world.set_body_shape(shaped, &half_unit_box()));
10859 assert!(!world.set_body_shape(99, &half_unit_box()));
10860 let near = world.add_body_with_radius(
10861 RigidBody::new(
10862 Vec3Fix::new(Fix128::from_ratio(3, 2), Fix128::ZERO, Fix128::ZERO),
10863 Fix128::ONE,
10864 ),
10865 Fix128::ONE,
10866 );
10867 let far = world.add_body_with_radius(
10868 RigidBody::new(
10869 Vec3Fix::new(Fix128::from_ratio(5, 2), Fix128::ZERO, Fix128::ZERO),
10870 Fix128::ONE,
10871 ),
10872 Fix128::ONE,
10873 );
10874 let bare = world.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE));
10875 assert!(world.colliders_overlap(shaped, near));
10877 assert!(world.colliders_overlap(near, shaped));
10878 assert!(!world.colliders_overlap(shaped, far));
10879 assert!(!world.colliders_overlap(far, shaped));
10880 assert!(world.colliders_overlap(near, far));
10882 assert!(!world.colliders_overlap(shaped, bare));
10884 assert!(!world.colliders_overlap(near, bare));
10885 assert!(!world.colliders_overlap(near, 99));
10886 assert!(world.set_body_shape(far, &half_unit_box()));
10889 assert!(!world.colliders_overlap(shaped, far));
10890 assert!(world.set_body_shape(near, &half_unit_box()));
10891 assert!(world.colliders_overlap(shaped, near));
10892 }
10893
10894 #[test]
10899 fn sphere_rests_on_a_shaped_box_at_face_plus_radius() {
10900 for box_first in [true, false] {
10901 let mut world = PhysicsWorld::new(SolverConfig::default());
10902 let ball = || {
10903 RigidBody::new(
10904 Vec3Fix::new(Fix128::ZERO, Fix128::from_ratio(8, 5), Fix128::ZERO),
10905 Fix128::ONE,
10906 )
10907 };
10908 let (floor, b) = if box_first {
10909 let f = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10910 let b = world.add_body_with_radius(ball(), Fix128::from_ratio(1, 2));
10911 (f, b)
10912 } else {
10913 let b = world.add_body_with_radius(ball(), Fix128::from_ratio(1, 2));
10914 let f = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10915 (f, b)
10916 };
10917 assert!(world.set_body_shape(floor, &half_unit_box()));
10918 for _ in 0..120 {
10919 world.step(Fix128::from_ratio(1, 60));
10920 }
10921 let y = world.bodies[b].position.y.to_f64();
10922 assert!((y - 1.5).abs() < 0.02, "box_first {box_first}: y {y}");
10923 assert!(world.bodies[b].position.x.abs() < Fix128::from_ratio(1, 1000));
10924 }
10925 }
10926
10927 #[test]
10932 fn joint_motor_accessors_and_removal_remap() {
10933 let mut world = quiet_world();
10934 for x in 0..3 {
10935 world.add_body(RigidBody::new(Vec3Fix::from_int(x, 0, 0), Fix128::ONE));
10936 }
10937 let ball = |a, b| {
10938 Joint::Ball(crate::joint::BallJoint::new(
10939 a,
10940 b,
10941 Vec3Fix::ZERO,
10942 Vec3Fix::ZERO,
10943 ))
10944 };
10945 let j0 = world.add_joint(ball(0, 1));
10946 let j1 = world.add_joint(ball(1, 2));
10947 let pd = crate::motor::PdController::new(Fix128::ONE, Fix128::ONE, Fix128::from_int(10));
10948 let m0 = world.add_joint_motor(j0, pd);
10949 let m1 = world.add_joint_motor(j1, pd);
10950 assert!(world.set_joint_motor_velocity_target(m1, Fix128::from_int(2)));
10951 assert!(!world.set_joint_motor_velocity_target(9, Fix128::ONE));
10952 assert_eq!(
10953 world.joint_motor_mut(m1).unwrap().controller.mode,
10954 crate::motor::MotorMode::Velocity
10955 );
10956 assert!(world.disable_joint_motor(m1));
10957 assert!(!world.disable_joint_motor(9));
10958 assert_eq!(
10959 world.joint_motor_mut(m1).unwrap().controller.mode,
10960 crate::motor::MotorMode::Off
10961 );
10962 assert!(world.joint_motor_mut(9).is_none());
10963
10964 let r0 = world.add_joint_motor_3d(
10965 j0,
10966 Vec3Fix::from_int(1, 1, 1),
10967 Vec3Fix::from_int(1, 1, 1),
10968 Fix128::from_int(5),
10969 );
10970 let r1 = world.add_joint_motor_3d(
10971 j1,
10972 Vec3Fix::from_int(1, 1, 1),
10973 Vec3Fix::from_int(1, 1, 1),
10974 Fix128::from_int(5),
10975 );
10976 let target = QuatFix::from_axis_angle(Vec3Fix::from_int(0, 1, 0), Fix128::from_ratio(1, 4));
10977 assert!(world.set_joint_motor_3d_rotation_target(r1, target));
10978 assert!(!world.set_joint_motor_3d_rotation_target(9, target));
10979 assert_eq!(
10980 world.joint_motor_3d_mut(r1).unwrap().target_rotation,
10981 target
10982 );
10983 assert!(world.joint_motor_3d_mut(9).is_none());
10984 let _ = (m0, r0);
10985
10986 assert!(world.remove_joint(7).is_none());
10987 assert!(world.remove_joint(j0).is_some());
10988 assert_eq!(world.joint_motors.len(), 1);
10990 assert_eq!(world.joint_motors[0].joint_index, 0);
10991 assert_eq!(world.joint_motors_3d.len(), 1);
10992 assert_eq!(world.joint_motors_3d[0].0, 0);
10993 assert_eq!(world.joint_motors_3d[0].1.target_rotation, target);
10994 }
10995
10996 #[test]
11003 fn contact_forces_are_lambda_over_w_h_squared() {
11004 let mut world = PhysicsWorld::new(SolverConfig::default());
11005 let a = world.add_body(RigidBody::new(Vec3Fix::from_int(0, 1, 0), Fix128::ONE));
11006 let b = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
11007 let s = world.add_body(RigidBody::new_static(Vec3Fix::from_int(5, 0, 0)));
11008 let contact = Contact {
11009 depth: Fix128::from_ratio(1, 100),
11010 normal: Vec3Fix::from_int(0, 1, 0),
11011 point_a: Vec3Fix::from_int(0, 1, 0),
11012 point_b: Vec3Fix::ZERO,
11013 };
11014 let mut c = ContactConstraint::new(a, b, contact);
11015 c.cached_lambda = Fix128::from_ratio(1, 2);
11016 let mut dead = ContactConstraint::new(b, s, contact);
11017 dead.cached_lambda = Fix128::ONE;
11018 world.contact_constraints = vec![c, dead];
11019 let dt = Fix128::ONE;
11020 let forces = world.contact_forces(dt);
11021 assert_eq!(forces.len(), 1);
11022 assert_eq!(forces[0].0, Vec3Fix::from_int(0, 1, 0));
11023 assert_eq!(forces[0].1, Vec3Fix::from_int(0, 1, 0));
11024 assert_eq!(forces[0].2, Fix128::from_int(32));
11025 assert_eq!(
11026 world.contact_constraint_force(&world.contact_constraints[0], dt),
11027 Some(Fix128::from_int(32))
11028 );
11029 assert_eq!(
11030 world.contact_constraint_force(&world.contact_constraints[1], dt),
11031 None
11032 );
11033 let arrows = world.contact_arrows(dt);
11034 assert_eq!(arrows.len(), 1);
11035 assert_eq!(arrows[0].force_magnitude, Fix128::from_int(32));
11036 let friction = world.contact_friction_arrows(dt);
11037 assert_eq!(friction.len(), 2);
11038 let mu_f = (Fix128::from_int(32) * Fix128::from_ratio(3, 10)).to_f64();
11039 for f in &friction {
11040 assert!((f.force_magnitude.to_f64() - 9.6).abs() < 1e-9 && (mu_f - 9.6).abs() < 1e-9);
11041 }
11042 let cones = world.contact_friction_cones(dt);
11043 assert_eq!(cones.len(), 1);
11044 assert_eq!(cones[0].height, Fix128::from_int(32));
11045 }
11046
11047 #[test]
11050 fn static_collider_add_remove_count() {
11051 let mut world = quiet_world();
11052 let plane = |h: i64| {
11053 crate::static_collider::StaticCollider::Plane(
11054 crate::plane_collider::PlaneCollider::new(
11055 Vec3Fix::from_int(0, 1, 0),
11056 Fix128::from_int(h),
11057 ),
11058 )
11059 };
11060 assert_eq!(world.add_static_collider(plane(0)), 0);
11061 assert_eq!(world.add_static_collider(plane(1)), 1);
11062 assert_eq!(world.static_collider_count(), 2);
11063 assert!(world.remove_static_collider(2).is_none());
11064 assert!(world.remove_static_collider(0).is_some());
11065 assert_eq!(world.static_collider_count(), 1);
11066 let c = world.static_colliders_slice()[0]
11069 .collide_sphere(Vec3Fix::from_int(0, 1, 0), Fix128::from_ratio(1, 2))
11070 .expect("touching the plane at y = 1");
11071 assert!((c.depth.to_f64() - 0.5).abs() < 1e-9, "{c:?}");
11072 }
11073
11074 #[test]
11080 fn dynamic_tree_and_hybrid_broadphases_find_the_pairs() {
11081 for kind in [Broadphase::DynamicTree, Broadphase::Hybrid] {
11082 let mut world = quiet_world();
11083 world.set_broadphase(kind);
11084 assert_eq!(world.broadphase(), kind);
11085 let a =
11086 world.add_body_with_radius(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE), Fix128::ONE);
11087 let b = world.add_body_with_radius(
11088 RigidBody::new(
11089 Vec3Fix::new(Fix128::from_ratio(3, 2), Fix128::ZERO, Fix128::ZERO),
11090 Fix128::ONE,
11091 ),
11092 Fix128::ONE,
11093 );
11094 let far = world.add_body_with_radius(
11095 RigidBody::new(Vec3Fix::from_int(50, 0, 0), Fix128::ONE),
11096 Fix128::ONE,
11097 );
11098 let bare = world.add_body(RigidBody::new(Vec3Fix::from_int(-50, 0, 0), Fix128::ONE));
11099 world.step(Fix128::from_ratio(1, 60));
11100 let gap = (world.bodies[b].position - world.bodies[a].position).length();
11101 assert!(gap > Fix128::from_ratio(3, 2), "{kind:?}: gap {gap:?}");
11102 assert_eq!(world.bodies[far].position, Vec3Fix::from_int(50, 0, 0));
11103 if kind == Broadphase::DynamicTree {
11104 assert_eq!(world.broadphase_stats().proxies, 3);
11105 let bx = world.broadphase_proxy_aabb(far).expect("proxy");
11106 assert!(bx.min.x <= Fix128::from_int(49) && bx.max.x >= Fix128::from_int(51));
11107 assert!(world.broadphase_proxy_aabb(bare).is_none());
11108 assert!(world.broadphase_proxy_aabb(99).is_none());
11109 }
11110 }
11111 }
11112
11113 struct ConstantPush {
11115 body: usize,
11116 force: Vec3Fix,
11117 fail: bool,
11118 }
11119
11120 impl crate::world_participant::Participant for ConstantPush {
11121 fn kind(&self) -> crate::world_participant::ParticipantKind {
11122 crate::world_participant::ParticipantKind::new(0x5055_5348)
11123 }
11124 fn substep(
11125 &mut self,
11126 ctx: &mut crate::world_participant::SubstepCtx<'_>,
11127 _h: Fix128,
11128 ) -> Result<(), crate::world_participant::ParticipantFault> {
11129 if self.fail {
11130 return Err(crate::world_participant::ParticipantFault::InvalidState);
11131 }
11132 ctx.add_force(self.body, self.force)
11133 .map_err(|_| crate::world_participant::ParticipantFault::InvalidState)
11134 }
11135 fn observe(&self, _: &mut crate::world_participant::ObservationSink) {}
11136 fn write_state(&self, _: &mut Vec<u8>) {}
11137 fn check_state(&self, _: &[u8]) -> Result<(), crate::world_participant::StateError> {
11138 Ok(())
11139 }
11140 fn read_state(&mut self, _: &[u8]) {}
11141 }
11142
11143 #[test]
11149 fn tgs_participant_force_gives_f_dt_over_m() {
11150 for with_joint in [false, true] {
11151 for fail in [false, true] {
11152 let mut world = PhysicsWorld::new(SolverConfig {
11153 gravity: Vec3Fix::ZERO,
11154 damping: Fix128::ONE,
11155 solver_backend: SolverBackend::Tgs,
11156 ..Default::default()
11157 });
11158 let body = world.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::from_int(2)));
11159 if with_joint {
11160 let other =
11161 world.add_body(RigidBody::new(Vec3Fix::from_int(0, 10, 0), Fix128::ONE));
11162 let anchor = world.add_body(RigidBody::new_static(Vec3Fix::from_int(0, 12, 0)));
11163 world.add_joint(Joint::Ball(crate::joint::BallJoint::new(
11164 other,
11165 anchor,
11166 Vec3Fix::ZERO,
11167 Vec3Fix::from_int(0, -2, 0),
11168 )));
11169 }
11170 world
11171 .add_participant(Box::new(ConstantPush {
11172 body,
11173 force: Vec3Fix::from_int(4, 0, 0),
11174 fail,
11175 }))
11176 .expect("register");
11177 world.step(Fix128::from_ratio(1, 64));
11178 let v = world.bodies[body].velocity;
11179 if fail {
11180 assert_eq!(v, Vec3Fix::ZERO, "joint {with_joint}");
11181 assert!(
11182 matches!(
11183 world.fault(),
11184 Some(crate::world_participant::WorldFault::Participant {
11185 index: 0,
11186 ..
11187 })
11188 ),
11189 "joint {with_joint}: {:?}",
11190 world.fault()
11191 );
11192 } else {
11193 assert!(
11194 (v.x.to_f64() - 1.0 / 32.0).abs() < 1e-12,
11195 "joint {with_joint}: v {v:?}"
11196 );
11197 assert!(
11198 v.y.is_zero() && v.z.is_zero(),
11199 "joint {with_joint}: v {v:?}"
11200 );
11201 assert_eq!(world.fault(), None);
11202 }
11203 }
11204 }
11205 }
11206
11207 #[test]
11212 fn checked_world_inv_inertia_apply_of_an_isotropic_body_is_c_tau() {
11213 let c = Fix128::from_ratio(5, 2);
11214 let tau = Vec3Fix::new(
11215 Fix128::from_ratio(3, 7),
11216 Fix128::from_ratio(-11, 13),
11217 Fix128::from_ratio(1, 3),
11218 );
11219 let expected = Vec3Fix::new(tau.x * c, tau.y * c, tau.z * c);
11220 let mut differ = 0;
11221 for i in 0..16_i64 {
11222 let axis = Vec3Fix::new(
11223 Fix128::from_ratio(1 + i, 3),
11224 Fix128::from_ratio(2 - i, 5),
11225 Fix128::from_ratio(3 + 2 * i, 7),
11226 )
11227 .normalize();
11228 for k in 1..=4_i64 {
11229 let mut b = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
11230 b.inv_inertia = Vec3Fix::new(c, c, c);
11231 b.rotation =
11232 QuatFix::from_axis_angle(axis, Fix128::from_ratio(7 * k + i, 9)).normalize();
11233 let got = b.checked_world_inv_inertia_apply(tau);
11234 assert_eq!(got, Some(b.world_inv_inertia_apply(tau)));
11235 differ += usize::from(got != Some(expected));
11236 }
11237 }
11238 assert_eq!(differ, 0, "{differ}/64 orientations differ from c·τ");
11239 let mut big = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
11240 big.inv_inertia = Vec3Fix::new(
11241 Fix128::from_int(4),
11242 Fix128::from_int(4),
11243 Fix128::from_int(4),
11244 );
11245 let huge = Vec3Fix::new(Fix128::from_int(1_i64 << 62), Fix128::ZERO, Fix128::ZERO);
11246 assert_eq!(big.checked_world_inv_inertia_apply(huge), None);
11247 }
11248}
11249
11250#[cfg(all(test, loom))]
11265mod loom_tests {
11266 use super::*;
11267 use loom::cell::UnsafeCell;
11268 use loom::sync::Arc;
11269 use loom::thread;
11270
11271 fn v3(x: i64, y: i64, z: i64) -> Vec3Fix {
11272 Vec3Fix::from_int(x, y, z)
11273 }
11274
11275 fn batches_of(world: &mut PhysicsWorld) -> Vec<Vec<(Option<usize>, Option<usize>)>> {
11278 world.rebuild_batches();
11279 let dynamic = |i: usize| (!world.bodies[i].inv_mass.is_zero()).then_some(i);
11280 world
11281 .constraint_batches
11282 .iter()
11283 .map(|b| {
11284 let mut pairs = Vec::new();
11285 for &ci in &b.distance_indices {
11286 let c = world.distance_constraints[ci];
11287 pairs.push((dynamic(c.body_a), dynamic(c.body_b)));
11288 }
11289 for &ci in &b.contact_indices {
11290 let c = world.contact_constraints[ci];
11291 pairs.push((dynamic(c.body_a), dynamic(c.body_b)));
11292 }
11293 pairs
11294 })
11295 .collect()
11296 }
11297
11298 fn check_batches(batches: Vec<Vec<(Option<usize>, Option<usize>)>>, n_bodies: usize) {
11299 for batch in batches {
11300 for chunk in batch.chunks(3) {
11302 let chunk: Vec<_> = chunk.to_vec();
11303 let n = n_bodies;
11304 loom::model(move || {
11305 let slots: Arc<Vec<UnsafeCell<u32>>> =
11306 Arc::new((0..n).map(|_| UnsafeCell::new(0)).collect());
11307 let handles: Vec<_> = chunk
11308 .iter()
11309 .copied()
11310 .map(|(a, b)| {
11311 let slots = Arc::clone(&slots);
11312 thread::spawn(move || {
11313 for body in [a, b].into_iter().flatten() {
11314 slots[body].with_mut(|p| unsafe { *p += 1 });
11317 }
11318 })
11319 })
11320 .collect();
11321 for h in handles {
11322 h.join().unwrap();
11323 }
11324 });
11325 }
11326 }
11327 }
11328
11329 #[test]
11330 fn loom_batches_touch_disjoint_dynamic_bodies_static_floor_hub() {
11331 let mut world = PhysicsWorld::new(SolverConfig {
11333 gravity: Vec3Fix::ZERO,
11334 ..SolverConfig::default()
11335 });
11336 let floor =
11337 world.add_body_with_radius(RigidBody::new_static(Vec3Fix::ZERO), Fix128::from_int(4));
11338 let mut ids = Vec::new();
11339 for i in 0..6i64 {
11340 let angle = Fix128::from_ratio(i, 6) * Fix128::TWO_PI;
11341 let (s, c) = angle.sin_cos();
11342 let pos = Vec3Fix::new(
11343 c * Fix128::from_int(4),
11344 s * Fix128::from_int(4),
11345 Fix128::ZERO,
11346 );
11347 ids.push(
11348 world.add_body_with_radius(RigidBody::new_dynamic(pos, Fix128::ONE), Fix128::ONE),
11349 );
11350 }
11351 for w in ids.windows(2).take(2) {
11352 world.add_distance_constraint(DistanceConstraint {
11353 body_a: w[0],
11354 body_b: w[1],
11355 local_anchor_a: Vec3Fix::ZERO,
11356 local_anchor_b: Vec3Fix::ZERO,
11357 target_distance: Fix128::from_int(4),
11358 compliance: Fix128::ZERO,
11359 cached_lambda: Fix128::ZERO,
11360 });
11361 }
11362 let _ = floor;
11363 world.clear_contacts();
11364 world.detect_collisions();
11365 assert!(
11366 !world.contact_constraints.is_empty(),
11367 "spheres must touch the floor"
11368 );
11369 let n = world.bodies.len();
11370 check_batches(batches_of(&mut world), n);
11371 }
11372
11373 #[test]
11374 fn loom_batches_touch_disjoint_dynamic_bodies_dynamic_hub() {
11375 let mut world = PhysicsWorld::new(SolverConfig {
11378 gravity: Vec3Fix::ZERO,
11379 ..SolverConfig::default()
11380 });
11381 let hub = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
11382 for i in 0..5i64 {
11383 let spoke = world.add_body(RigidBody::new_dynamic(v3(3 * (i + 1), 0, 0), Fix128::ONE));
11384 world.add_distance_constraint(DistanceConstraint {
11385 body_a: hub,
11386 body_b: spoke,
11387 local_anchor_a: Vec3Fix::ZERO,
11388 local_anchor_b: Vec3Fix::ZERO,
11389 target_distance: Fix128::from_int(3 * (i + 1)),
11390 compliance: Fix128::ZERO,
11391 cached_lambda: Fix128::ZERO,
11392 });
11393 }
11394 let n = world.bodies.len();
11395 let batches = batches_of(&mut world);
11396 assert_eq!(batches.len(), 5, "hub forces one batch per spoke");
11397 check_batches(batches, n);
11398 }
11399}