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>,
1685 joint_motors: Vec<crate::motor::JointMotor>,
1688 joint_motors_3d: Vec<(usize, crate::motor::PdController3D)>,
1691 pub force_fields: Vec<ForceFieldInstance>,
1693 pub events: EventCollector,
1695 pub islands: IslandManager,
1697 body_collision_radii: Vec<Option<Fix128>>,
1700 broadphase: Broadphase,
1702 broadphase_tree: crate::dynamic_bvh::DynamicAabbTree,
1704 broadphase_proxies: Vec<Option<u32>>,
1706 broadphase_hybrid: BroadphaseHybrid,
1708 body_colliders: Vec<Option<crate::body_collider::BodyCollider>>,
1712 body_filters: Vec<CollisionFilter>,
1714 overflow_detected: bool,
1723 kinematic_substeps_left: usize,
1729 #[cfg(feature = "std")]
1733 tgs_impulse_cache: crate::solver_tgs::ImpulseCache,
1734 #[cfg(feature = "std")]
1741 body_stable_ids: Vec<u64>,
1742 #[cfg(feature = "std")]
1744 next_body_stable_id: u64,
1745 sleep_skip: bool,
1747 park: ParkState,
1749 park_generation: u64,
1752 stage_work: StageWork,
1754 sdf_contact_log: Vec<crate::sdf_collider::SdfContact>,
1758 sdf_contact_substep: usize,
1760 sdf_generations: Vec<u64>,
1764 #[cfg(feature = "parallel")]
1768 sdf_contact_scratch: Vec<Vec<crate::sdf_collider::SdfContact>>,
1769 #[cfg(feature = "std")]
1774 participants: std::sync::Mutex<Vec<Box<dyn crate::world_participant::Participant>>>,
1775 #[cfg(feature = "std")]
1779 participant_plan: Option<crate::world_participant::ParticipantPlan>,
1780 fault: Option<crate::world_participant::WorldFault>,
1782 fields: crate::world_participant::FieldBoard,
1784 ccd: WorldCcdConfig,
1786}
1787
1788fn fnv1a_fold(hash: &mut u64, bytes: &[u8]) {
1793 for &b in bytes {
1794 *hash ^= u64::from(b);
1795 *hash = hash.wrapping_mul(0x0000_0001_0000_01b3);
1796 }
1797}
1798
1799#[cfg(feature = "std")]
1802fn sdf_contact_of(
1803 collider: Option<&crate::body_collider::BodyCollider>,
1804 body: &RigidBody,
1805 radius: Fix128,
1806 sdf: &crate::sdf_collider::SdfCollider,
1807) -> Option<crate::collider::Contact> {
1808 match collider {
1809 Some(c) => c.sdf_contact(body.position, body.rotation, sdf),
1810 None => crate::sdf_collider::collide_sphere_sdf(body.position, radius, sdf),
1811 }
1812}
1813
1814fn kinematic_substep_pose(
1822 position: Vec3Fix,
1823 rotation: QuatFix,
1824 target_pos: Vec3Fix,
1825 target_rot: QuatFix,
1826 left: usize,
1827) -> (Vec3Fix, QuatFix) {
1828 if left <= 1 {
1829 return (target_pos, target_rot);
1830 }
1831 let frac = Fix128::ONE / Fix128::from_int(left as i64);
1832 (
1833 position + (target_pos - position) * frac,
1834 crate::interpolation::slerp(rotation, target_rot, frac),
1835 )
1836}
1837
1838impl PhysicsWorld {
1839 #[must_use]
1859 pub fn new(config: SolverConfig) -> Self {
1860 Self {
1861 config,
1862 bodies: Vec::new(),
1863 distance_constraints: Vec::new(),
1864 contact_constraints: Vec::new(),
1865 sdf_colliders: Vec::new(),
1866 sdf_collision_radius: Fix128::from_ratio(1, 2), static_colliders: Vec::new(),
1868 constraint_batches: Vec::new(),
1869 batches_dirty: true,
1870 batch_static_bodies: Vec::new(),
1871 contact_cache: crate::contact_cache::ContactCache::new(),
1872 material_table: crate::material::MaterialTable::new(),
1873 body_materials: Vec::new(),
1874 #[cfg(feature = "std")]
1875 pre_solve_hooks: Vec::new(),
1876 #[cfg(feature = "std")]
1877 contact_modifiers: Vec::new(),
1878 #[cfg(feature = "std")]
1879 contact_discarded: Vec::new(),
1880 contact_pre_vn: Vec::new(),
1881 free_rotation: Vec::new(),
1882 predicted_pose: Vec::new(),
1883 #[cfg(feature = "gpu-solver-bridge")]
1884 gpu_solver_bridge: None,
1885 joints: Vec::new(),
1886 joint_motors: Vec::new(),
1887 joint_motors_3d: Vec::new(),
1888 force_fields: Vec::new(),
1889 events: EventCollector::new(),
1890 islands: IslandManager::new(0, SleepConfig::default()),
1891 body_collision_radii: Vec::new(),
1892 broadphase: Broadphase::default(),
1893 broadphase_tree: crate::dynamic_bvh::DynamicAabbTree::new(),
1894 broadphase_proxies: Vec::new(),
1895 broadphase_hybrid: BroadphaseHybrid::new(),
1896 body_colliders: Vec::new(),
1897 body_filters: Vec::new(),
1898 overflow_detected: false,
1899 kinematic_substeps_left: 0,
1900 #[cfg(feature = "std")]
1901 tgs_impulse_cache: crate::solver_tgs::ImpulseCache::new(),
1902 #[cfg(feature = "std")]
1903 body_stable_ids: Vec::new(),
1904 #[cfg(feature = "std")]
1905 next_body_stable_id: 0,
1906 sleep_skip: true,
1907 park: ParkState::default(),
1908 park_generation: 0,
1909 stage_work: StageWork::default(),
1910 sdf_contact_log: Vec::new(),
1911 sdf_contact_substep: 0,
1912 sdf_generations: Vec::new(),
1913 #[cfg(feature = "parallel")]
1914 sdf_contact_scratch: Vec::new(),
1915 #[cfg(feature = "std")]
1916 participants: std::sync::Mutex::new(Vec::new()),
1917 #[cfg(feature = "std")]
1918 participant_plan: None,
1919 fault: None,
1920 fields: crate::world_participant::FieldBoard::new(),
1921 ccd: WorldCcdConfig::new(),
1922 }
1923 }
1924
1925 pub fn reset_world(&mut self) {
1958 *self = Self::new(self.config);
1959 }
1960
1961 #[cfg(feature = "gpu-solver-bridge")]
1980 pub fn set_gpu_solver_bridge(
1981 &mut self,
1982 bridge: Option<Box<dyn crate::gpu_bridge::GpuSolverBridge + Send + Sync>>,
1983 ) {
1984 self.gpu_solver_bridge = bridge;
1985 }
1986
1987 #[cfg(feature = "gpu-solver-bridge")]
1991 pub fn take_gpu_solver_bridge(
1992 &mut self,
1993 ) -> Option<Box<dyn crate::gpu_bridge::GpuSolverBridge + Send + Sync>> {
1994 self.gpu_solver_bridge.take()
1995 }
1996
1997 #[cfg(feature = "gpu-solver-bridge")]
2000 #[must_use]
2001 pub const fn gpu_solver_bridge_installed(&self) -> bool {
2002 self.gpu_solver_bridge.is_some()
2003 }
2004
2005 #[cfg(feature = "std")]
2009 fn sync_body_stable_ids(&mut self) {
2010 let n = self.bodies.len();
2011 self.body_stable_ids.truncate(n);
2012 while self.body_stable_ids.len() < n {
2013 self.body_stable_ids.push(self.next_body_stable_id);
2014 self.next_body_stable_id += 1;
2015 }
2016 }
2017
2018 pub fn add_body(&mut self, mut body: RigidBody) -> usize {
2023 body.make_rotations_unit();
2024 let idx = self.bodies.len();
2025 self.bodies.push(body);
2026 self.body_materials.push(crate::material::DEFAULT_MATERIAL);
2027 self.body_collision_radii.push(None);
2028 self.body_colliders.push(None);
2029 self.body_filters.push(CollisionFilter::DEFAULT);
2030 self.islands.resize(idx + 1);
2031 #[cfg(feature = "std")]
2032 self.sync_body_stable_ids();
2033 idx
2034 }
2035
2036 pub fn add_body_with_radius(&mut self, mut body: RigidBody, radius: Fix128) -> usize {
2044 body.make_rotations_unit();
2045 let idx = self.bodies.len();
2046 self.bodies.push(body);
2047 self.body_materials.push(crate::material::DEFAULT_MATERIAL);
2048 self.body_collision_radii.push(Some(radius));
2049 self.body_colliders.push(None);
2050 self.body_filters.push(CollisionFilter::DEFAULT);
2051 self.islands.resize(idx + 1);
2052 #[cfg(feature = "std")]
2053 self.sync_body_stable_ids();
2054 idx
2055 }
2056
2057 pub fn add_shaped_body(
2073 &mut self,
2074 shape: &crate::shape::Shape,
2075 density: Fix128,
2076 position: Vec3Fix,
2077 ) -> Result<usize, crate::shape::ShapeError> {
2078 let (mass, inertia) = shape.mass_and_inertia(density)?;
2079 let mut body = RigidBody::new_dynamic(position, mass);
2080 body.inv_inertia = Vec3Fix::new(
2081 Fix128::ONE / inertia.x,
2082 Fix128::ONE / inertia.y,
2083 Fix128::ONE / inertia.z,
2084 );
2085 let idx = self.add_body_with_radius(body, shape.bounding_radius());
2086 self.body_colliders[idx] = Some(crate::body_collider::BodyCollider::Shape(*shape));
2087 Ok(idx)
2088 }
2089
2090 pub fn set_body_shape(&mut self, body_idx: usize, shape: &crate::shape::Shape) -> bool {
2100 if body_idx >= self.bodies.len() || body_idx >= self.body_colliders.len() {
2101 return false;
2102 }
2103 self.park_generation = self.park_generation.wrapping_add(1);
2104 self.body_collision_radii[body_idx] = Some(shape.bounding_radius());
2105 self.body_colliders[body_idx] = Some(crate::body_collider::BodyCollider::Shape(*shape));
2106 true
2107 }
2108
2109 #[must_use]
2118 pub fn colliders_overlap(&self, a: usize, b: usize) -> bool {
2119 let (Some(body_a), Some(body_b)) = (self.bodies.get(a), self.bodies.get(b)) else {
2120 return false;
2121 };
2122 let collider = |i: usize| self.body_colliders.get(i).and_then(Option::as_ref);
2123 let radius = |i: usize| self.body_collision_radii.get(i).copied().flatten();
2124 let pose_a = (body_a.position, body_a.rotation);
2125 let pose_b = (body_b.position, body_b.rotation);
2126 match (collider(a), collider(b)) {
2127 (Some(ca), Some(cb)) => crate::body_collider::colliders_meet(ca, pose_a, cb, pose_b),
2128 (Some(ca), None) => radius(b).is_some_and(|rb| {
2129 crate::body_collider::collider_meets_sphere(
2130 ca,
2131 pose_a,
2132 crate::collider::Sphere::new(body_b.position, rb),
2133 )
2134 }),
2135 (None, Some(cb)) => radius(a).is_some_and(|ra| {
2136 crate::body_collider::collider_meets_sphere(
2137 cb,
2138 pose_b,
2139 crate::collider::Sphere::new(body_a.position, ra),
2140 )
2141 }),
2142 (None, None) => match (radius(a), radius(b)) {
2143 (Some(ra), Some(rb)) => {
2144 let reach = ra + rb;
2145 (body_a.position - body_b.position).length_squared() < reach * reach
2146 }
2147 _ => false,
2148 },
2149 }
2150 }
2151
2152 pub fn add_compound_body(
2172 &mut self,
2173 compound: &crate::compound::CompoundShape,
2174 density: Fix128,
2175 position: Vec3Fix,
2176 ) -> Result<usize, crate::shape::ShapeError> {
2177 use crate::shape::ShapeError;
2178 if density <= Fix128::ZERO {
2179 return Err(ShapeError::NonPositiveDensity);
2180 }
2181 if compound.is_empty() {
2182 return Err(ShapeError::DegenerateShape);
2183 }
2184 let props = compound.mass_properties(density);
2185 if props.mass <= Fix128::ZERO {
2186 return Err(ShapeError::DegenerateShape);
2187 }
2188 let (moments, axes) = crate::mass_properties::principal_axes(props.inertia_tensor);
2189 if !(moments.x > Fix128::ZERO && moments.y > Fix128::ZERO && moments.z > Fix128::ZERO) {
2190 return Err(ShapeError::MassNotRepresentable);
2191 }
2192 let frame = crate::body_collider::quat_from_rotation(axes);
2194 let to_principal = frame.conjugate();
2195 let mut stored = crate::compound::CompoundShape::new();
2196 for child in &compound.children {
2197 stored.children.push(crate::compound::CompoundChild {
2198 shape: child.shape.clone(),
2199 local_position: to_principal
2200 .rotate_vec(child.local_position - props.center_of_mass),
2201 local_rotation: to_principal.mul(child.local_rotation),
2202 });
2203 }
2204 let collider = crate::body_collider::BodyCollider::Compound(stored);
2205 let radius = collider.bounding_radius();
2206 let estimate = props.mass.to_f64() * radius.to_f64() * radius.to_f64();
2208 if !(estimate.is_finite() && estimate < (1u64 << 61) as f64) {
2209 return Err(ShapeError::MassNotRepresentable);
2210 }
2211 let mut body = RigidBody::new_dynamic(position, props.mass);
2212 body.rotation = frame;
2213 body.inv_inertia = Vec3Fix::new(
2214 Fix128::ONE / moments.x,
2215 Fix128::ONE / moments.y,
2216 Fix128::ONE / moments.z,
2217 );
2218 let idx = self.add_body_with_radius(body, radius);
2219 self.body_colliders[idx] = Some(collider);
2220 Ok(idx)
2221 }
2222
2223 fn pad_body_tables(&mut self) {
2226 let n = self.bodies.len();
2227 if self.body_materials.len() < n {
2228 self.body_materials
2229 .resize(n, crate::material::DEFAULT_MATERIAL);
2230 }
2231 if self.body_collision_radii.len() < n {
2232 self.body_collision_radii.resize(n, None);
2233 }
2234 if self.body_colliders.len() < n {
2235 self.body_colliders.resize(n, None);
2236 }
2237 if self.body_filters.len() < n {
2238 self.body_filters.resize(n, CollisionFilter::DEFAULT);
2239 }
2240 }
2241
2242 pub(crate) fn remove_body_with_attachments(
2247 &mut self,
2248 idx: usize,
2249 ) -> Option<(RigidBody, BodyAttachments)> {
2250 let attachments = BodyAttachments {
2253 material: self
2254 .body_materials
2255 .get(idx)
2256 .copied()
2257 .unwrap_or(crate::material::DEFAULT_MATERIAL),
2258 collision_radius: self.body_collision_radii.get(idx).copied().flatten(),
2259 collider: self.body_colliders.get(idx).cloned().flatten(),
2260 filter: self.body_filters.get(idx).copied().unwrap_or_default(),
2261 };
2262 let body = self.remove_body(idx)?;
2263 Some((body, attachments))
2264 }
2265
2266 pub(crate) fn add_body_with_attachments(
2269 &mut self,
2270 body: RigidBody,
2271 attachments: BodyAttachments,
2272 ) -> usize {
2273 let idx = self.add_body(body);
2274 self.body_materials[idx] = attachments.material;
2275 self.body_collision_radii[idx] = attachments.collision_radius;
2276 self.body_colliders[idx] = attachments.collider;
2277 self.body_filters[idx] = attachments.filter;
2278 idx
2279 }
2280
2281 pub fn remove_body(&mut self, idx: usize) -> Option<RigidBody> {
2287 if idx >= self.bodies.len() {
2288 return None;
2289 }
2290 let last = self.bodies.len() - 1;
2291
2292 self.distance_constraints
2299 .retain(|c| c.body_a != idx && c.body_b != idx);
2300 self.contact_constraints
2301 .retain(|c| c.body_a != idx && c.body_b != idx);
2302 if !self.joint_motors.is_empty() || !self.joint_motors_3d.is_empty() {
2303 let mut next = 0;
2306 let joint_map: Vec<Option<usize>> = self
2307 .joints
2308 .iter()
2309 .map(|j| {
2310 let (a, b) = j.bodies();
2311 if a != idx && b != idx {
2312 next += 1;
2313 Some(next - 1)
2314 } else {
2315 None
2316 }
2317 })
2318 .collect();
2319 self.remap_motor_joints(|j| joint_map.get(j).copied().flatten());
2320 }
2321 self.joints.retain(|j| {
2322 let (a, b) = j.bodies();
2323 a != idx && b != idx
2324 });
2325
2326 #[cfg(feature = "std")]
2329 self.sync_body_stable_ids();
2330 self.pad_body_tables();
2333 let removed = self.bodies.swap_remove(idx);
2334 self.body_materials.swap_remove(idx);
2335 self.body_collision_radii.swap_remove(idx);
2336 self.body_colliders.swap_remove(idx);
2337 self.body_filters.swap_remove(idx);
2338 #[cfg(feature = "std")]
2339 self.body_stable_ids.swap_remove(idx);
2340
2341 if idx != last {
2343 for c in &mut self.distance_constraints {
2344 if c.body_a == last {
2345 c.body_a = idx;
2346 }
2347 if c.body_b == last {
2348 c.body_b = idx;
2349 }
2350 }
2351 for c in &mut self.contact_constraints {
2352 if c.body_a == last {
2353 c.body_a = idx;
2354 }
2355 if c.body_b == last {
2356 c.body_b = idx;
2357 }
2358 }
2359 self.remap_joint_indices(last, idx);
2360 }
2361
2362 let new_len = self.bodies.len();
2364 self.islands = IslandManager::new(new_len, self.islands.config);
2365 for j in &self.joints {
2366 let (a, b) = j.bodies();
2367 if a < new_len && b < new_len {
2368 self.islands.union(a, b);
2369 }
2370 }
2371
2372 self.park_generation = self.park_generation.wrapping_add(1);
2373 self.batches_dirty = true;
2374 Some(removed)
2375 }
2376
2377 fn remap_joint_indices(&mut self, from: usize, to: usize) {
2379 macro_rules! remap {
2381 ($j:expr) => {
2382 if $j.body_a == from {
2383 $j.body_a = to;
2384 }
2385 if $j.body_b == from {
2386 $j.body_b = to;
2387 }
2388 };
2389 }
2390 for joint in &mut self.joints {
2391 match joint {
2392 Joint::Ball(j) => {
2393 remap!(j);
2394 }
2395 Joint::Hinge(j) => {
2396 remap!(j);
2397 }
2398 Joint::Fixed(j) => {
2399 remap!(j);
2400 }
2401 Joint::Slider(j) => {
2402 remap!(j);
2403 }
2404 Joint::Spring(j) => {
2405 remap!(j);
2406 }
2407 Joint::D6(j) => {
2408 remap!(j);
2409 }
2410 Joint::ConeTwist(j) => {
2411 remap!(j);
2412 }
2413 }
2414 }
2415 }
2416
2417 #[inline]
2419 #[must_use]
2420 pub fn body_count(&self) -> usize {
2421 self.bodies.len()
2422 }
2423
2424 #[must_use]
2426 pub fn active_body_count(&self) -> usize {
2427 self.bodies.len() - self.islands.sleeping_count().min(self.bodies.len())
2428 }
2429
2430 pub fn set_body_material(&mut self, body_idx: usize, material_id: crate::material::MaterialId) {
2432 if body_idx < self.body_materials.len() {
2433 self.body_materials[body_idx] = material_id;
2434 }
2435 }
2436
2437 #[cfg(feature = "std")]
2442 pub fn add_pre_solve_hook(&mut self, hook: PreSolveHook) {
2443 self.pre_solve_hooks.push(hook);
2444 }
2445
2446 #[cfg(feature = "std")]
2448 pub fn clear_pre_solve_hooks(&mut self) {
2449 self.pre_solve_hooks.clear();
2450 }
2451
2452 #[cfg(feature = "std")]
2457 pub fn add_contact_modifier(&mut self, modifier: Box<dyn ContactModifier>) {
2458 self.contact_modifiers.push(modifier);
2459 }
2460
2461 #[cfg(feature = "std")]
2463 pub fn clear_contact_modifiers(&mut self) {
2464 self.contact_modifiers.clear();
2465 }
2466
2467 pub fn begin_frame(&mut self) {
2469 self.contact_cache.begin_frame();
2470 }
2471
2472 pub fn end_frame(&mut self) {
2474 self.contact_cache.end_frame();
2475 }
2476
2477 pub fn add_joint(&mut self, joint: Joint) -> usize {
2485 let (a, b) = joint.bodies();
2486 assert!(
2487 a < self.bodies.len() && b < self.bodies.len(),
2488 "Joint body indices ({}, {}) out of bounds (body count = {})",
2489 a,
2490 b,
2491 self.bodies.len()
2492 );
2493 let idx = self.joints.len();
2494 self.islands.resize(self.bodies.len());
2495 self.islands.union(a, b);
2496 self.joints.push(joint);
2497 idx
2498 }
2499
2500 pub fn remove_joint(&mut self, idx: usize) -> Option<Joint> {
2506 if idx >= self.joints.len() {
2507 return None;
2508 }
2509 let last = self.joints.len() - 1;
2510 self.remap_motor_joints(|j| {
2511 if j == idx {
2512 None
2513 } else if j == last {
2514 Some(idx)
2515 } else {
2516 Some(j)
2517 }
2518 });
2519 Some(self.joints.swap_remove(idx))
2520 }
2521
2522 fn remap_motor_joints(&mut self, map: impl Fn(usize) -> Option<usize>) {
2524 self.joint_motors.retain_mut(|m| match map(m.joint_index) {
2525 Some(j) => {
2526 m.joint_index = j;
2527 true
2528 }
2529 None => false,
2530 });
2531 self.joint_motors_3d
2532 .retain_mut(|(joint, _)| match map(*joint) {
2533 Some(j) => {
2534 *joint = j;
2535 true
2536 }
2537 None => false,
2538 });
2539 }
2540
2541 pub fn add_joint_motor(
2556 &mut self,
2557 joint_index: usize,
2558 controller: crate::motor::PdController,
2559 ) -> usize {
2560 self.joint_motors
2561 .push(crate::motor::JointMotor::new(joint_index, controller));
2562 self.joint_motors.len() - 1
2563 }
2564
2565 #[must_use]
2568 pub fn joint_motor_mut(&mut self, motor: usize) -> Option<&mut crate::motor::JointMotor> {
2569 self.joint_motors.get_mut(motor)
2570 }
2571
2572 pub fn set_joint_motor_velocity_target(&mut self, motor: usize, target: Fix128) -> bool {
2576 match self.joint_motors.get_mut(motor) {
2577 Some(m) => {
2578 m.controller.set_velocity_target(target);
2579 true
2580 }
2581 None => false,
2582 }
2583 }
2584
2585 pub fn disable_joint_motor(&mut self, motor: usize) -> bool {
2588 match self.joint_motors.get_mut(motor) {
2589 Some(m) => {
2590 m.controller.disable();
2591 true
2592 }
2593 None => false,
2594 }
2595 }
2596
2597 pub fn add_joint_motor_3d(
2605 &mut self,
2606 joint_index: usize,
2607 kp: Vec3Fix,
2608 kd: Vec3Fix,
2609 max_torque: Fix128,
2610 ) -> usize {
2611 self.joint_motors_3d.push((
2612 joint_index,
2613 crate::motor::PdController3D::new(kp, kd, max_torque),
2614 ));
2615 self.joint_motors_3d.len() - 1
2616 }
2617
2618 #[must_use]
2620 pub fn joint_motor_3d_mut(
2621 &mut self,
2622 motor: usize,
2623 ) -> Option<&mut crate::motor::PdController3D> {
2624 self.joint_motors_3d.get_mut(motor).map(|(_, c)| c)
2625 }
2626
2627 pub fn set_joint_motor_3d_rotation_target(&mut self, motor: usize, target: QuatFix) -> bool {
2630 match self.joint_motors_3d.get_mut(motor) {
2631 Some((_, c)) => {
2632 c.set_rotation_target(target);
2633 true
2634 }
2635 None => false,
2636 }
2637 }
2638
2639 fn apply_joint_motors(&mut self, dt: Fix128) {
2646 if self.joint_motors.is_empty() && self.joint_motors_3d.is_empty() {
2647 return;
2648 }
2649 let mut asleep: Vec<(usize, Vec3Fix, Vec3Fix)> = Vec::new();
2650 let n = self.bodies.len();
2651 let pairs = self
2652 .joint_motors
2653 .iter()
2654 .map(|m| m.joint_index)
2655 .chain(self.joint_motors_3d.iter().map(|(j, _)| *j));
2656 for j in pairs {
2657 if let Some(joint) = self.joints.get(j) {
2658 let (a, b) = joint.bodies();
2659 for x in [a, b] {
2660 if x < n && self.islands.is_sleeping(x) {
2661 asleep.push((x, self.bodies[x].velocity, self.bodies[x].angular_velocity));
2662 }
2663 }
2664 }
2665 }
2666 crate::motor::apply_motors(&self.joint_motors, &self.joints, &mut self.bodies, dt);
2667 crate::motor::apply_motors_3d(&self.joint_motors_3d, &self.joints, &mut self.bodies, dt);
2668 for (x, v, w) in asleep {
2669 let body = &self.bodies[x];
2670 if (body.velocity != v || body.angular_velocity != w)
2671 && x < self.islands.sleep_data.len()
2672 {
2673 self.islands.wake_island(x);
2674 }
2675 }
2676 }
2677
2678 #[inline]
2680 #[must_use]
2681 pub fn joint_count(&self) -> usize {
2682 self.joints.len()
2683 }
2684
2685 pub fn add_force_field(&mut self, field: ForceFieldInstance) -> usize {
2689 let idx = self.force_fields.len();
2690 self.force_fields.push(field);
2691 idx
2692 }
2693
2694 pub fn remove_force_field(&mut self, idx: usize) -> Option<ForceFieldInstance> {
2696 if idx >= self.force_fields.len() {
2697 return None;
2698 }
2699 Some(self.force_fields.swap_remove(idx))
2700 }
2701
2702 pub fn set_body_collision_radius(&mut self, body_idx: usize, radius: Fix128) {
2706 if body_idx < self.body_collision_radii.len() {
2707 self.park_generation = self.park_generation.wrapping_add(1);
2708 self.body_collision_radii[body_idx] = Some(radius);
2709 }
2710 }
2711
2712 pub fn clear_body_collision_radius(&mut self, body_idx: usize) {
2714 if body_idx < self.body_collision_radii.len() {
2715 self.park_generation = self.park_generation.wrapping_add(1);
2716 self.body_collision_radii[body_idx] = None;
2717 }
2718 }
2719
2720 pub fn set_body_filter(&mut self, body_idx: usize, filter: CollisionFilter) {
2722 if body_idx < self.body_filters.len() {
2723 self.body_filters[body_idx] = filter;
2724 }
2725 }
2726
2727 #[must_use]
2729 pub fn body_filter(&self, body_idx: usize) -> CollisionFilter {
2730 self.body_filters
2731 .get(body_idx)
2732 .copied()
2733 .unwrap_or(CollisionFilter::DEFAULT)
2734 }
2735
2736 #[inline]
2740 #[must_use]
2741 pub fn is_sleeping(&self, body_idx: usize) -> bool {
2742 self.islands.is_sleeping(body_idx)
2743 }
2744
2745 pub fn wake_body(&mut self, body_idx: usize) {
2747 if body_idx >= self.bodies.len() {
2750 return;
2751 }
2752 self.islands.wake_island(body_idx);
2753 }
2754
2755 pub fn set_sleep_config(&mut self, config: SleepConfig) {
2757 self.islands.config = config;
2758 }
2759
2760 pub fn set_sleep_skip(&mut self, enabled: bool) {
2774 self.sleep_skip = enabled;
2775 if !enabled {
2776 self.park.clear();
2777 }
2778 }
2779
2780 #[must_use]
2782 pub const fn sleep_skip(&self) -> bool {
2783 self.sleep_skip
2784 }
2785
2786 #[must_use]
2788 pub const fn stage_work(&self) -> StageWork {
2789 self.stage_work
2790 }
2791
2792 #[must_use]
2807 pub fn observe_body(&self, body_idx: usize) -> Option<BodyObservation> {
2808 let body = self.bodies.get(body_idx)?;
2809 let in_contact = self
2810 .events
2811 .contact_events()
2812 .iter()
2813 .any(|e| e.body_a == body_idx || e.body_b == body_idx);
2814 Some(BodyObservation {
2815 body_index: body_idx,
2816 position: body.position,
2817 velocity: body.velocity,
2818 rotation: body.rotation,
2819 angular_velocity: body.angular_velocity,
2820 sleeping: self.is_sleeping(body_idx),
2821 in_contact,
2822 })
2823 }
2824
2825 #[must_use]
2827 pub fn observe_bodies(&self) -> Vec<BodyObservation> {
2828 (0..self.bodies.len())
2829 .map(|i| {
2830 self.observe_body(i)
2831 .expect("index came from 0..bodies.len(), always in range")
2832 })
2833 .collect()
2834 }
2835
2836 #[inline]
2840 #[must_use]
2841 pub fn contact_events(&self) -> &[crate::event::ContactEvent] {
2842 self.events.contact_events()
2843 }
2844
2845 #[inline]
2847 #[must_use]
2848 pub fn trigger_events(&self) -> &[crate::event::TriggerEvent] {
2849 self.events.trigger_events()
2850 }
2851
2852 #[inline]
2854 pub fn drain_contact_events(&mut self) -> Vec<crate::event::ContactEvent> {
2855 self.events.drain_contact_events()
2856 }
2857
2858 #[inline]
2860 pub fn drain_trigger_events(&mut self) -> Vec<crate::event::TriggerEvent> {
2861 self.events.drain_trigger_events()
2862 }
2863
2864 pub(crate) fn ray_geometry(
2870 &self,
2871 i: usize,
2872 ) -> Option<(Fix128, Option<&crate::body_collider::BodyCollider>)> {
2873 let radius = self.body_collision_radii.get(i).copied().flatten()?;
2874 Some((radius, self.body_colliders.get(i).and_then(Option::as_ref)))
2875 }
2876
2877 pub(crate) fn static_colliders_slice(&self) -> &[crate::static_collider::StaticCollider] {
2879 &self.static_colliders
2880 }
2881
2882 pub(crate) fn contact_constraint_force(
2885 &self,
2886 c: &ContactConstraint,
2887 dt: Fix128,
2888 ) -> Option<Fix128> {
2889 let h = self.substep_dt(dt);
2890 self.contact_force(c, h * h).map(|(_, _, force)| force)
2891 }
2892
2893 #[must_use]
2906 pub fn raycast(
2907 &self,
2908 origin: Vec3Fix,
2909 direction: Vec3Fix,
2910 max_distance: Fix128,
2911 ) -> Option<(usize, Fix128)> {
2912 if direction.length_squared().is_zero() {
2913 return None;
2914 }
2915 let dir_norm = direction.normalize();
2916
2917 let mut primitives = Vec::new();
2919 for i in 0..self.bodies.len() {
2920 if let Some(radius) = self.body_collision_radii.get(i).and_then(|r| *r) {
2921 let pos = self.bodies[i].position;
2922 let half = Vec3Fix::new(radius, radius, radius);
2923 let aabb = crate::collider::AABB::from_center_half(pos, half);
2924 primitives.push(crate::bvh::BvhPrimitive {
2925 aabb,
2926 index: i as u32,
2927 morton: 0,
2928 });
2929 }
2930 }
2931
2932 if primitives.is_empty() {
2933 return None;
2934 }
2935
2936 let endpoint = origin + dir_norm * max_distance;
2938 let ray_min = Vec3Fix::new(
2939 if origin.x < endpoint.x {
2940 origin.x
2941 } else {
2942 endpoint.x
2943 },
2944 if origin.y < endpoint.y {
2945 origin.y
2946 } else {
2947 endpoint.y
2948 },
2949 if origin.z < endpoint.z {
2950 origin.z
2951 } else {
2952 endpoint.z
2953 },
2954 );
2955 let ray_max = Vec3Fix::new(
2956 if origin.x > endpoint.x {
2957 origin.x
2958 } else {
2959 endpoint.x
2960 },
2961 if origin.y > endpoint.y {
2962 origin.y
2963 } else {
2964 endpoint.y
2965 },
2966 if origin.z > endpoint.z {
2967 origin.z
2968 } else {
2969 endpoint.z
2970 },
2971 );
2972 let ray_aabb = crate::collider::AABB {
2973 min: ray_min,
2974 max: ray_max,
2975 };
2976
2977 let bvh = crate::bvh::LinearBvh::build(primitives);
2978 let candidates = bvh.query(&ray_aabb);
2979
2980 let mut best: Option<(usize, Fix128)> = None;
2982 for &prim_idx in &candidates {
2983 let i = prim_idx as usize;
2984 let radius = match self.body_collision_radii.get(i) {
2985 Some(Some(r)) => *r,
2986 _ => continue,
2987 };
2988 let oc = origin - self.bodies[i].position;
2989 let b = oc.dot(dir_norm);
2990 let c = oc.dot(oc) - radius * radius;
2991 let discriminant = b * b - c;
2992 if discriminant < Fix128::ZERO {
2993 continue;
2994 }
2995 let sqrt_d = discriminant.sqrt();
2996 let mut t = -b - sqrt_d;
2998 if t < Fix128::ZERO {
3000 t = -b + sqrt_d;
3001 }
3002 if t < Fix128::ZERO || t > max_distance {
3003 continue;
3004 }
3005 let dominated = match best {
3006 None => true,
3007 Some((_, prev_t)) => t < prev_t,
3008 };
3009 if dominated {
3010 best = Some((i, t));
3011 }
3012 }
3013 best
3014 }
3015
3016 #[must_use]
3033 pub fn combined_material(
3034 &self,
3035 body_a: usize,
3036 body_b: usize,
3037 ) -> crate::material::CombinedMaterial {
3038 let side = |i: usize| -> (crate::material::MaterialId, Option<(Fix128, Fix128)>) {
3039 let Some(&mat) = self.body_materials.get(i) else {
3040 return (crate::material::DEFAULT_MATERIAL, None);
3041 };
3042 if mat != crate::material::DEFAULT_MATERIAL {
3043 return (mat, None);
3044 }
3045 (mat, self.bodies.get(i).map(|b| (b.friction, b.restitution)))
3046 };
3047 let (mat_a, surface_a) = side(body_a);
3048 let (mat_b, surface_b) = side(body_b);
3049 self.material_table
3050 .combine_with_surface(mat_a, surface_a, mat_b, surface_b)
3051 }
3052
3053 pub fn add_distance_constraint(&mut self, constraint: DistanceConstraint) {
3055 self.distance_constraints.push(constraint);
3056 self.batches_dirty = true;
3057 }
3058
3059 pub fn add_contact(&mut self, contact: ContactConstraint) {
3061 let key = crate::contact_cache::BodyPairKey::new(contact.body_a, contact.body_b);
3063 let manifold = self
3064 .contact_cache
3065 .get_or_create(key, contact.friction, contact.restitution);
3066 manifold.add_or_update(
3067 &contact.contact,
3068 contact.contact.point_a,
3069 contact.contact.point_b,
3070 );
3071
3072 self.contact_constraints.push(contact);
3073 self.batches_dirty = true;
3074 }
3075
3076 pub fn add_contact_with_material(&mut self, body_a: usize, body_b: usize, contact: Contact) {
3080 let combined = self.combined_material(body_a, body_b);
3081 let constraint = ContactConstraint {
3082 body_a,
3083 body_b,
3084 contact,
3085 friction: combined.friction,
3086 restitution: combined.restitution,
3087 cached_lambda: Fix128::ZERO,
3088 };
3089 self.add_contact(constraint);
3090 }
3091
3092 pub fn clear_contacts(&mut self) {
3097 self.contact_constraints.clear();
3098 #[cfg(feature = "std")]
3099 self.contact_discarded.clear();
3100 self.batches_dirty = true;
3101 }
3102
3103 pub fn rebuild_batches(&mut self) {
3108 if !self.batches_dirty {
3109 return;
3110 }
3111
3112 self.constraint_batches.clear();
3113
3114 let num_bodies = self.bodies.len();
3120 self.batch_static_bodies.clear();
3121 self.batch_static_bodies
3122 .extend(self.bodies.iter().map(|b| b.inv_mass.is_zero()));
3123
3124 let mut body_colors: Vec<Vec<u64>> = vec![Vec::new(); num_bodies];
3129
3130 for (constraint_idx, constraint) in self.distance_constraints.iter().enumerate() {
3132 let body_a = constraint.body_a;
3133 let body_b = constraint.body_b;
3134
3135 let color =
3137 Self::find_free_color(&body_colors, &self.batch_static_bodies, body_a, body_b);
3138
3139 while self.constraint_batches.len() <= color {
3141 self.constraint_batches.push(ConstraintBatch::default());
3142 }
3143
3144 self.constraint_batches[color]
3146 .distance_indices
3147 .push(constraint_idx);
3148
3149 Self::mark_color(&mut body_colors, &self.batch_static_bodies, body_a, color);
3151 Self::mark_color(&mut body_colors, &self.batch_static_bodies, body_b, color);
3152 }
3153
3154 for (constraint_idx, constraint) in self.contact_constraints.iter().enumerate() {
3156 let body_a = constraint.body_a;
3157 let body_b = constraint.body_b;
3158
3159 let color =
3160 Self::find_free_color(&body_colors, &self.batch_static_bodies, body_a, body_b);
3161
3162 while self.constraint_batches.len() <= color {
3163 self.constraint_batches.push(ConstraintBatch::default());
3164 }
3165
3166 self.constraint_batches[color]
3167 .contact_indices
3168 .push(constraint_idx);
3169
3170 Self::mark_color(&mut body_colors, &self.batch_static_bodies, body_a, color);
3171 Self::mark_color(&mut body_colors, &self.batch_static_bodies, body_b, color);
3172 }
3173
3174 self.batches_dirty = false;
3175
3176 debug_assert!(
3177 self.batches_are_body_disjoint(),
3178 "graph coloring produced a batch with two constraints sharing a dynamic body"
3179 );
3180 }
3181
3182 #[inline(always)]
3185 fn color_word(
3186 body_colors: &[Vec<u64>],
3187 static_bodies: &[bool],
3188 idx: usize,
3189 word: usize,
3190 ) -> u64 {
3191 if static_bodies.get(idx).copied().unwrap_or(false) {
3192 return 0;
3193 }
3194 body_colors
3195 .get(idx)
3196 .and_then(|words| words.get(word))
3197 .copied()
3198 .unwrap_or(0)
3199 }
3200
3201 fn find_free_color(
3209 body_colors: &[Vec<u64>],
3210 static_bodies: &[bool],
3211 body_a: usize,
3212 body_b: usize,
3213 ) -> usize {
3214 let words_a = body_colors.get(body_a).map_or(0, Vec::len);
3215 let words_b = body_colors.get(body_b).map_or(0, Vec::len);
3216 let words = words_a.max(words_b);
3217
3218 for word in 0..words {
3219 let occupied = Self::color_word(body_colors, static_bodies, body_a, word)
3220 | Self::color_word(body_colors, static_bodies, body_b, word);
3221 if occupied != u64::MAX {
3222 return word * 64 + (!occupied).trailing_zeros() as usize;
3223 }
3224 }
3225
3226 words * 64
3227 }
3228
3229 #[inline(always)]
3234 fn mark_color(body_colors: &mut [Vec<u64>], static_bodies: &[bool], idx: usize, color: usize) {
3235 if static_bodies.get(idx).copied().unwrap_or(false) {
3236 return;
3237 }
3238 if let Some(words) = body_colors.get_mut(idx) {
3239 let word = color / 64;
3240 if words.len() <= word {
3241 words.resize(word + 1, 0);
3242 }
3243 words[word] |= 1u64 << (color % 64);
3244 }
3245 }
3246
3247 pub(crate) fn batches_are_body_disjoint(&self) -> bool {
3254 let num_bodies = self.bodies.len();
3255 let mut last_batch: Vec<usize> = vec![usize::MAX; num_bodies];
3257
3258 for (batch_idx, batch) in self.constraint_batches.iter().enumerate() {
3259 let dist_bodies = batch.distance_indices.iter().map(|&i| {
3260 (
3261 self.distance_constraints[i].body_a,
3262 self.distance_constraints[i].body_b,
3263 )
3264 });
3265 let contact_bodies = batch.contact_indices.iter().map(|&i| {
3266 (
3267 self.contact_constraints[i].body_a,
3268 self.contact_constraints[i].body_b,
3269 )
3270 });
3271
3272 for (a, b) in dist_bodies.chain(contact_bodies) {
3273 for (k, idx) in [a, b].into_iter().enumerate() {
3274 if (k == 1 && a == b) || idx >= num_bodies || self.batch_static_bodies[idx] {
3276 continue;
3277 }
3278 if last_batch[idx] == batch_idx {
3279 return false;
3280 }
3281 last_batch[idx] = batch_idx;
3282 }
3283 }
3284 }
3285 true
3286 }
3287
3288 #[must_use]
3290 pub fn num_batches(&self) -> usize {
3291 self.constraint_batches.len()
3292 }
3293
3294 pub fn step(&mut self, dt: Fix128) {
3325 let _ = self.try_step(dt);
3326 }
3327
3328 fn make_body_rotations_unit(&mut self) {
3338 for body in &mut self.bodies {
3339 body.make_rotations_unit();
3340 }
3341 for collider in &mut self.sdf_colliders {
3342 collider.update_cache();
3343 }
3344 }
3345
3346 fn run_step(&mut self, dt: Fix128) {
3348 self.make_body_rotations_unit();
3349 self.wake_on_sdf_shape_change();
3350 self.stage_work = StageWork::default();
3351 self.begin_sdf_contact_log();
3352 let mut frozen = self.participant_flags();
3353
3354 #[cfg(feature = "std")]
3360 if matches!(self.config.solver_backend, SolverBackend::Tgs) {
3361 self.step_tgs(dt, &mut frozen);
3362 return;
3363 }
3364
3365 self.events.begin_frame();
3368
3369 self.islands.resize(self.bodies.len());
3371 self.islands.reset_unions();
3372 for j in &self.joints {
3373 let (a, b) = j.bodies();
3374 if a < self.bodies.len() && b < self.bodies.len() {
3375 self.islands.union(a, b);
3376 }
3377 }
3378
3379 let substep_dt = dt / Fix128::from_int(self.config.substeps as i64);
3380
3381 self.park_begin(dt, substep_dt);
3384
3385 if !self.force_fields.is_empty() {
3387 if self.park.active {
3388 for k in 0..self.park.awake.len() {
3389 let i = self.park.awake[k];
3390 let body = &mut self.bodies[i];
3391 if body.is_static() {
3392 continue;
3393 }
3394 self.stage_work.force_field_bodies += 1;
3395 body.velocity =
3396 crate::force::force_field_velocity(&self.force_fields, i, body, dt);
3397 }
3398 } else {
3399 self.stage_work.force_field_bodies += self.bodies.len() as u64;
3400 apply_force_fields(&self.force_fields, &mut self.bodies, dt);
3401 }
3402 }
3403
3404 let n = self.config.substeps;
3411 for i in 0..n {
3412 self.kinematic_substeps_left = n - i;
3413 self.park.first_substep = i == 0;
3414 self.sdf_contact_substep = i;
3415 let overflow_at_start = self.participants_begin_substep(i, n, substep_dt, &mut frozen);
3418 self.substep(substep_dt);
3419 self.participants_end_substep(overflow_at_start, &frozen);
3420 }
3421 self.kinematic_substeps_left = 0;
3422 self.sdf_contact_substep = 0;
3423 self.park.first_substep = false;
3424
3425 self.apply_frame_damping();
3427
3428 self.update_sleep_states();
3430 self.park.active = false;
3431
3432 self.sync_sdf_colliders();
3436
3437 self.events.end_frame();
3439 }
3440
3441 pub fn step_n(&mut self, n: usize, dt: Fix128) {
3465 for _ in 0..n {
3466 self.step(dt);
3467 }
3468 }
3469
3470 fn update_sleep_states(&mut self) {
3474 if !self.park.active {
3475 self.stage_work.sleep_evaluated += self.bodies.len() as u64;
3476 self.islands.update_sleep(&self.bodies);
3477 return;
3478 }
3479 for k in 0..self.park.awake.len() {
3480 let i = self.park.awake[k];
3481 self.stage_work.sleep_evaluated += 1;
3482 self.islands.update_sleep_body(i, &self.bodies[i]);
3483 }
3484 for k in 0..self.park.parked_list.len() {
3485 let i = self.park.parked_list[k];
3486 if !self.park.parked[i] {
3487 continue; }
3489 self.stage_work.parked_sleep_updates += 1;
3490 self.islands
3491 .update_sleep_idle(i, self.bodies[i].is_static());
3492 }
3493 }
3494
3495 fn park_begin(&mut self, dt: Fix128, substep_dt: Fix128) {
3507 self.park.active = false;
3508 let n = self.bodies.len();
3509 if !self.sleep_skip || self.broadphase != Broadphase::Bvh || n == 0 {
3510 if !self.park.proxies.is_empty() || self.park.key.is_some() {
3511 self.park.clear_counted(&mut self.stage_work);
3512 }
3513 return;
3514 }
3515 let cfg = self.islands.config;
3518 if !(Vec3Fix::ZERO.length() < cfg.linear_threshold) {
3519 self.park.clear_counted(&mut self.stage_work);
3520 return;
3521 }
3522 if self.sdf_colliders.iter().any(|c| c.body_index < n) {
3526 self.park.clear_counted(&mut self.stage_work);
3527 return;
3528 }
3529
3530 let key = ParkKey {
3531 substep_dt,
3532 sleep: cfg,
3533 generation: self.park_generation,
3534 sdf_radius: self.sdf_collision_radius,
3535 sdf: self
3536 .sdf_colliders
3537 .iter()
3538 .map(|c| {
3539 (
3540 c.position,
3541 c.rotation,
3542 c.scale,
3543 c.body_index,
3544 core::ptr::from_ref::<dyn crate::sdf_collider::SdfField>(c.field.as_ref())
3545 .cast::<u8>() as usize,
3546 )
3547 })
3548 .collect(),
3549 };
3550 if self.park.key.as_ref() != Some(&key) || n < self.park.parked.len() {
3552 self.park.clear_counted(&mut self.stage_work);
3553 self.park.key = Some(key);
3554 }
3555
3556 self.park.parked.resize(n, false);
3557 self.park.cache.resize(n, None);
3558 self.park.proxies.resize(n, None);
3559 self.park.constrained.clear();
3560 self.park.constrained.resize(n, false);
3561 for j in &self.joints {
3562 let (a, b) = j.bodies();
3563 for x in [a, b] {
3564 if x < n {
3565 self.park.constrained[x] = true;
3566 }
3567 }
3568 }
3569 for c in &self.distance_constraints {
3570 for x in [c.body_a, c.body_b] {
3571 if x < n {
3572 self.park.constrained[x] = true;
3573 }
3574 }
3575 }
3576
3577 self.park.frame_dt = dt;
3578 self.park.awake.clear();
3579 self.park.parked_list.clear();
3580 let inv_dt = Fix128::ONE / substep_dt;
3581 for i in 0..n {
3582 self.stage_work.sleep_scanned += 1;
3583 let (ok, recached) = self.park_check(i, inv_dt);
3584 self.park.parked[i] = ok;
3585 let radius = self.body_collision_radii.get(i).and_then(|r| *r);
3586 match (ok, radius) {
3587 (true, Some(r)) => {
3588 self.park.parked_list.push(i);
3589 if recached || self.park.proxies[i].is_none() {
3590 self.park.remove_proxy(i, &mut self.stage_work);
3591 let aabb = self.broadphase_box(i, r);
3592 self.park.proxies[i] = Some(self.park.tree.insert(aabb, i as u32));
3593 self.park.proxy_live += 1;
3594 self.stage_work.tree_inserts += 1;
3595 }
3596 }
3597 (true, None) => {
3598 self.park.parked_list.push(i);
3599 self.park.remove_proxy(i, &mut self.stage_work);
3600 }
3601 (false, _) => {
3602 self.park.awake.push(i);
3603 self.park.remove_proxy(i, &mut self.stage_work);
3604 }
3605 }
3606 }
3607 self.stage_work.parked = self.park.parked_list.len() as u64;
3608 self.park.active = true;
3609 }
3610
3611 fn park_check(&mut self, i: usize, inv_dt: Fix128) -> (bool, bool) {
3613 if !self.islands.is_sleeping(i) || self.park.constrained[i] {
3614 return (false, false);
3615 }
3616 let body = &self.bodies[i];
3617 let radius = self.body_collision_radii.get(i).and_then(|r| *r);
3618 match body.body_type {
3619 BodyType::Kinematic => (false, false),
3620 BodyType::Static => {
3621 if !body.is_static() {
3622 return (false, false);
3623 }
3624 let has_collider = self.body_colliders.get(i).is_some_and(Option::is_some);
3626 let c = ParkCache {
3627 position: body.position,
3628 rotation: body.rotation,
3629 angular_velocity: Vec3Fix::ZERO,
3630 radius,
3631 static_kind: true,
3632 };
3633 match self.park.cache[i] {
3634 Some(old)
3635 if old.static_kind
3636 && old.position == c.position
3637 && old.radius == radius
3638 && (old.rotation == c.rotation || !has_collider) =>
3639 {
3640 (true, false)
3641 }
3642 _ => {
3643 self.park.cache[i] = Some(c);
3644 (true, true)
3645 }
3646 }
3647 }
3648 BodyType::Dynamic => {
3649 if body.prev_position != body.position
3650 || body.prev_rotation != body.rotation
3651 || body.velocity != Vec3Fix::ZERO
3652 {
3653 return (false, false);
3654 }
3655 if let Some(c) = self.park.cache[i] {
3656 if !c.static_kind
3657 && c.position == body.position
3658 && c.rotation == body.rotation
3659 && c.radius == radius
3660 {
3661 return (body.angular_velocity == c.angular_velocity, false);
3662 }
3663 }
3664 let omega = angular_from_rotations(body.rotation, body.rotation, inv_dt);
3665 if body.angular_velocity != omega
3666 || !(omega.length() < self.islands.config.angular_threshold)
3667 || !self.resolution_clear(i)
3668 {
3669 self.park.cache[i] = None;
3670 return (false, false);
3671 }
3672 self.park.cache[i] = Some(ParkCache {
3673 position: body.position,
3674 rotation: body.rotation,
3675 angular_velocity: omega,
3676 radius,
3677 static_kind: false,
3678 });
3679 (true, true)
3680 }
3681 }
3682 }
3683
3684 fn resolution_clear(&self, i: usize) -> bool {
3688 let body = &self.bodies[i];
3689 if body.is_static() || body.is_sensor {
3690 return true; }
3692 let radius = self
3693 .body_collision_radii
3694 .get(i)
3695 .and_then(|r| *r)
3696 .unwrap_or(self.sdf_collision_radius);
3697 for collider in &self.static_colliders {
3698 if collider.collide_sphere(body.position, radius).is_some() {
3699 return false;
3700 }
3701 }
3702 #[cfg(feature = "std")]
3703 {
3704 let collider = self.body_colliders.get(i).and_then(Option::as_ref);
3705 for sdf in &self.sdf_colliders {
3706 if sdf.body_index == i {
3708 continue;
3709 }
3710 if sdf_contact_of(collider, body, self.sdf_collision_radius, sdf).is_some() {
3711 return false;
3712 }
3713 }
3714 }
3715 true
3716 }
3717
3718 #[inline]
3721 fn stage_count(&self) -> usize {
3722 if self.park.active {
3723 self.park.awake.len()
3724 } else {
3725 self.bodies.len()
3726 }
3727 }
3728
3729 #[inline]
3731 fn stage_body(&self, k: usize) -> usize {
3732 if self.park.active {
3733 self.park.awake[k]
3734 } else {
3735 k
3736 }
3737 }
3738
3739 #[cfg(feature = "parallel")]
3741 fn unparked_count(&self) -> u64 {
3742 self.stage_count() as u64
3743 }
3744
3745 #[cfg(feature = "std")]
3756 fn advance_kinematic_targets_for_tgs(&mut self, dt: Fix128) {
3757 for body in &mut self.bodies {
3758 if body.body_type != BodyType::Kinematic {
3759 continue;
3760 }
3761 body.prev_position = body.position;
3762 body.prev_rotation = body.rotation;
3763 if let Some((target_pos, target_rot)) = body.kinematic_target {
3764 body.velocity = (target_pos - body.position) * (Fix128::ONE / dt);
3765 body.position = target_pos;
3766 body.rotation = target_rot;
3767 }
3768 }
3769 }
3770 #[cfg(feature = "std")]
3774 fn tgs_static_corrections(&mut self) {
3775 if !self.static_colliders.is_empty() {
3776 self.resolve_static_collisions(true);
3777 }
3778 }
3779
3780 #[cfg(feature = "std")]
3783 fn jointed_bodies(&self) -> Vec<usize> {
3784 let n = self.bodies.len();
3785 let mut out: Vec<usize> = self
3786 .joints
3787 .iter()
3788 .flat_map(|j| {
3789 let (a, b) = j.bodies();
3790 [a, b]
3791 })
3792 .filter(|&i| i < n)
3793 .collect();
3794 out.sort_unstable();
3795 out.dedup();
3796 out
3797 }
3798
3799 #[cfg(feature = "std")]
3808 fn tgs_project_joints(
3809 &mut self,
3810 tgs_bodies: &mut [crate::solver_tgs_hooks_6dof_oriented::Body6DofOrientedState],
3811 jointed: &[usize],
3812 sub_dt: Fix128,
3813 ) {
3814 use crate::solver_tgs_backend::tgs_to_body;
3815 for &i in jointed {
3816 tgs_to_body(&tgs_bodies[i], &mut self.bodies[i]);
3817 }
3818 let before: Vec<(Vec3Fix, QuatFix)> = jointed
3819 .iter()
3820 .map(|&i| (self.bodies[i].position, self.bodies[i].rotation))
3821 .collect();
3822 self.solve_joints_dispatch(sub_dt);
3823 let inv_dt = Fix128::ONE / sub_dt;
3824 let mut overflow = false;
3825 for (&i, (p0, q0)) in jointed.iter().zip(before) {
3826 let body = &mut self.bodies[i];
3827 if body.is_dynamic() {
3828 if body.position != p0 {
3829 match (body.position - p0).checked_scale(inv_dt) {
3830 Some(dv) => body.velocity = body.velocity + dv,
3831 None => overflow = true,
3832 }
3833 }
3834 if body.rotation != q0 {
3835 body.angular_velocity =
3836 body.angular_velocity + angular_from_rotations(body.rotation, q0, inv_dt);
3837 }
3838 }
3839 let state = &mut tgs_bodies[i];
3840 state.position = [body.position.x, body.position.y, body.position.z];
3841 state.orientation = body.rotation;
3842 state.linear_velocity = [body.velocity.x, body.velocity.y, body.velocity.z];
3843 state.angular_velocity = [
3844 body.angular_velocity.x,
3845 body.angular_velocity.y,
3846 body.angular_velocity.z,
3847 ];
3848 }
3849 if overflow {
3850 self.note_rigid_overflow();
3851 }
3852 }
3853
3854 #[cfg(feature = "std")]
3867 fn step_tgs(&mut self, dt: Fix128, frozen: &mut [bool]) {
3868 use crate::solver_tgs::{build_islands, DistanceRef};
3869 use crate::solver_tgs_backend::{body_to_tgs, contact_to_tgs, tgs_to_body};
3870 use crate::solver_tgs_hooks_6dof_oriented::Pgs6DofOrientedConfig;
3871 use crate::solver_tgs_hooks_6dof_oriented_scoped::solve_oriented_islands_serial;
3872
3873 self.events.begin_frame();
3876
3877 self.islands.resize(self.bodies.len());
3882 self.islands.reset_unions();
3883 for j in &self.joints {
3884 let (a, b) = j.bodies();
3885 if a < self.bodies.len() && b < self.bodies.len() {
3886 self.islands.union(a, b);
3887 }
3888 }
3889
3890 if !self.force_fields.is_empty() {
3893 apply_force_fields(&self.force_fields, &mut self.bodies, dt);
3894 }
3895 self.apply_joint_motors(dt);
3898 self.advance_kinematic_targets_for_tgs(dt);
3899
3900 self.clear_contacts();
3903 self.detect_collisions();
3904
3905 #[cfg(feature = "std")]
3916 if !self.sdf_colliders.is_empty() {
3917 self.resolve_sdf_collisions();
3918 }
3919
3920 self.apply_contact_filters();
3926
3927 let n = self.bodies.len();
3930 let mut tgs_bodies: Vec<_> = self
3931 .bodies
3932 .iter()
3933 .enumerate()
3934 .map(|(i, b)| body_to_tgs(b, i as u64))
3935 .collect();
3936 self.sync_body_stable_ids();
3940 let ids = &self.body_stable_ids;
3941 let id_of = |i: usize| ids.get(i).copied().unwrap_or(u64::MAX);
3944 let mut contact_ordinals = std::collections::BTreeMap::new();
3945 let discarded = &self.contact_discarded;
3953 let mut tgs_contacts: Vec<_> = self
3954 .contact_constraints
3955 .iter()
3956 .enumerate()
3957 .filter_map(|(i, c)| {
3958 let (ia, ib) = (id_of(c.body_a), id_of(c.body_b));
3964 let flipped;
3965 let c = if ia > ib {
3966 flipped = ContactConstraint {
3967 body_a: c.body_b,
3968 body_b: c.body_a,
3969 contact: Contact {
3970 depth: c.contact.depth,
3971 normal: -c.contact.normal,
3972 point_a: c.contact.point_b,
3973 point_b: c.contact.point_a,
3974 },
3975 ..*c
3976 };
3977 &flipped
3978 } else {
3979 c
3980 };
3981 let key = tgs_cache_key(
3982 TGS_KEY_CONTACT,
3983 ia.min(ib),
3984 ia.max(ib),
3985 &mut contact_ordinals,
3986 );
3987 if discarded.get(i).copied().unwrap_or(false) {
3988 return None;
3989 }
3990 Some(contact_to_tgs(c, &self.bodies, key))
3991 })
3992 .collect();
3993 let joint_refs: Vec<DistanceRef<'_>> = self
3994 .distance_constraints
3995 .iter()
3996 .map(|joint| DistanceRef { joint })
3997 .collect();
3998 let mut joint_ordinals = std::collections::BTreeMap::new();
4001 let mut tgs_joints: Vec<crate::solver_tgs_hooks_6dof_oriented::JointOriented> = self
4002 .distance_constraints
4003 .iter()
4004 .map(|j| crate::solver_tgs_hooks_6dof_oriented::JointOriented {
4005 body_a: j.body_a,
4006 body_b: j.body_b,
4007 stable_id: tgs_cache_key(
4008 TGS_KEY_DISTANCE,
4009 id_of(j.body_a),
4010 id_of(j.body_b),
4011 &mut joint_ordinals,
4012 ),
4013 local_anchor_a: [j.local_anchor_a.x, j.local_anchor_a.y, j.local_anchor_a.z],
4014 local_anchor_b: [j.local_anchor_b.x, j.local_anchor_b.y, j.local_anchor_b.z],
4015 target_distance: j.target_distance,
4016 accum: Fix128::ZERO,
4017 })
4018 .collect();
4019
4020 let islands = build_islands(&tgs_bodies, &tgs_contacts, &joint_refs).unwrap_or_default();
4027
4028 let cfg = Pgs6DofOrientedConfig {
4029 gravity: [
4030 self.config.gravity.x,
4031 self.config.gravity.y,
4032 self.config.gravity.z,
4033 ],
4034 ..Pgs6DofOrientedConfig::default()
4035 };
4036 let tgs_cfg = crate::solver_tgs::TgsConfig {
4037 substeps: self.config.substeps.max(1) as u32,
4038 velocity_iters: self.config.iterations.max(1) as u32,
4039 ..crate::solver_tgs::TgsConfig::default()
4040 };
4041 let with_participants = !frozen.is_empty();
4046 if self.joints.is_empty() && !with_participants {
4047 solve_oriented_islands_serial(
4048 &mut tgs_bodies,
4049 &mut tgs_contacts,
4050 &mut tgs_joints,
4051 &islands,
4052 &mut self.tgs_impulse_cache,
4053 cfg,
4054 &tgs_cfg,
4055 dt,
4056 );
4057 } else {
4058 let one_substep = crate::solver_tgs::TgsConfig {
4066 substeps: 1,
4067 ..tgs_cfg
4068 };
4069 let substeps = self.config.substeps;
4076 let sub_dt = dt * Fix128::from_f32(1.0 / tgs_cfg.substeps as f32);
4077 let jointed = self.jointed_bodies();
4078 for s in 0..tgs_cfg.substeps {
4079 let overflow_at_start = if with_participants {
4080 for (body, state) in self.bodies.iter_mut().zip(tgs_bodies.iter()) {
4084 tgs_to_body(state, body);
4085 }
4086 let flag =
4087 self.participants_begin_substep(s as usize, substeps, sub_dt, frozen);
4088 for (state, body) in tgs_bodies.iter_mut().zip(self.bodies.iter()) {
4089 state.linear_velocity = [body.velocity.x, body.velocity.y, body.velocity.z];
4090 state.angular_velocity = [
4091 body.angular_velocity.x,
4092 body.angular_velocity.y,
4093 body.angular_velocity.z,
4094 ];
4095 }
4096 flag
4097 } else {
4098 false
4099 };
4100 solve_oriented_islands_serial(
4101 &mut tgs_bodies,
4102 &mut tgs_contacts,
4103 &mut tgs_joints,
4104 &islands,
4105 &mut self.tgs_impulse_cache,
4106 cfg,
4107 &one_substep,
4108 sub_dt,
4109 );
4110 if !self.joints.is_empty() {
4111 self.tgs_project_joints(&mut tgs_bodies, &jointed, sub_dt);
4112 }
4113 if with_participants {
4114 for (body, state) in self.bodies.iter_mut().zip(tgs_bodies.iter()) {
4120 tgs_to_body(state, body);
4121 }
4122 if tgs_bodies.iter().any(|s| s.overflow) {
4124 self.note_rigid_overflow();
4125 }
4126 self.participants_end_substep(overflow_at_start, frozen);
4127 }
4128 }
4129 }
4130 self.tgs_impulse_cache.sweep();
4139 debug_assert_eq!(tgs_bodies.len(), n);
4140 for (body, state) in self.bodies.iter_mut().zip(tgs_bodies.iter()) {
4141 tgs_to_body(state, body);
4142 }
4143 if tgs_bodies.iter().any(|s| s.overflow) {
4147 self.note_rigid_overflow();
4148 }
4149
4150 self.tgs_static_corrections();
4157
4158 self.apply_frame_damping();
4162
4163 self.islands.update_sleep(&self.bodies);
4165
4166 self.sync_sdf_colliders();
4170
4171 self.events.end_frame();
4173 }
4174
4175 #[cfg(feature = "std")]
4183 #[must_use]
4184 pub fn tgs_cache_stats(&self) -> TgsCacheStats {
4185 self.tgs_impulse_cache.stats().into()
4186 }
4187
4188 #[cfg(feature = "std")]
4194 pub fn reset_tgs_cache_stats(&mut self) {
4195 self.tgs_impulse_cache.reset_stats();
4196 }
4197
4198 #[cfg(feature = "parallel")]
4226 pub fn step_parallel(&mut self, dt: Fix128) {
4227 let _ = self.try_step_parallel(dt);
4228 }
4229
4230 #[cfg(feature = "parallel")]
4233 fn run_step_parallel(&mut self, dt: Fix128) {
4234 self.make_body_rotations_unit();
4235 self.wake_on_sdf_shape_change();
4236 self.begin_sdf_contact_log();
4237 let mut frozen = self.participant_flags();
4238
4239 self.events.begin_frame();
4242
4243 self.islands.resize(self.bodies.len());
4245 self.islands.reset_unions();
4246 for j in &self.joints {
4247 let (a, b) = j.bodies();
4248 if a < self.bodies.len() && b < self.bodies.len() {
4249 self.islands.union(a, b);
4250 }
4251 }
4252
4253 if !self.force_fields.is_empty() {
4255 apply_force_fields(&self.force_fields, &mut self.bodies, dt);
4256 }
4257
4258 let substep_dt = dt / Fix128::from_int(self.config.substeps as i64);
4263 let n = self.config.substeps;
4264 for i in 0..n {
4265 self.kinematic_substeps_left = n - i;
4266 self.sdf_contact_substep = i;
4267 let overflow_at_start = self.participants_begin_substep(i, n, substep_dt, &mut frozen);
4268 self.substep_batched(substep_dt);
4269 self.participants_end_substep(overflow_at_start, &frozen);
4270 }
4271 self.kinematic_substeps_left = 0;
4272 self.sdf_contact_substep = 0;
4273
4274 self.apply_frame_damping();
4276
4277 self.islands.update_sleep(&self.bodies);
4279
4280 self.sync_sdf_colliders();
4284
4285 self.events.end_frame();
4287 }
4288
4289 fn substep(&mut self, dt: Fix128) {
4291 self.apply_joint_motors(dt);
4293 self.integrate_positions(dt);
4294 self.reset_lambdas();
4295
4296 let ccd_hits = if self.ccd.is_enabled() {
4299 self.ccd_sweep()
4300 } else {
4301 Vec::new()
4302 };
4303
4304 self.clear_contacts();
4307 self.detect_collisions();
4308 if !ccd_hits.is_empty() {
4309 self.ccd_add_contacts(&ccd_hits);
4310 }
4311
4312 #[cfg(feature = "std")]
4314 if !self.sdf_colliders.is_empty() {
4315 self.resolve_sdf_collisions();
4316 }
4317 if !self.static_colliders.is_empty() {
4318 self.resolve_static_collisions(false);
4319 }
4320
4321 #[cfg(feature = "std")]
4324 self.apply_contact_filters();
4325 for _ in 0..self.config.iterations {
4326 self.solve_distance_constraints(dt);
4327 self.solve_contact_constraints(dt);
4328 }
4329
4330 self.solve_joints_dispatch(dt);
4334
4335 self.update_velocities(dt);
4336 }
4337
4338 #[cfg(feature = "parallel")]
4340 fn substep_batched(&mut self, dt: Fix128) {
4341 self.apply_joint_motors(dt);
4342 self.integrate_positions(dt);
4343 self.reset_lambdas();
4344
4345 let ccd_hits = if self.ccd.is_enabled() {
4347 self.ccd_sweep()
4348 } else {
4349 Vec::new()
4350 };
4351
4352 self.clear_contacts();
4354 self.detect_collisions();
4355 if !ccd_hits.is_empty() {
4356 self.ccd_add_contacts(&ccd_hits);
4357 }
4358 self.rebuild_batches();
4359
4360 #[cfg(feature = "std")]
4362 if !self.sdf_colliders.is_empty() {
4363 self.resolve_sdf_collisions();
4364 }
4365 if !self.static_colliders.is_empty() {
4366 self.resolve_static_collisions(false);
4367 }
4368
4369 #[cfg(feature = "std")]
4372 self.apply_contact_filters();
4373 for _ in 0..self.config.iterations {
4374 self.solve_constraints_batched(dt);
4375 }
4376
4377 self.solve_joints_dispatch(dt);
4381
4382 self.update_velocities(dt);
4383 }
4384
4385 #[inline]
4403 fn integrate_positions(&mut self, dt: Fix128) {
4404 let overflow = core::sync::atomic::AtomicBool::new(false);
4408 let mut free = core::mem::take(&mut self.free_rotation);
4412 free.clear();
4413 free.resize(self.bodies.len(), None);
4414 let mut predicted = core::mem::take(&mut self.predicted_pose);
4415 predicted.clear();
4416 predicted.resize(self.bodies.len(), None);
4417 #[cfg(feature = "parallel")]
4418 {
4419 let gravity = self.config.gravity;
4420 let left = self.kinematic_substeps_left;
4421 let sleep_data = &self.islands.sleep_data;
4422 let parked: &[bool] = if self.park.active {
4423 &self.park.parked
4424 } else {
4425 &[]
4426 };
4427 self.stage_work.integrated += self.unparked_count();
4428 self.bodies
4429 .par_iter_mut()
4430 .zip(free.par_iter_mut())
4431 .zip(predicted.par_iter_mut())
4432 .enumerate()
4433 .for_each(|(i, ((body, free), predicted))| {
4434 if parked.get(i).copied().unwrap_or(false) {
4435 return;
4436 }
4437 match body.body_type {
4438 BodyType::Static => return,
4439 BodyType::Kinematic => {
4440 body.prev_position = body.position;
4441 body.prev_rotation = body.rotation;
4442 if let Some((target_pos, target_rot)) = body.kinematic_target {
4443 let (p, r) = kinematic_substep_pose(
4444 body.position,
4445 body.rotation,
4446 target_pos,
4447 target_rot,
4448 left,
4449 );
4450 body.velocity = (p - body.position) * (Fix128::ONE / dt);
4451 body.position = p;
4452 body.rotation = r;
4453 }
4454 return;
4455 }
4456 BodyType::Dynamic => {}
4457 }
4458
4459 if sleep_data
4461 .get(i)
4462 .is_some_and(super::sleeping::SleepData::is_sleeping)
4463 {
4464 body.prev_position = body.position;
4465 body.prev_rotation = body.rotation;
4466 return;
4467 }
4468
4469 body.prev_position = body.position;
4471 body.prev_rotation = body.rotation;
4472
4473 body.velocity = body.velocity + gravity * body.gravity_scale * dt;
4475
4476 match body
4486 .velocity
4487 .checked_scale(dt)
4488 .and_then(|d| body.position.checked_add(d))
4489 {
4490 Some(p) => body.position = p,
4491 None => overflow.store(true, core::sync::atomic::Ordering::Relaxed),
4492 }
4493
4494 let mut spin_overflow = false;
4496 *free = predict_rotation(body, dt, &mut spin_overflow);
4497 if spin_overflow {
4498 overflow.store(true, core::sync::atomic::Ordering::Relaxed);
4499 }
4500
4501 *predicted = Some((body.position, body.rotation));
4504 });
4505 self.free_rotation = free;
4506 self.predicted_pose = predicted;
4507 }
4508
4509 #[cfg(not(feature = "parallel"))]
4510 {
4511 let count = self.stage_count();
4512 for k in 0..count {
4513 let i = self.stage_body(k);
4514 self.stage_work.integrated += 1;
4515 match self.bodies[i].body_type {
4516 BodyType::Static => continue,
4517 BodyType::Kinematic => {
4518 self.bodies[i].prev_position = self.bodies[i].position;
4519 self.bodies[i].prev_rotation = self.bodies[i].rotation;
4520 if let Some((target_pos, target_rot)) = self.bodies[i].kinematic_target {
4521 let (p, r) = kinematic_substep_pose(
4522 self.bodies[i].position,
4523 self.bodies[i].rotation,
4524 target_pos,
4525 target_rot,
4526 self.kinematic_substeps_left,
4527 );
4528 self.bodies[i].velocity =
4529 (p - self.bodies[i].position) * (Fix128::ONE / dt);
4530 self.bodies[i].position = p;
4531 self.bodies[i].rotation = r;
4532 }
4533 continue;
4534 }
4535 BodyType::Dynamic => {}
4536 }
4537
4538 if self.islands.is_sleeping(i) {
4540 self.bodies[i].prev_position = self.bodies[i].position;
4541 self.bodies[i].prev_rotation = self.bodies[i].rotation;
4542 continue;
4543 }
4544
4545 self.bodies[i].prev_position = self.bodies[i].position;
4547 self.bodies[i].prev_rotation = self.bodies[i].rotation;
4548
4549 let grav = self.config.gravity * self.bodies[i].gravity_scale * dt;
4551 self.bodies[i].velocity = self.bodies[i].velocity + grav;
4552
4553 let body = &mut self.bodies[i];
4562 match body
4563 .velocity
4564 .checked_scale(dt)
4565 .and_then(|d| body.position.checked_add(d))
4566 {
4567 Some(p) => body.position = p,
4568 None => overflow.store(true, core::sync::atomic::Ordering::Relaxed),
4569 }
4570
4571 let mut spin_overflow = false;
4573 free[i] = predict_rotation(&mut self.bodies[i], dt, &mut spin_overflow);
4574 if spin_overflow {
4575 overflow.store(true, core::sync::atomic::Ordering::Relaxed);
4576 }
4577
4578 predicted[i] = Some((self.bodies[i].position, self.bodies[i].rotation));
4581 }
4582 self.free_rotation = free;
4583 self.predicted_pose = predicted;
4584 }
4585
4586 if overflow.load(core::sync::atomic::Ordering::Relaxed) {
4588 self.note_rigid_overflow();
4589 }
4590 }
4591
4592 fn reset_lambdas(&mut self) {
4607 for c in &mut self.distance_constraints {
4608 c.cached_lambda = Fix128::ZERO;
4609 }
4610 }
4611
4612 fn apply_frame_damping(&mut self) {
4624 let damping = self.config.damping;
4625 let count = self.stage_count();
4626 for k in 0..count {
4627 let i = self.stage_body(k);
4628 self.stage_work.damping_bodies += 1;
4629 if self.bodies[i].body_type != BodyType::Dynamic || self.islands.is_sleeping(i) {
4630 continue;
4631 }
4632 let body = &mut self.bodies[i];
4633 body.velocity = body.velocity * damping * body.linear_damping;
4634 body.angular_velocity = body.angular_velocity * damping * body.angular_damping;
4635 }
4636 }
4637
4638 #[inline]
4641 fn update_velocities(&mut self, dt: Fix128) {
4642 let inv_dt = Fix128::ONE / dt;
4643 let overflow = core::sync::atomic::AtomicBool::new(false);
4647
4648 self.contact_pre_vn.clear();
4653 for constraint in &self.contact_constraints {
4654 let relative =
4655 self.bodies[constraint.body_a].velocity - self.bodies[constraint.body_b].velocity;
4656 self.contact_pre_vn
4657 .push(relative.dot(constraint.contact.normal));
4658 }
4659
4660 #[cfg(feature = "parallel")]
4662 {
4663 let parked: &[bool] = if self.park.active {
4664 &self.park.parked
4665 } else {
4666 &[]
4667 };
4668 self.stage_work.velocity_bodies += self.unparked_count();
4669 let free: &[Option<crate::gyroscopic::FreeRotation>] = &self.free_rotation;
4670 let predicted: &[Option<(Vec3Fix, QuatFix)>] = &self.predicted_pose;
4671 self.bodies
4672 .par_iter_mut()
4673 .enumerate()
4674 .for_each(|(i, body)| {
4675 if body.body_type == BodyType::Static || parked.get(i).copied().unwrap_or(false)
4676 {
4677 return;
4678 }
4679 let pose = predicted.get(i).copied().flatten();
4682 if !derive_linear_velocity(body, pose.map(|(x, _)| x), inv_dt) {
4683 overflow.store(true, core::sync::atomic::Ordering::Relaxed);
4684 }
4685 let f = free.get(i).copied().flatten();
4690 body.angular_velocity =
4691 derived_angular_velocity(body, f, pose.map(|(_, q)| q), inv_dt);
4692 });
4693 }
4694
4695 #[cfg(not(feature = "parallel"))]
4696 {
4697 let count = self.stage_count();
4698 for k in 0..count {
4699 let i = self.stage_body(k);
4700 self.stage_work.velocity_bodies += 1;
4701 let body = &mut self.bodies[i];
4702 if body.body_type == BodyType::Static {
4703 continue;
4704 }
4705 let pose = self.predicted_pose.get(i).copied().flatten();
4708 if !derive_linear_velocity(body, pose.map(|(x, _)| x), inv_dt) {
4709 overflow.store(true, core::sync::atomic::Ordering::Relaxed);
4710 }
4711 let f = self.free_rotation.get(i).copied().flatten();
4714 let body = &mut self.bodies[i];
4715 body.angular_velocity =
4716 derived_angular_velocity(body, f, pose.map(|(_, q)| q), inv_dt);
4717 }
4718 }
4719
4720 let restitution_threshold = self.config.gravity.length() * dt * Fix128::from_int(2);
4726 let num_contacts = self.contact_constraints.len();
4727 for i in 0..num_contacts {
4728 let constraint = self.contact_constraints[i];
4729 let body_a = self.bodies[constraint.body_a];
4730 let body_b = self.bodies[constraint.body_b];
4731
4732 if body_a.is_sensor || body_b.is_sensor {
4733 continue;
4734 }
4735
4736 #[cfg(feature = "std")]
4741 if self.contact_discarded.get(i).copied().unwrap_or(false) {
4742 continue;
4743 }
4744
4745 let w_sum = body_a.inv_mass + body_b.inv_mass;
4746 if w_sum < W_SUM_EPSILON {
4747 continue;
4748 }
4749
4750 let n = constraint.contact.normal;
4751 let relative_vel = body_a.velocity - body_b.velocity;
4752 let vn = relative_vel.dot(n);
4753
4754 let pre_vn = self.contact_pre_vn.get(i).copied().unwrap_or(vn);
4763 let restitution = if pre_vn < -restitution_threshold {
4764 constraint.restitution
4765 } else {
4766 Fix128::ZERO
4767 };
4768 let target = -restitution * pre_vn;
4769 let target = if target.is_negative() {
4770 Fix128::ZERO
4771 } else {
4772 target
4773 };
4774 if vn < target {
4775 let delta_vn = target - vn;
4776 let impulse_n = n * delta_vn;
4777 let inv_w = Fix128::ONE / w_sum;
4778 self.bodies[constraint.body_a].velocity =
4779 self.bodies[constraint.body_a].velocity + impulse_n * (body_a.inv_mass * inv_w);
4780 self.bodies[constraint.body_b].velocity =
4781 self.bodies[constraint.body_b].velocity - impulse_n * (body_b.inv_mass * inv_w);
4782 }
4783
4784 let relative_vel2 =
4786 self.bodies[constraint.body_a].velocity - self.bodies[constraint.body_b].velocity;
4787 let vn2 = relative_vel2.dot(n);
4788 let tangent_vel = relative_vel2 - n * vn2;
4789 let tangent_speed_sq = tangent_vel.length_squared();
4790 if tangent_speed_sq > Fix128::ZERO {
4791 let tangent_speed = tangent_speed_sq.sqrt();
4792 let friction = constraint.friction;
4793 let max_friction_impulse = friction * constraint.cached_lambda * inv_dt;
4804 let applied = if tangent_speed < max_friction_impulse {
4805 tangent_speed
4806 } else {
4807 max_friction_impulse
4808 };
4809 let friction_dir = tangent_vel / tangent_speed;
4810 let friction_impulse = friction_dir * applied;
4811 let inv_w = Fix128::ONE / w_sum;
4812 self.bodies[constraint.body_a].velocity = self.bodies[constraint.body_a].velocity
4813 - friction_impulse * (body_a.inv_mass * inv_w);
4814 self.bodies[constraint.body_b].velocity = self.bodies[constraint.body_b].velocity
4815 + friction_impulse * (body_b.inv_mass * inv_w);
4816 }
4817 }
4818
4819 if overflow.load(core::sync::atomic::Ordering::Relaxed) {
4821 self.note_rigid_overflow();
4822 }
4823 }
4824
4825 #[cfg(feature = "std")]
4845 fn apply_contact_filters(&mut self) {
4846 self.contact_discarded.clear();
4847 if self.pre_solve_hooks.is_empty() && self.contact_modifiers.is_empty() {
4848 return;
4849 }
4850 let num = self.contact_constraints.len();
4851 self.contact_discarded.resize(num, false);
4852 for i in 0..num {
4853 let constraint = self.contact_constraints[i];
4854 if self.bodies[constraint.body_a].is_sensor || self.bodies[constraint.body_b].is_sensor
4855 {
4856 continue;
4857 }
4858
4859 let mut contact = constraint.contact;
4860 let mut friction = constraint.friction;
4861 let mut restitution = constraint.restitution;
4862
4863 let mut skip = false;
4864 for hook in &self.pre_solve_hooks {
4865 if !hook(constraint.body_a, constraint.body_b, &contact) {
4866 skip = true;
4867 break;
4868 }
4869 }
4870 if !skip {
4871 for modifier in &self.contact_modifiers {
4872 if !modifier.modify_contact(
4873 constraint.body_a,
4874 constraint.body_b,
4875 &mut contact,
4876 &mut friction,
4877 &mut restitution,
4878 ) {
4879 skip = true;
4880 break;
4881 }
4882 }
4883 }
4884 if skip {
4885 self.contact_discarded[i] = true;
4886 continue;
4887 }
4888
4889 let slot = &mut self.contact_constraints[i];
4890 slot.contact = contact;
4891 slot.friction = friction;
4892 slot.restitution = restitution;
4893 }
4894 }
4895
4896 #[cfg(feature = "parallel")]
4909 fn solve_constraints_batched(&mut self, dt: Fix128) {
4910 let snapshot_stale = self.batch_static_bodies.len() != self.bodies.len()
4916 || self
4917 .bodies
4918 .iter()
4919 .zip(&self.batch_static_bodies)
4920 .any(|(body, &was_static)| body.inv_mass.is_zero() != was_static);
4921 if snapshot_stale {
4922 self.batches_dirty = true;
4923 self.rebuild_batches();
4924 }
4925
4926 let num_batches = self.constraint_batches.len();
4927
4928 let bodies = BodySlicePtr {
4929 ptr: self.bodies.as_mut_ptr(),
4930 len: self.bodies.len(),
4931 };
4932 let dists = DistConstraintSlicePtr {
4933 ptr: self.distance_constraints.as_mut_ptr(),
4934 len: self.distance_constraints.len(),
4935 };
4936 let static_bodies: &[bool] = &self.batch_static_bodies;
4937 #[cfg(feature = "std")]
4940 let discarded: &[bool] = &self.contact_discarded;
4941 #[cfg(not(feature = "std"))]
4942 let discarded: &[bool] = &[];
4943
4944 for batch_idx in 0..num_batches {
4945 {
4947 let indices = &self.constraint_batches[batch_idx].distance_indices;
4948 indices.par_iter().for_each(|&idx| {
4949 unsafe {
4956 let constraint = dists.get_mut(idx);
4957 let body_a =
4958 bodies.borrow(constraint.body_a, static_bodies[constraint.body_a]);
4959 let body_b =
4960 bodies.borrow(constraint.body_b, static_bodies[constraint.body_b]);
4961 Self::solve_distance_pair(body_a, body_b, constraint, dt);
4962 }
4963 });
4964 }
4965
4966 {
4968 let contacts = ContactConstraintSlicePtr {
4969 ptr: self.contact_constraints.as_mut_ptr(),
4970 len: self.contact_constraints.len(),
4971 };
4972 let indices = &self.constraint_batches[batch_idx].contact_indices;
4973 indices.par_iter().for_each(|&idx| {
4974 if discarded.get(idx).copied().unwrap_or(false) {
4975 return;
4976 }
4977 unsafe {
4981 let constraint = contacts.get_mut(idx);
4982 let body_a =
4983 bodies.borrow(constraint.body_a, static_bodies[constraint.body_a]);
4984 let body_b =
4985 bodies.borrow(constraint.body_b, static_bodies[constraint.body_b]);
4986 Self::solve_contact_pair(body_a, body_b, constraint);
4987 }
4988 });
4989 }
4990 }
4991 }
4992
4993 #[cfg(feature = "parallel")]
4999 #[inline(always)]
5000 fn solve_distance_pair(
5001 mut body_a: BodyRef<'_>,
5002 mut body_b: BodyRef<'_>,
5003 constraint: &mut DistanceConstraint,
5004 dt: Fix128,
5005 ) {
5006 let (pos_a, rot_a, inv_mass_a) = {
5007 let a = body_a.get();
5008 (a.position, a.rotation, a.inv_mass)
5009 };
5010 let (pos_b, rot_b, inv_mass_b) = {
5011 let b = body_b.get();
5012 (b.position, b.rotation, b.inv_mass)
5013 };
5014
5015 let anchor_a = pos_a + rot_a.rotate_vec(constraint.local_anchor_a);
5016 let anchor_b = pos_b + rot_b.rotate_vec(constraint.local_anchor_b);
5017
5018 let delta = anchor_b - anchor_a;
5019 let (normal, distance) = delta.normalize_with_length();
5020
5021 if distance.is_zero() {
5022 return;
5023 }
5024
5025 let error = distance - constraint.target_distance;
5026
5027 let compliance_term = constraint.compliance / (dt * dt);
5028 let w_sum = inv_mass_a + inv_mass_b + compliance_term;
5029
5030 if w_sum < W_SUM_EPSILON {
5031 return;
5032 }
5033
5034 let inv_w_sum = Fix128::ONE / w_sum;
5042 let lambda = constraint.cached_lambda;
5043 let dlambda = (error - lambda * compliance_term) * inv_w_sum;
5044 constraint.cached_lambda = lambda + dlambda;
5045 let correction = normal * dlambda;
5046
5047 let delta_a = correction * inv_mass_a;
5051 let delta_b = correction * inv_mass_b;
5052 body_a.set_position(select_vec3(!inv_mass_a.is_zero(), pos_a + delta_a, pos_a));
5053 body_b.set_position(select_vec3(!inv_mass_b.is_zero(), pos_b - delta_b, pos_b));
5054 }
5055
5056 #[cfg(feature = "parallel")]
5062 #[inline(always)]
5063 fn solve_contact_pair(
5064 mut body_a: BodyRef<'_>,
5065 mut body_b: BodyRef<'_>,
5066 constraint: &mut ContactConstraint,
5067 ) {
5068 let (pos_a, prev_a, inv_mass_a, sensor_a) = {
5069 let a = body_a.get();
5070 (a.position, a.prev_position, a.inv_mass, a.is_sensor)
5071 };
5072 let (pos_b, prev_b, inv_mass_b, sensor_b) = {
5073 let b = body_b.get();
5074 (b.position, b.prev_position, b.inv_mass, b.is_sensor)
5075 };
5076
5077 if sensor_a || sensor_b {
5079 return;
5080 }
5081
5082 let contact = constraint.contact;
5083
5084 if contact.depth <= Fix128::ZERO {
5085 return;
5086 }
5087
5088 let w_sum = inv_mass_a + inv_mass_b;
5089 if w_sum < W_SUM_EPSILON {
5090 return;
5091 }
5092
5093 let inv_w_sum = Fix128::ONE / w_sum;
5101 let lambda = constraint.cached_lambda;
5102 let dlambda = contact.depth - lambda;
5103 let (mut pos_a, mut pos_b) = (pos_a, pos_b);
5104 if dlambda > Fix128::ZERO {
5105 constraint.cached_lambda = lambda + dlambda;
5106
5107 let correction = contact.normal * dlambda;
5108 let correction_a = correction * (inv_mass_a * inv_w_sum);
5109 let correction_b = correction * (inv_mass_b * inv_w_sum);
5110
5111 pos_a = select_vec3(!inv_mass_a.is_zero(), pos_a + correction_a, pos_a);
5113 pos_b = select_vec3(!inv_mass_b.is_zero(), pos_b - correction_b, pos_b);
5114 }
5115
5116 if let Some((fa, fb)) = static_friction_correction(
5119 pos_a - prev_a,
5120 pos_b - prev_b,
5121 contact.normal,
5122 inv_mass_a,
5123 inv_mass_b,
5124 inv_w_sum,
5125 constraint.friction,
5126 constraint.cached_lambda,
5127 ) {
5128 pos_a = select_vec3(!inv_mass_a.is_zero(), pos_a - fa, pos_a);
5129 pos_b = select_vec3(!inv_mass_b.is_zero(), pos_b + fb, pos_b);
5130 }
5131
5132 body_a.set_position(pos_a);
5134 body_b.set_position(pos_b);
5135 }
5136
5137 #[inline(always)]
5142 fn solve_distance_constraints(&mut self, dt: Fix128) {
5143 let num_constraints = self.distance_constraints.len();
5144 for i in 0..num_constraints {
5145 let constraint = self.distance_constraints[i];
5146 let body_a = self.bodies[constraint.body_a];
5147 let body_b = self.bodies[constraint.body_b];
5148
5149 let anchor_a = body_a.position + body_a.rotation.rotate_vec(constraint.local_anchor_a);
5151 let anchor_b = body_b.position + body_b.rotation.rotate_vec(constraint.local_anchor_b);
5152
5153 let delta = anchor_b - anchor_a;
5155 let (normal, distance) = delta.normalize_with_length();
5156
5157 if distance.is_zero() {
5158 continue;
5159 }
5160
5161 let error = distance - constraint.target_distance;
5162
5163 let compliance_term = constraint.compliance / (dt * dt);
5165 let w_sum = body_a.inv_mass + body_b.inv_mass + compliance_term;
5166
5167 if w_sum < W_SUM_EPSILON {
5168 continue;
5169 }
5170
5171 let inv_w_sum = Fix128::ONE / w_sum;
5174 let lambda = constraint.cached_lambda;
5175 let dlambda = (error - lambda * compliance_term) * inv_w_sum;
5176 self.distance_constraints[i].cached_lambda = lambda + dlambda;
5177 let correction = normal * dlambda;
5178
5179 let delta_a = correction * body_a.inv_mass;
5181 let delta_b = correction * body_b.inv_mass;
5182 self.bodies[constraint.body_a].position = select_vec3(
5183 !body_a.inv_mass.is_zero(),
5184 self.bodies[constraint.body_a].position + delta_a,
5185 self.bodies[constraint.body_a].position,
5186 );
5187 self.bodies[constraint.body_b].position = select_vec3(
5188 !body_b.inv_mass.is_zero(),
5189 self.bodies[constraint.body_b].position - delta_b,
5190 self.bodies[constraint.body_b].position,
5191 );
5192 }
5193 }
5194
5195 fn solve_contact_constraints(&mut self, _dt: Fix128) {
5221 #[cfg(feature = "gpu-solver-bridge")]
5222 if let Some(mut bridge) = self.gpu_solver_bridge.take() {
5223 self.solve_contact_pass_with_bridge(bridge.as_mut());
5224 self.gpu_solver_bridge = Some(bridge);
5225 return;
5226 }
5227 let num_constraints = self.contact_constraints.len();
5228 for i in 0..num_constraints {
5229 let constraint = self.contact_constraints[i];
5230 let body_a = self.bodies[constraint.body_a];
5231 let body_b = self.bodies[constraint.body_b];
5232
5233 if body_a.is_sensor || body_b.is_sensor {
5235 continue;
5236 }
5237
5238 #[cfg(feature = "std")]
5240 if self.contact_discarded.get(i).copied().unwrap_or(false) {
5241 continue;
5242 }
5243
5244 let contact = constraint.contact;
5247
5248 if contact.depth <= Fix128::ZERO {
5250 continue;
5251 }
5252
5253 let w_sum = body_a.inv_mass + body_b.inv_mass;
5254 if w_sum < W_SUM_EPSILON {
5255 continue;
5256 }
5257
5258 let inv_w_sum = Fix128::ONE / w_sum;
5260 let lambda = constraint.cached_lambda;
5261 let dlambda = contact.depth - lambda;
5262 if dlambda > Fix128::ZERO {
5263 self.contact_constraints[i].cached_lambda = lambda + dlambda;
5264
5265 let correction = contact.normal * dlambda;
5266 let correction_a = correction * (body_a.inv_mass * inv_w_sum);
5267 let correction_b = correction * (body_b.inv_mass * inv_w_sum);
5268
5269 self.bodies[constraint.body_a].position = select_vec3(
5271 !body_a.inv_mass.is_zero(),
5272 self.bodies[constraint.body_a].position + correction_a,
5273 self.bodies[constraint.body_a].position,
5274 );
5275 self.bodies[constraint.body_b].position = select_vec3(
5276 !body_b.inv_mass.is_zero(),
5277 self.bodies[constraint.body_b].position - correction_b,
5278 self.bodies[constraint.body_b].position,
5279 );
5280 }
5281
5282 self.apply_static_friction(i, inv_w_sum);
5285 }
5286 }
5287
5288 fn apply_static_friction(&mut self, i: usize, inv_w_sum: Fix128) {
5294 let constraint = self.contact_constraints[i];
5295 let a = self.bodies[constraint.body_a];
5296 let b = self.bodies[constraint.body_b];
5297 if let Some((fa, fb)) = static_friction_correction(
5298 a.position - a.prev_position,
5299 b.position - b.prev_position,
5300 constraint.contact.normal,
5301 a.inv_mass,
5302 b.inv_mass,
5303 inv_w_sum,
5304 constraint.friction,
5305 constraint.cached_lambda,
5306 ) {
5307 self.bodies[constraint.body_a].position =
5308 select_vec3(!a.inv_mass.is_zero(), a.position - fa, a.position);
5309 self.bodies[constraint.body_b].position =
5310 select_vec3(!b.inv_mass.is_zero(), b.position + fb, b.position);
5311 }
5312 }
5313
5314 #[cfg(feature = "gpu-solver-bridge")]
5364 pub fn solve_contact_constraints_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5365 &mut self,
5366 bridge: &mut B,
5367 ) {
5368 self.apply_contact_filters();
5369 self.solve_contact_pass_with_bridge(bridge);
5370 }
5371
5372 #[cfg(feature = "gpu-solver-bridge")]
5377 fn solve_contact_pass_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5378 &mut self,
5379 bridge: &mut B,
5380 ) {
5381 let num_constraints = self.contact_constraints.len();
5383 let mut filtered: Vec<ContactConstraint> = Vec::with_capacity(num_constraints);
5384 let mut mapping: Vec<usize> = Vec::with_capacity(num_constraints);
5385
5386 for i in 0..num_constraints {
5387 let constraint = self.contact_constraints[i];
5388 let body_a = self.bodies[constraint.body_a];
5389 let body_b = self.bodies[constraint.body_b];
5390
5391 if body_a.is_sensor || body_b.is_sensor {
5392 continue;
5393 }
5394 if self.contact_discarded.get(i).copied().unwrap_or(false) {
5395 continue;
5396 }
5397
5398 let contact = constraint.contact;
5399 let friction = constraint.friction;
5400 let restitution = constraint.restitution;
5401
5402 filtered.push(ContactConstraint {
5403 body_a: constraint.body_a,
5404 body_b: constraint.body_b,
5405 contact,
5406 friction,
5407 restitution,
5408 cached_lambda: constraint.cached_lambda,
5409 });
5410 mapping.push(i);
5411 }
5412
5413 if filtered.is_empty() {
5414 return;
5415 }
5416
5417 let mut positions: Vec<[Fix128; 3]> = Vec::with_capacity(self.bodies.len());
5419 let mut inv_masses: Vec<Fix128> = Vec::with_capacity(self.bodies.len());
5420 for body in &self.bodies {
5421 positions.push([body.position.x, body.position.y, body.position.z]);
5422 inv_masses.push(body.inv_mass);
5423 }
5424
5425 bridge.send_contact_constraints(&filtered);
5427 bridge.send_body_state(&positions, &inv_masses);
5428 bridge.dispatch_contact_solve_iteration(self.config.warm_start_factor);
5429
5430 let mut updated_constraints = filtered;
5431 let mut updated_positions = positions;
5432 bridge.recv_contact_constraints(&mut updated_constraints);
5433 bridge.recv_body_positions(&mut updated_positions);
5434
5435 for (filtered_idx, orig_slot) in mapping.iter().enumerate() {
5437 self.contact_constraints[*orig_slot].cached_lambda =
5438 updated_constraints[filtered_idx].cached_lambda;
5439 }
5440 for (i, pos) in updated_positions.iter().enumerate() {
5441 self.bodies[i].position = Vec3Fix::new(pos[0], pos[1], pos[2]);
5442 }
5443
5444 for &orig_slot in &mapping {
5448 let c = self.contact_constraints[orig_slot];
5449 let w_sum = self.bodies[c.body_a].inv_mass + self.bodies[c.body_b].inv_mass;
5450 if c.contact.depth <= Fix128::ZERO || w_sum < W_SUM_EPSILON {
5451 continue;
5452 }
5453 self.apply_static_friction(orig_slot, Fix128::ONE / w_sum);
5454 }
5455 }
5456
5457 #[cfg(feature = "gpu-solver-bridge")]
5478 pub fn step_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5479 &mut self,
5480 bridge: &mut B,
5481 dt: Fix128,
5482 ) {
5483 let h = Self::divided_substep_width(dt, self.config.substeps);
5489 if !matches!(self.check_step(dt, h), Ok(true)) {
5490 return;
5491 }
5492 self.make_body_rotations_unit();
5493 self.wake_on_sdf_shape_change();
5494 self.begin_sdf_contact_log();
5495 let mut frozen = self.participant_flags();
5496
5497 self.events.begin_frame();
5500
5501 self.islands.resize(self.bodies.len());
5503 self.islands.reset_unions();
5504 for j in &self.joints {
5505 let (a, b) = j.bodies();
5506 if a < self.bodies.len() && b < self.bodies.len() {
5507 self.islands.union(a, b);
5508 }
5509 }
5510
5511 if !self.force_fields.is_empty() {
5513 apply_force_fields(&self.force_fields, &mut self.bodies, dt);
5514 }
5515
5516 let substep_dt = dt / Fix128::from_int(self.config.substeps as i64);
5520 let n = self.config.substeps;
5521 for i in 0..n {
5522 self.kinematic_substeps_left = n - i;
5523 self.sdf_contact_substep = i;
5524 let overflow_at_start = self.participants_begin_substep(i, n, substep_dt, &mut frozen);
5525 self.substep_with_bridge(bridge, substep_dt);
5526 self.participants_end_substep(overflow_at_start, &frozen);
5527 }
5528 self.kinematic_substeps_left = 0;
5529 self.sdf_contact_substep = 0;
5530
5531 self.apply_frame_damping();
5533
5534 self.islands.update_sleep(&self.bodies);
5536
5537 self.sync_sdf_colliders();
5541
5542 self.events.end_frame();
5544 }
5545
5546 #[cfg(feature = "gpu-solver-bridge")]
5573 pub fn substep_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5574 &mut self,
5575 bridge: &mut B,
5576 dt: Fix128,
5577 ) {
5578 self.make_body_rotations_unit();
5581 self.apply_joint_motors(dt);
5582 self.integrate_positions(dt);
5583 self.reset_lambdas();
5584
5585 self.clear_contacts();
5587 self.detect_collisions();
5588
5589 if !self.sdf_colliders.is_empty() {
5591 self.resolve_sdf_collisions();
5592 }
5593 if !self.static_colliders.is_empty() {
5594 self.resolve_static_collisions(false);
5595 }
5596
5597 for iteration in 0..self.config.iterations {
5602 self.solve_distance_constraints(dt);
5603 if iteration == 0 {
5604 self.solve_contact_constraints_with_bridge(bridge);
5605 } else {
5606 self.solve_contact_pass_with_bridge(bridge);
5607 }
5608 }
5609
5610 let forces = if self.joints.is_empty() {
5614 None
5615 } else {
5616 self.bridge_joint_reaction_forces(dt)
5617 };
5618 self.solve_joints_with_bridge(bridge, dt);
5619 if let Some(forces) = forces {
5620 self.break_overloaded_joints(&forces);
5621 }
5622
5623 self.update_velocities(dt);
5624 }
5625
5626 #[cfg(feature = "gpu-solver-bridge")]
5660 pub fn solve_joints_with_bridge<B: crate::gpu_bridge::GpuSolverBridge + ?Sized>(
5661 &mut self,
5662 bridge: &mut B,
5663 dt: Fix128,
5664 ) {
5665 let joints: Vec<crate::joint::Joint> = self
5670 .joints
5671 .iter()
5672 .filter(|j| {
5673 let (a, b) = j.bodies();
5674 a != b
5675 })
5676 .copied()
5677 .collect();
5678 if joints.is_empty() {
5679 return;
5680 }
5681
5682 let mut positions: Vec<[Fix128; 3]> = Vec::with_capacity(self.bodies.len());
5684 let mut inv_masses: Vec<Fix128> = Vec::with_capacity(self.bodies.len());
5685 let mut rotations: Vec<[Fix128; 4]> = Vec::with_capacity(self.bodies.len());
5686 for body in &self.bodies {
5687 positions.push([body.position.x, body.position.y, body.position.z]);
5688 inv_masses.push(body.inv_mass);
5689 rotations.push([
5690 body.rotation.x,
5691 body.rotation.y,
5692 body.rotation.z,
5693 body.rotation.w,
5694 ]);
5695 }
5696
5697 bridge.send_joints(&joints);
5699 bridge.send_body_state(&positions, &inv_masses);
5700 bridge.send_body_rotations(&rotations);
5701 bridge.dispatch_joint_solve_iteration(dt);
5702
5703 let mut updated_positions = positions;
5704 bridge.recv_body_positions(&mut updated_positions);
5705
5706 for (i, pos) in updated_positions.iter().enumerate() {
5708 self.bodies[i].position = crate::math::Vec3Fix::new(pos[0], pos[1], pos[2]);
5709 }
5710 }
5711
5712 fn solve_joints_dispatch(&mut self, dt: Fix128) {
5718 if self.joints.is_empty() {
5719 return;
5720 }
5721 #[cfg(feature = "gpu-solver-bridge")]
5722 if let Some(mut bridge) = self.gpu_solver_bridge.take() {
5723 let forces = self.bridge_joint_reaction_forces(dt);
5724 self.solve_joints_with_bridge(bridge.as_mut(), dt);
5725 self.gpu_solver_bridge = Some(bridge);
5726 if let Some(forces) = forces {
5727 self.break_overloaded_joints(&forces);
5728 }
5729 return;
5730 }
5731 if self.joints.iter().any(|j| j.break_force().is_some()) {
5732 let forces =
5733 crate::joint::solve_joints_with_reaction_forces(&self.joints, &mut self.bodies, dt);
5734 self.break_overloaded_joints(&forces);
5735 } else {
5736 solve_joints(&self.joints, &mut self.bodies, dt);
5737 }
5738 }
5739
5740 #[cfg(feature = "gpu-solver-bridge")]
5747 fn bridge_joint_reaction_forces(&self, dt: Fix128) -> Option<Vec<Fix128>> {
5748 if !self.joints.iter().any(|j| j.break_force().is_some()) {
5749 return None;
5750 }
5751 let mut copy = self.bodies.clone();
5752 Some(crate::joint::solve_joints_with_reaction_forces(
5753 &self.joints,
5754 &mut copy,
5755 dt,
5756 ))
5757 }
5758
5759 fn break_overloaded_joints(&mut self, forces: &[Fix128]) {
5784 let overloaded: Vec<(usize, Fix128)> = self
5785 .joints
5786 .iter()
5787 .zip(forces)
5788 .enumerate()
5789 .filter(|(_, (joint, force))| joint.break_force().is_some_and(|max| **force > max))
5790 .map(|(i, (_, force))| (i, *force))
5791 .collect();
5792 for &(index, force) in overloaded.iter().rev() {
5793 if let Some(joint) = self.remove_joint(index) {
5794 self.events
5795 .report_joint_break(crate::event::JointBreakEvent {
5796 index,
5797 joint,
5798 force,
5799 });
5800 }
5801 }
5802 if !overloaded.is_empty() {
5803 self.islands.resize(self.bodies.len());
5804 self.islands.reset_unions();
5805 for j in &self.joints {
5806 let (a, b) = j.bodies();
5807 if a < self.bodies.len() && b < self.bodies.len() {
5808 self.islands.union(a, b);
5809 }
5810 }
5811 }
5812 }
5813
5814 pub fn add_sdf_collider(&mut self, mut collider: SdfCollider) -> usize {
5826 collider.update_cache();
5827 collider.sync_to_body(&self.bodies);
5828 let idx = self.sdf_colliders.len();
5829 self.sdf_colliders.push(collider);
5830 self.sync_sdf_generations();
5831 idx
5832 }
5833
5834 fn sync_sdf_generations(&mut self) {
5839 let n = self.sdf_colliders.len();
5840 self.sdf_generations.truncate(n);
5841 for c in &self.sdf_colliders[self.sdf_generations.len()..] {
5842 self.sdf_generations.push(c.field.generation());
5843 }
5844 }
5845
5846 fn wake_on_sdf_shape_change(&mut self) {
5854 self.sync_sdf_generations();
5855 let mut changed = false;
5856 for (seen, c) in self.sdf_generations.iter_mut().zip(&self.sdf_colliders) {
5857 let now = c.field.generation();
5858 if *seen != now {
5859 *seen = now;
5860 changed = true;
5861 }
5862 }
5863 if changed {
5864 for i in 0..self.bodies.len() {
5869 if self.islands.is_sleeping(i) {
5870 self.wake_body(i);
5871 }
5872 }
5873 }
5874 }
5875
5876 fn begin_sdf_contact_log(&mut self) {
5879 self.sdf_contact_log.clear();
5880 self.sdf_contact_substep = 0;
5881 }
5882
5883 #[must_use]
5908 pub fn last_step_sdf_contacts(&self) -> &[crate::sdf_collider::SdfContact] {
5909 &self.sdf_contact_log
5910 }
5911
5912 fn sync_sdf_colliders(&mut self) {
5916 crate::sdf_collider::sync_dynamic_sdf_colliders(&mut self.sdf_colliders, &self.bodies);
5917 }
5918
5919 pub fn remove_sdf_collider(&mut self, idx: usize) -> Option<SdfCollider> {
5921 if idx < self.sdf_colliders.len() {
5922 if idx < self.sdf_generations.len() {
5923 self.sdf_generations.remove(idx);
5924 }
5925 Some(self.sdf_colliders.remove(idx))
5926 } else {
5927 None
5928 }
5929 }
5930
5931 pub fn set_sdf_collision_radius(&mut self, radius: Fix128) {
5933 self.sdf_collision_radius = radius;
5934 }
5935
5936 #[cfg(feature = "std")]
5943 fn resolve_sdf_collisions(&mut self) {
5944 self.sync_sdf_colliders();
5947 let sdf_colliders = &self.sdf_colliders;
5948 let colliders = &self.body_colliders;
5949 let collision_radius = self.sdf_collision_radius;
5950 let substep = self.sdf_contact_substep;
5951
5952 let push_out =
5955 |idx: usize, body: &mut RigidBody, log: &mut Vec<crate::sdf_collider::SdfContact>| {
5956 if body.is_static() || body.is_sensor {
5957 return;
5958 }
5959 let collider = colliders.get(idx).and_then(Option::as_ref);
5960 for (collider_index, sdf) in sdf_colliders.iter().enumerate() {
5961 if sdf.body_index == idx {
5963 continue;
5964 }
5965 if let Some(contact) = sdf_contact_of(collider, body, collision_radius, sdf) {
5966 log.push(crate::sdf_collider::SdfContact {
5967 body_index: idx,
5968 collider_index,
5969 point: contact.point_b,
5970 normal: contact.normal,
5971 depth: contact.depth,
5972 approach_speed: -body.velocity.dot(contact.normal),
5973 substep,
5974 collider_position: sdf.position,
5975 collider_rotation: sdf.rotation,
5976 collider_scale: sdf.scale,
5977 });
5978 body.position = body.position + contact.normal * contact.depth;
5979 }
5980 }
5981 };
5982
5983 #[cfg(feature = "parallel")]
5984 {
5985 let parked: &[bool] = if self.park.active {
5986 &self.park.parked
5987 } else {
5988 &[]
5989 };
5990 self.stage_work.resolution_bodies += self.unparked_count();
5991 let scratch = &mut self.sdf_contact_scratch;
5992 if scratch.len() < self.bodies.len() {
5993 scratch.resize_with(self.bodies.len(), Vec::new);
5994 }
5995 self.bodies
5996 .par_iter_mut()
5997 .zip(scratch.par_iter_mut())
5998 .enumerate()
5999 .for_each(|(idx, (body, out))| {
6000 out.clear();
6001 if !parked.get(idx).copied().unwrap_or(false) {
6002 push_out(idx, body, out);
6003 }
6004 });
6005 let log = &mut self.sdf_contact_log;
6006 for out in &scratch[..self.bodies.len()] {
6007 log.extend_from_slice(out);
6008 }
6009 }
6010
6011 #[cfg(not(feature = "parallel"))]
6012 {
6013 let count = if self.park.active {
6014 self.park.awake.len()
6015 } else {
6016 self.bodies.len()
6017 };
6018 for k in 0..count {
6019 let idx = if self.park.active {
6020 self.park.awake[k]
6021 } else {
6022 k
6023 };
6024 self.stage_work.resolution_bodies += 1;
6025 push_out(idx, &mut self.bodies[idx], &mut self.sdf_contact_log);
6026 }
6027 }
6028 }
6029
6030 #[cfg(feature = "std")]
6040 #[must_use]
6041 pub fn sdf_contacts(&self) -> Vec<(usize, crate::collider::Contact)> {
6042 let mut contacts = Vec::new();
6043 for (idx, body) in self.bodies.iter().enumerate() {
6044 if body.is_static() || body.is_sensor {
6045 continue;
6046 }
6047 let collider = self.body_colliders.get(idx).and_then(Option::as_ref);
6048 for sdf in &self.sdf_colliders {
6049 if sdf.body_index == idx {
6050 continue;
6051 }
6052 if let Some(c) = sdf_contact_of(collider, body, self.sdf_collision_radius, sdf) {
6053 contacts.push((idx, c));
6054 }
6055 }
6056 }
6057 contacts
6058 }
6059
6060 #[must_use]
6073 pub fn contact_forces(&self, dt: Fix128) -> Vec<(Vec3Fix, Vec3Fix, Fix128)> {
6074 let h2 = self.substep_dt(dt) * self.substep_dt(dt);
6075 self.contact_constraints
6076 .iter()
6077 .filter_map(|c| self.contact_force(c, h2))
6078 .collect()
6079 }
6080
6081 fn substep_dt(&self, dt: Fix128) -> Fix128 {
6083 dt / Fix128::from_int(self.config.substeps.max(1) as i64)
6084 }
6085
6086 fn contact_force(
6088 &self,
6089 c: &ContactConstraint,
6090 h2: Fix128,
6091 ) -> Option<(Vec3Fix, Vec3Fix, Fix128)> {
6092 let w = self.bodies.get(c.body_a)?.inv_mass + self.bodies.get(c.body_b)?.inv_mass;
6093 if w < W_SUM_EPSILON || h2.is_zero() {
6094 return None;
6095 }
6096 Some((
6097 c.contact.point_a,
6098 c.contact.normal,
6099 c.cached_lambda / (w * h2),
6100 ))
6101 }
6102
6103 #[must_use]
6107 pub fn contact_arrows(&self, dt: Fix128) -> Vec<crate::contact_viz::ContactArrow> {
6108 crate::contact_viz::generate_contact_arrows(&self.contact_forces(dt))
6109 }
6110
6111 #[must_use]
6115 pub fn contact_friction_arrows(&self, dt: Fix128) -> Vec<crate::contact_viz::ContactArrow> {
6116 let h2 = self.substep_dt(dt) * self.substep_dt(dt);
6117 self.contact_constraints
6118 .iter()
6119 .filter_map(|c| Some((self.contact_force(c, h2)?, c.friction)))
6120 .flat_map(|(force, mu)| crate::contact_viz::generate_friction_arrows(&[force], mu))
6121 .collect()
6122 }
6123
6124 #[must_use]
6127 pub fn contact_friction_cones(&self, dt: Fix128) -> Vec<crate::contact_viz::FrictionCone> {
6128 let h2 = self.substep_dt(dt) * self.substep_dt(dt);
6129 self.contact_constraints
6130 .iter()
6131 .filter_map(|c| Some((self.contact_force(c, h2)?, c.friction)))
6132 .flat_map(|(force, mu)| crate::contact_viz::generate_friction_cones(&[force], mu))
6133 .collect()
6134 }
6135
6136 pub fn add_static_collider(
6139 &mut self,
6140 collider: crate::static_collider::StaticCollider,
6141 ) -> usize {
6142 let idx = self.static_colliders.len();
6143 self.park_generation = self.park_generation.wrapping_add(1);
6144 self.static_colliders.push(collider);
6145 idx
6146 }
6147
6148 pub fn remove_static_collider(
6151 &mut self,
6152 idx: usize,
6153 ) -> Option<crate::static_collider::StaticCollider> {
6154 if idx < self.static_colliders.len() {
6155 self.park_generation = self.park_generation.wrapping_add(1);
6156 Some(self.static_colliders.remove(idx))
6157 } else {
6158 None
6159 }
6160 }
6161
6162 #[must_use]
6164 pub fn static_collider_count(&self) -> usize {
6165 self.static_colliders.len()
6166 }
6167
6168 fn resolve_static_collisions(&mut self, remove_approach: bool) {
6181 let default_radius = self.sdf_collision_radius;
6182 let count = self.stage_count();
6183 for k in 0..count {
6184 let i = self.stage_body(k);
6185 self.stage_work.resolution_bodies += 1;
6186 let body = &mut self.bodies[i];
6187 if body.is_static() || body.is_sensor {
6188 continue;
6189 }
6190 let radius = self
6191 .body_collision_radii
6192 .get(i)
6193 .and_then(|r| *r)
6194 .unwrap_or(default_radius);
6195 for collider in &self.static_colliders {
6196 if let Some(contact) = collider.collide_sphere(body.position, radius) {
6197 body.position = body.position + contact.normal * contact.depth;
6198 if remove_approach {
6199 let vn = body.velocity.dot(contact.normal);
6200 if vn < Fix128::ZERO {
6201 body.velocity = body.velocity - contact.normal * vn;
6202 }
6203 }
6204 }
6205 }
6206 }
6207 }
6208
6209 pub fn set_broadphase(&mut self, kind: Broadphase) {
6214 self.park_generation = self.park_generation.wrapping_add(1);
6215 self.broadphase = kind;
6216 self.broadphase_reset();
6217 }
6218
6219 #[must_use]
6221 pub fn broadphase(&self) -> Broadphase {
6222 self.broadphase
6223 }
6224
6225 #[must_use]
6229 pub fn broadphase_stats(&self) -> BroadphaseStats {
6230 BroadphaseStats {
6231 proxies: self.broadphase_tree.proxy_count(),
6232 height: self.broadphase_tree.height(),
6233 }
6234 }
6235
6236 #[must_use]
6239 pub fn broadphase_proxy_aabb(&self, body_idx: usize) -> Option<AABB> {
6240 let proxy = (*self.broadphase_proxies.get(body_idx)?)?;
6241 if self.broadphase_tree.user_data(proxy) as usize != body_idx {
6242 return None;
6243 }
6244 Some(self.broadphase_tree.get_aabb(proxy))
6245 }
6246
6247 fn broadphase_reset(&mut self) {
6251 self.broadphase_tree = crate::dynamic_bvh::DynamicAabbTree::new();
6252 self.broadphase_proxies.clear();
6253 self.broadphase_hybrid = BroadphaseHybrid::new();
6254 }
6255
6256 fn broadphase_box(&self, i: usize, radius: Fix128) -> AABB {
6265 let pos = self.bodies[i].position;
6266 let cube = AABB::from_center_half(pos, Vec3Fix::new(radius, radius, radius));
6267 match self.body_colliders.get(i).and_then(Option::as_ref) {
6268 Some(collider) => {
6269 let tight = collider.world_aabb(pos, self.bodies[i].rotation);
6270 let max = |x: Fix128, y: Fix128| if x > y { x } else { y };
6271 let min = |x: Fix128, y: Fix128| if x < y { x } else { y };
6272 AABB {
6273 min: Vec3Fix::new(
6274 max(tight.min.x, cube.min.x),
6275 max(tight.min.y, cube.min.y),
6276 max(tight.min.z, cube.min.z),
6277 ),
6278 max: Vec3Fix::new(
6279 min(tight.max.x, cube.max.x),
6280 min(tight.max.y, cube.max.y),
6281 min(tight.max.z, cube.max.z),
6282 ),
6283 }
6284 }
6285 None => cube,
6286 }
6287 }
6288
6289 fn broadphase_pairs(&mut self, primitives: Vec<BvhPrimitive>) -> Vec<(u32, u32)> {
6292 match self.broadphase {
6293 Broadphase::Bvh => LinearBvh::build(primitives).find_pairs(),
6294 Broadphase::DynamicTree => {
6295 let n = self.bodies.len();
6296 for proxy in self
6301 .broadphase_proxies
6302 .drain(n.min(self.broadphase_proxies.len())..)
6303 .flatten()
6304 {
6305 self.broadphase_tree.remove(proxy);
6306 }
6307 self.broadphase_proxies.resize(n, None);
6308 let mut present = vec![false; n];
6309 for p in &primitives {
6310 let i = p.index as usize;
6311 present[i] = true;
6312 match self.broadphase_proxies[i] {
6313 Some(proxy) => {
6314 self.broadphase_tree.update(proxy, p.aabb);
6315 }
6316 None => {
6317 self.broadphase_proxies[i] =
6318 Some(self.broadphase_tree.insert(p.aabb, p.index));
6319 }
6320 }
6321 }
6322 for (i, here) in present.iter().enumerate() {
6324 if !here {
6325 if let Some(proxy) = self.broadphase_proxies[i].take() {
6326 self.broadphase_tree.remove(proxy);
6327 }
6328 }
6329 }
6330 self.broadphase_tree.find_pairs()
6331 }
6332 Broadphase::Hybrid => {
6333 let hybrid = &mut self.broadphase_hybrid;
6334 hybrid.clear_dynamic();
6335 for p in &primitives {
6336 let is_static = self.bodies[p.index as usize].is_static();
6337 hybrid.insert_dynamic(p.index, p.aabb, is_static);
6338 }
6339 hybrid.build_dynamic();
6340 let mut pairs = Vec::new();
6341 self.stage_work.broadphase_box_tests += hybrid.query_pairs(&mut pairs);
6342 pairs
6343 }
6344 }
6345 }
6346
6347 fn parked_broadphase_pairs(&mut self, primitives: Vec<BvhPrimitive>) -> Vec<(u32, u32)> {
6354 let mut pairs = Vec::new();
6355 for p in &primitives {
6356 let i = p.index;
6357 self.park.tree.query_callback(&p.aabb, |j| {
6358 pairs.push(if i < j { (i, j) } else { (j, i) });
6359 });
6360 }
6361 if primitives.len() >= 2 {
6362 pairs.extend(LinearBvh::build(primitives).find_pairs());
6363 }
6364 pairs.sort_unstable();
6365 pairs.dedup();
6366 pairs
6367 }
6368
6369 #[allow(clippy::too_many_lines, clippy::items_after_statements)]
6380 fn detect_collisions(&mut self) {
6381 let n = self.bodies.len();
6382 if n < 2 {
6383 return;
6384 }
6385
6386 let mut primitives = Vec::new();
6389 let count = self.stage_count();
6390 for k in 0..count {
6391 let i = self.stage_body(k);
6392 if let Some(radius) = self.body_collision_radii.get(i).and_then(|r| *r) {
6393 primitives.push(BvhPrimitive {
6394 aabb: self.broadphase_box(i, radius),
6395 index: i as u32,
6396 morton: 0,
6397 });
6398 }
6399 }
6400 self.stage_work.broadphase_primitives += primitives.len() as u64;
6401
6402 let parked_with_radius = if self.park.active {
6403 self.park.proxy_live
6404 } else {
6405 0
6406 };
6407 if primitives.len() + parked_with_radius < 2 {
6408 return;
6409 }
6410
6411 let pairs = if self.park.active {
6412 self.parked_broadphase_pairs(primitives)
6413 } else {
6414 self.broadphase_pairs(primitives)
6415 };
6416 self.stage_work.broadphase_pairs += pairs.len() as u64;
6417
6418 let velocity_of = |i: usize| -> Vec3Fix {
6422 let body = &self.bodies[i];
6423 if self.park.first_substep
6424 && !self.force_fields.is_empty()
6425 && self.park.is_parked(i)
6426 && !body.is_static()
6427 {
6428 crate::force::force_field_velocity(&self.force_fields, i, body, self.park.frame_dt)
6429 } else {
6430 body.velocity
6431 }
6432 };
6433
6434 struct ContactInfo {
6436 body_a: usize,
6437 body_b: usize,
6438 contact: Contact,
6439 normal: Vec3Fix,
6440 point: Vec3Fix,
6441 depth: Fix128,
6442 rel_vel: Fix128,
6443 is_sensor: bool,
6444 }
6445 let mut results: Vec<ContactInfo> = Vec::new();
6446 let mut radius_overflow = false;
6447
6448 for (a32, b32) in pairs {
6449 let a = a32 as usize;
6450 let b = b32 as usize;
6451 if a >= n || b >= n {
6452 continue;
6453 }
6454
6455 let radius_a = self
6457 .body_collision_radii
6458 .get(a)
6459 .and_then(|r| *r)
6460 .unwrap_or(Fix128::ZERO);
6461 let radius_b = self
6462 .body_collision_radii
6463 .get(b)
6464 .and_then(|r| *r)
6465 .unwrap_or(Fix128::ZERO);
6466
6467 let delta = self.bodies[a].position - self.bodies[b].position;
6475 let Some(combined_radius) = radius_a.checked_add(radius_b) else {
6478 radius_overflow = true;
6479 continue;
6480 };
6481 let mut scaled: Option<(Vec3Fix, Fix128)> = None;
6495 let collider_a = self.body_colliders.get(a).and_then(Option::as_ref);
6502 let collider_b = self.body_colliders.get(b).and_then(Option::as_ref);
6503 if collider_a.is_none() && collider_b.is_none() {
6504 let squares = delta
6505 .checked_length_squared()
6506 .zip(combined_radius.checked_mul(combined_radius));
6507 match squares {
6508 Some((d2, r2)) => {
6509 if d2 >= r2 || d2.is_zero() {
6510 continue;
6511 }
6512 }
6513 None => {
6514 let Some(dist) = delta.checked_length_scaled() else {
6517 continue;
6518 };
6519 if dist >= combined_radius || dist.is_zero() {
6520 continue;
6521 }
6522 let Some(normal) = delta.try_normalize_scaled() else {
6523 continue;
6524 };
6525 scaled = Some((normal, dist));
6526 }
6527 }
6528 } else if !self
6529 .broadphase_box(a, radius_a)
6530 .intersects(&self.broadphase_box(b, radius_b))
6531 {
6532 continue;
6533 }
6534
6535 let filter_a = self
6537 .body_filters
6538 .get(a)
6539 .copied()
6540 .unwrap_or(CollisionFilter::DEFAULT);
6541 let filter_b = self
6542 .body_filters
6543 .get(b)
6544 .copied()
6545 .unwrap_or(CollisionFilter::DEFAULT);
6546 if !CollisionFilter::can_collide(&filter_a, &filter_b) {
6547 continue;
6548 }
6549
6550 if self.bodies[a].is_static() && self.bodies[b].is_static() {
6552 continue;
6553 }
6554
6555 if self.islands.is_sleeping(a) && self.islands.is_sleeping(b) {
6557 continue;
6558 }
6559
6560 if collider_a.is_some() || collider_b.is_some() {
6563 let pose_a = (self.bodies[a].position, self.bodies[a].rotation);
6564 let pose_b = (self.bodies[b].position, self.bodies[b].rotation);
6565 let hit = match (collider_a, collider_b) {
6566 (Some(ca), Some(cb)) => {
6567 crate::body_collider::contact_between(ca, pose_a, cb, pose_b)
6568 }
6569 (Some(ca), None) => crate::body_collider::contact_with_sphere(
6570 ca,
6571 pose_a,
6572 crate::collider::Sphere::new(pose_b.0, radius_b),
6573 true,
6574 ),
6575 (None, Some(cb)) => crate::body_collider::contact_with_sphere(
6576 cb,
6577 pose_b,
6578 crate::collider::Sphere::new(pose_a.0, radius_a),
6579 false,
6580 ),
6581 (None, None) => None,
6582 };
6583 if let Some(contact) = hit {
6584 if contact.depth > Fix128::ZERO {
6585 let rel_vel = (velocity_of(a) - velocity_of(b)).dot(contact.normal);
6586 results.push(ContactInfo {
6587 body_a: a,
6588 body_b: b,
6589 contact,
6590 normal: contact.normal,
6591 point: contact.point_a,
6592 depth: contact.depth,
6593 rel_vel,
6594 is_sensor: self.bodies[a].is_sensor || self.bodies[b].is_sensor,
6595 });
6596 }
6597 }
6598 continue;
6599 }
6600
6601 let (normal, dist) = scaled.unwrap_or_else(|| delta.normalize_with_length());
6602
6603 if dist < combined_radius && !dist.is_zero() {
6604 let depth = combined_radius - dist;
6605 let point_a = self.bodies[a].position - normal * radius_a;
6606 let point_b = self.bodies[b].position + normal * radius_b;
6607 let rel_vel = (velocity_of(a) - velocity_of(b)).dot(normal);
6609 let is_sensor = self.bodies[a].is_sensor || self.bodies[b].is_sensor;
6610
6611 let contact = Contact {
6612 depth,
6613 normal,
6614 point_a,
6615 point_b,
6616 };
6617
6618 results.push(ContactInfo {
6619 body_a: a,
6620 body_b: b,
6621 contact,
6622 normal,
6623 point: point_a,
6624 depth,
6625 rel_vel,
6626 is_sensor,
6627 });
6628 }
6629 }
6630
6631 if radius_overflow {
6632 self.note_rigid_overflow();
6633 }
6634
6635 for info in results {
6637 self.events.report_contact(
6639 info.body_a,
6640 info.body_b,
6641 info.normal,
6642 info.point,
6643 info.depth,
6644 info.rel_vel,
6645 );
6646
6647 for x in [info.body_a, info.body_b] {
6653 if !self.islands.is_sleeping(x) {
6654 continue;
6655 }
6656 if self.park.is_parked(x) {
6657 self.islands.wake_body(x);
6661 self.park.unpark(x, &mut self.stage_work);
6662 } else {
6663 self.islands.wake_island(x);
6664 }
6665 }
6666
6667 if info.is_sensor {
6668 self.events.report_trigger(info.body_a, info.body_b);
6669 } else {
6670 self.add_contact_with_material(info.body_a, info.body_b, info.contact);
6671 }
6672 }
6673 }
6674
6675 #[inline]
6677 #[must_use]
6678 pub fn get_body(&self, idx: usize) -> Option<&RigidBody> {
6679 self.bodies.get(idx)
6680 }
6681
6682 #[inline]
6688 pub fn get_body_mut(&mut self, idx: usize) -> Option<&mut RigidBody> {
6689 self.bodies.get_mut(idx)
6690 }
6691
6692 #[must_use]
6715 pub const fn overflow_detected(&self) -> bool {
6716 self.overflow_detected
6717 }
6718
6719 #[inline]
6726 pub(crate) fn note_rigid_overflow(&mut self) {
6727 self.overflow_detected = true;
6728 }
6729
6730 pub const STATE_MAGIC: [u8; 4] = *b"APHY";
6735
6736 pub const STATE_VERSION: u16 = 3;
6748
6749 #[must_use]
6784 pub fn serialize_state(&self) -> Vec<u8> {
6785 let mut data = Vec::new();
6786
6787 data.extend_from_slice(&Self::STATE_MAGIC);
6789 data.extend_from_slice(&Self::STATE_VERSION.to_le_bytes());
6790 data.extend_from_slice(&0u16.to_le_bytes());
6791
6792 let count = self.bodies.len() as u32;
6794 data.extend_from_slice(&count.to_le_bytes());
6795
6796 for body in &self.bodies {
6798 data.extend_from_slice(&body.position.x.hi.to_le_bytes());
6800 data.extend_from_slice(&body.position.x.lo.to_le_bytes());
6801 data.extend_from_slice(&body.position.y.hi.to_le_bytes());
6802 data.extend_from_slice(&body.position.y.lo.to_le_bytes());
6803 data.extend_from_slice(&body.position.z.hi.to_le_bytes());
6804 data.extend_from_slice(&body.position.z.lo.to_le_bytes());
6805
6806 data.extend_from_slice(&body.velocity.x.hi.to_le_bytes());
6808 data.extend_from_slice(&body.velocity.x.lo.to_le_bytes());
6809 data.extend_from_slice(&body.velocity.y.hi.to_le_bytes());
6810 data.extend_from_slice(&body.velocity.y.lo.to_le_bytes());
6811 data.extend_from_slice(&body.velocity.z.hi.to_le_bytes());
6812 data.extend_from_slice(&body.velocity.z.lo.to_le_bytes());
6813
6814 data.extend_from_slice(&body.rotation.x.hi.to_le_bytes());
6816 data.extend_from_slice(&body.rotation.x.lo.to_le_bytes());
6817 data.extend_from_slice(&body.rotation.y.hi.to_le_bytes());
6818 data.extend_from_slice(&body.rotation.y.lo.to_le_bytes());
6819 data.extend_from_slice(&body.rotation.z.hi.to_le_bytes());
6820 data.extend_from_slice(&body.rotation.z.lo.to_le_bytes());
6821 data.extend_from_slice(&body.rotation.w.hi.to_le_bytes());
6822 data.extend_from_slice(&body.rotation.w.lo.to_le_bytes());
6823
6824 data.extend_from_slice(&body.angular_velocity.x.hi.to_le_bytes());
6826 data.extend_from_slice(&body.angular_velocity.x.lo.to_le_bytes());
6827 data.extend_from_slice(&body.angular_velocity.y.hi.to_le_bytes());
6828 data.extend_from_slice(&body.angular_velocity.y.lo.to_le_bytes());
6829 data.extend_from_slice(&body.angular_velocity.z.hi.to_le_bytes());
6830 data.extend_from_slice(&body.angular_velocity.z.lo.to_le_bytes());
6831 }
6832
6833 for i in 0..self.bodies.len() {
6839 let sd = self
6840 .islands
6841 .sleep_data
6842 .get(i)
6843 .copied()
6844 .unwrap_or_else(SleepData::new);
6845 data.push(match sd.state {
6846 SleepState::Awake => 0,
6847 SleepState::Sleeping => 1,
6848 });
6849 data.extend_from_slice(&sd.idle_frames.to_le_bytes());
6850 }
6851
6852 data.push(u8::from(self.overflow_detected));
6857
6858 data.extend_from_slice(&self.population_fingerprint().to_le_bytes());
6864
6865 data
6866 }
6867
6868 #[must_use]
6884 pub fn population_fingerprint(&self) -> u64 {
6885 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
6886 for i in 0..self.bodies.len() {
6887 let body = &self.bodies[i];
6888 fnv1a_fold(&mut hash, &body.inv_mass.hi.to_le_bytes());
6889 fnv1a_fold(&mut hash, &body.inv_mass.lo.to_le_bytes());
6890 fnv1a_fold(&mut hash, &body.inv_inertia.x.hi.to_le_bytes());
6891 fnv1a_fold(&mut hash, &body.inv_inertia.x.lo.to_le_bytes());
6892 fnv1a_fold(&mut hash, &body.inv_inertia.y.hi.to_le_bytes());
6893 fnv1a_fold(&mut hash, &body.inv_inertia.y.lo.to_le_bytes());
6894 fnv1a_fold(&mut hash, &body.inv_inertia.z.hi.to_le_bytes());
6895 fnv1a_fold(&mut hash, &body.inv_inertia.z.lo.to_le_bytes());
6896 fnv1a_fold(&mut hash, &[body.body_type as u8]);
6897 fnv1a_fold(&mut hash, &[u8::from(body.is_sensor)]);
6898 match self.body_collision_radii.get(i).copied().flatten() {
6899 Some(r) => {
6900 fnv1a_fold(&mut hash, &[1u8]);
6901 fnv1a_fold(&mut hash, &r.hi.to_le_bytes());
6902 fnv1a_fold(&mut hash, &r.lo.to_le_bytes());
6903 }
6904 None => fnv1a_fold(&mut hash, &[0u8]),
6905 }
6906 let filter = self.body_filter(i);
6907 fnv1a_fold(&mut hash, &filter.layer.to_le_bytes());
6908 fnv1a_fold(&mut hash, &filter.mask.to_le_bytes());
6909 fnv1a_fold(&mut hash, &filter.group.to_le_bytes());
6910 let material = self
6911 .body_materials
6912 .get(i)
6913 .copied()
6914 .unwrap_or(crate::material::DEFAULT_MATERIAL);
6915 fnv1a_fold(&mut hash, &material.to_le_bytes());
6916 }
6917 hash
6918 }
6919
6920 pub fn deserialize_state(&mut self, data: &[u8]) -> bool {
6944 if data.len() < 12 {
6946 return false;
6947 }
6948 if data[0..4] != Self::STATE_MAGIC {
6951 return false;
6952 }
6953 if u16::from_le_bytes([data[4], data[5]]) != Self::STATE_VERSION {
6954 return false;
6955 }
6956 if u16::from_le_bytes([data[6], data[7]]) != 0 {
6961 return false;
6962 }
6963
6964 let count = u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
6965
6966 if count != self.bodies.len() {
6967 return false;
6968 }
6969
6970 let expected = 12 + count * 208 + count * 5 + 1 + 8;
6972 if data.len() < expected {
6973 return false;
6974 }
6975
6976 let fp_offset = expected - 8;
6981 let stored_fingerprint = u64::from_le_bytes([
6982 data[fp_offset],
6983 data[fp_offset + 1],
6984 data[fp_offset + 2],
6985 data[fp_offset + 3],
6986 data[fp_offset + 4],
6987 data[fp_offset + 5],
6988 data[fp_offset + 6],
6989 data[fp_offset + 7],
6990 ]);
6991 if stored_fingerprint != self.population_fingerprint() {
6992 return false;
6993 }
6994
6995 let mut offset = 12;
6997 for body in &mut self.bodies {
6998 if offset + 208 > data.len() {
6999 return false;
7000 }
7001
7002 let read_fix128 = |o: &mut usize| -> Fix128 {
7004 let hi = i64::from_le_bytes([
7005 data[*o],
7006 data[*o + 1],
7007 data[*o + 2],
7008 data[*o + 3],
7009 data[*o + 4],
7010 data[*o + 5],
7011 data[*o + 6],
7012 data[*o + 7],
7013 ]);
7014 *o += 8;
7015 let lo = u64::from_le_bytes([
7016 data[*o],
7017 data[*o + 1],
7018 data[*o + 2],
7019 data[*o + 3],
7020 data[*o + 4],
7021 data[*o + 5],
7022 data[*o + 6],
7023 data[*o + 7],
7024 ]);
7025 *o += 8;
7026 Fix128 { hi, lo }
7027 };
7028
7029 body.position.x = read_fix128(&mut offset);
7030 body.position.y = read_fix128(&mut offset);
7031 body.position.z = read_fix128(&mut offset);
7032
7033 body.velocity.x = read_fix128(&mut offset);
7034 body.velocity.y = read_fix128(&mut offset);
7035 body.velocity.z = read_fix128(&mut offset);
7036
7037 body.rotation.x = read_fix128(&mut offset);
7038 body.rotation.y = read_fix128(&mut offset);
7039 body.rotation.z = read_fix128(&mut offset);
7040 body.rotation.w = read_fix128(&mut offset);
7041
7042 body.angular_velocity.x = read_fix128(&mut offset);
7043 body.angular_velocity.y = read_fix128(&mut offset);
7044 body.angular_velocity.z = read_fix128(&mut offset);
7045 }
7046
7047 let n = self.bodies.len();
7049 while self.body_collision_radii.len() < n {
7050 self.body_collision_radii.push(None);
7051 }
7052 self.body_collision_radii.truncate(n);
7053 self.body_colliders.resize(n, None);
7054 while self.body_filters.len() < n {
7055 self.body_filters.push(CollisionFilter::DEFAULT);
7056 }
7057 self.body_filters.truncate(n);
7058 while self.body_materials.len() < n {
7059 self.body_materials.push(crate::material::DEFAULT_MATERIAL);
7060 }
7061 self.body_materials.truncate(n);
7062 #[cfg(feature = "std")]
7063 self.sync_body_stable_ids();
7064 self.islands = IslandManager::new(n, self.islands.config);
7067
7068 for i in 0..n {
7070 let state = match data[offset] {
7071 0 => SleepState::Awake,
7072 1 => SleepState::Sleeping,
7073 _ => return false,
7076 };
7077 offset += 1;
7078 let idle_frames = u32::from_le_bytes([
7079 data[offset],
7080 data[offset + 1],
7081 data[offset + 2],
7082 data[offset + 3],
7083 ]);
7084 offset += 4;
7085 if let Some(sd) = self.islands.sleep_data.get_mut(i) {
7086 sd.state = state;
7087 sd.idle_frames = idle_frames;
7088 }
7089 }
7090
7091 self.overflow_detected = match data[offset] {
7097 0 => false,
7098 1 => true,
7099 _ => return false,
7101 };
7102
7103 true
7104 }
7105}
7106
7107impl core::fmt::Debug for PhysicsWorld {
7108 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
7109 f.debug_struct("PhysicsWorld")
7110 .field("config", &self.config)
7111 .field("bodies", &self.bodies.len())
7112 .field("distance_constraints", &self.distance_constraints.len())
7113 .field("contact_constraints", &self.contact_constraints.len())
7114 .field("sdf_colliders", &self.sdf_colliders.len())
7115 .field("static_colliders", &self.static_colliders.len())
7116 .field("joints", &self.joints.len())
7117 .field("force_fields", &self.force_fields.len())
7118 .finish_non_exhaustive()
7119 }
7120}
7121
7122#[cfg(all(test, feature = "std"))]
7123mod tests {
7124 use super::*;
7125
7126 #[test]
7133 fn checked_world_inv_inertia_apply_is_the_unchecked_one_in_range() {
7134 let torques = [
7135 Vec3Fix::from_int(3, -1, 2),
7136 Vec3Fix::new(
7137 Fix128::from_ratio(-5, 7),
7138 Fix128::from_ratio(11, 3),
7139 Fix128::from_int(-9),
7140 ),
7141 ];
7142 let rotations = [
7143 QuatFix::from_axis_angle(Vec3Fix::from_int(1, 2, 3), Fix128::from_ratio(7, 10)),
7144 QuatFix::from_axis_angle(Vec3Fix::from_int(-2, 1, 0), Fix128::from_ratio(-13, 5)),
7145 QuatFix::from_axis_angle(Vec3Fix::from_int(0, 0, 1), Fix128::from_ratio(1, 3)),
7146 ];
7147 for q in rotations {
7148 let mut body = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
7149 body.inv_inertia = Vec3Fix::from_int(1, 2, 4);
7150 body.rotation = q;
7151 for tau in torques {
7152 let unchecked = body.world_inv_inertia_apply(tau);
7153 assert_eq!(
7154 body.checked_world_inv_inertia_apply(tau),
7155 Some(unchecked),
7156 "q {q:?} tau {tau:?}"
7157 );
7158 let mut unrotated = body;
7159 unrotated.rotation = QuatFix::IDENTITY;
7160 assert_ne!(
7161 unrotated.world_inv_inertia_apply(tau),
7162 unchecked,
7163 "fixture: the rotation must matter"
7164 );
7165 let mut isotropic = body;
7166 isotropic.inv_inertia = Vec3Fix::from_int(2, 2, 2);
7167 assert_ne!(
7168 isotropic.world_inv_inertia_apply(tau),
7169 unchecked,
7170 "fixture: the anisotropy must matter"
7171 );
7172 }
7173 }
7174 }
7175
7176 #[test]
7180 fn tgs_cache_key_separates_ordinal_kind_and_pair() {
7181 let mut ord = std::collections::BTreeMap::new();
7182 let k0 = tgs_cache_key(TGS_KEY_CONTACT, 3, 7, &mut ord);
7183 let k1 = tgs_cache_key(TGS_KEY_CONTACT, 3, 7, &mut ord);
7184 let other = tgs_cache_key(TGS_KEY_CONTACT, 3, 8, &mut ord);
7185 assert_ne!(k0, k1, "two entries of one pair must not share a key");
7186 let mut again = std::collections::BTreeMap::new();
7187 assert_eq!(tgs_cache_key(TGS_KEY_CONTACT, 3, 7, &mut again), k0);
7188 assert_eq!(tgs_cache_key(TGS_KEY_CONTACT, 3, 7, &mut again), k1);
7189 let mut fresh = std::collections::BTreeMap::new();
7190 assert_ne!(tgs_cache_key(TGS_KEY_DISTANCE, 3, 7, &mut fresh), k0);
7191 let mut fresh = std::collections::BTreeMap::new();
7192 assert_ne!(tgs_cache_key(TGS_KEY_CONTACT, 7, 3, &mut fresh), k0);
7193 assert_ne!(other, k0);
7194 assert_eq!(ord.get(&(3, 7)), Some(&2));
7195 }
7196
7197 #[test]
7198 fn test_rigid_body_creation() {
7199 let body = RigidBody::new(Vec3Fix::ZERO, Fix128::ONE);
7200 assert!(!body.is_static());
7201
7202 let static_body = RigidBody::new_static(Vec3Fix::ZERO);
7203 assert!(static_body.is_static());
7204 }
7205
7206 #[test]
7207 fn test_gravity_integration() {
7208 let config = SolverConfig::default();
7209 let mut world = PhysicsWorld::new(config);
7210
7211 let body = RigidBody::new(Vec3Fix::from_int(0, 10, 0), Fix128::ONE);
7212 world.add_body(body);
7213
7214 for _ in 0..10 {
7216 world.step(Fix128::from_ratio(1, 10));
7217 }
7218
7219 let body = world.get_body(0).unwrap();
7221 assert!(
7222 body.position.y < Fix128::from_int(10),
7223 "Body should have fallen"
7224 );
7225 }
7226
7227 #[test]
7228 fn test_distance_constraint() {
7229 let config = SolverConfig {
7230 substeps: 4,
7231 iterations: 8,
7232 gravity: Vec3Fix::ZERO, ..Default::default()
7234 };
7235 let mut world = PhysicsWorld::new(config);
7236
7237 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
7239 let b = world.add_body(RigidBody::new(Vec3Fix::from_int(5, 0, 0), Fix128::ONE));
7240
7241 world.add_distance_constraint(DistanceConstraint::new(
7242 a,
7243 b,
7244 Vec3Fix::ZERO,
7245 Vec3Fix::ZERO,
7246 Fix128::from_int(3), ));
7248
7249 for _ in 0..100 {
7251 world.step(Fix128::from_ratio(1, 60));
7252 }
7253
7254 let body_b = world.get_body(b).unwrap();
7256 let distance = body_b.position.length();
7257
7258 assert!(
7260 distance < Fix128::from_int(5),
7261 "Constraint should pull body closer"
7262 );
7263 }
7264
7265 #[test]
7266 fn test_state_serialization() {
7267 let config = SolverConfig::default();
7268 let mut world = PhysicsWorld::new(config);
7269
7270 world.add_body(RigidBody::new(Vec3Fix::from_int(1, 2, 3), Fix128::ONE));
7271 world.add_body(RigidBody::new(
7272 Vec3Fix::from_int(4, 5, 6),
7273 Fix128::from_int(2),
7274 ));
7275
7276 let state = world.serialize_state();
7277
7278 let mut world2 = PhysicsWorld::new(config);
7283 world2.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE));
7284 world2.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::from_int(2)));
7285
7286 assert!(world2.deserialize_state(&state));
7287
7288 assert_eq!(world2.bodies[0].position.x.hi, 1);
7289 assert_eq!(world2.bodies[0].position.y.hi, 2);
7290 assert_eq!(world2.bodies[1].position.x.hi, 4);
7291 }
7292
7293 #[test]
7294 fn test_determinism() {
7295 let config = SolverConfig::default();
7297
7298 let run_simulation = || {
7299 let mut world = PhysicsWorld::new(config);
7300 world.add_body(RigidBody::new(Vec3Fix::from_int(0, 10, 0), Fix128::ONE));
7301 world.add_body(RigidBody::new(
7302 Vec3Fix::from_int(5, 10, 0),
7303 Fix128::from_int(2),
7304 ));
7305
7306 for _ in 0..100 {
7307 world.step(Fix128::from_ratio(1, 60));
7308 }
7309
7310 world.serialize_state()
7311 };
7312
7313 let state1 = run_simulation();
7314 let state2 = run_simulation();
7315
7316 assert_eq!(state1, state2, "Simulation must be deterministic");
7317 }
7318
7319 #[test]
7320 fn test_body_material_assignment() {
7321 let config = SolverConfig::default();
7322 let mut world = PhysicsWorld::new(config);
7323
7324 let metal_id = world.material_table.register_metal();
7325 let rubber_id = world.material_table.register_rubber();
7326
7327 let a = world.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE));
7328 let b = world.add_body(RigidBody::new(Vec3Fix::from_int(2, 0, 0), Fix128::ONE));
7329
7330 world.set_body_material(a, metal_id);
7331 world.set_body_material(b, rubber_id);
7332
7333 let combined = world.combined_material(a, b);
7334 assert!(
7335 combined.friction > Fix128::ZERO,
7336 "Combined friction should be positive"
7337 );
7338 }
7339
7340 #[test]
7341 fn test_contact_cache_integration() {
7342 let config = SolverConfig::default();
7343 let mut world = PhysicsWorld::new(config);
7344
7345 let a = world.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE));
7346 let b = world.add_body(RigidBody::new(Vec3Fix::from_int(1, 0, 0), Fix128::ONE));
7347
7348 world.begin_frame();
7349
7350 let contact = Contact {
7351 depth: Fix128::from_ratio(1, 10),
7352 normal: Vec3Fix::UNIT_X,
7353 point_a: Vec3Fix::from_int(1, 0, 0),
7354 point_b: Vec3Fix::from_int(1, 0, 0),
7355 };
7356 world.add_contact_with_material(a, b, contact);
7357
7358 world.end_frame();
7359
7360 let key = crate::contact_cache::BodyPairKey::new(a, b);
7362 let manifold = world.contact_cache.find(&key);
7363 assert!(
7364 manifold.is_some(),
7365 "Contact cache should contain the manifold"
7366 );
7367 }
7368
7369 #[test]
7370 fn test_kinematic_body() {
7371 let config = SolverConfig::default();
7372 let mut world = PhysicsWorld::new(config);
7373
7374 let kinematic = RigidBody::new_kinematic(Vec3Fix::ZERO);
7375 let k_id = world.add_body(kinematic);
7376
7377 world.bodies[k_id].set_kinematic_target(Vec3Fix::from_int(5, 0, 0), QuatFix::IDENTITY);
7379
7380 world.step(Fix128::from_ratio(1, 60));
7381
7382 let pos = world.bodies[k_id].position;
7384 assert_eq!(pos.x.hi, 5, "Kinematic body should move to target");
7385 }
7386
7387 #[test]
7388 fn test_body_type_enum() {
7389 let dynamic = RigidBody::new(Vec3Fix::ZERO, Fix128::ONE);
7390 assert!(dynamic.is_dynamic());
7391 assert!(!dynamic.is_kinematic());
7392
7393 let static_b = RigidBody::new_static(Vec3Fix::ZERO);
7394 assert!(static_b.is_static());
7395 assert!(!static_b.is_dynamic());
7396
7397 let kinematic = RigidBody::new_kinematic(Vec3Fix::ZERO);
7398 assert!(kinematic.is_kinematic());
7399 assert!(kinematic.is_static()); }
7401
7402 #[test]
7403 fn test_per_body_gravity_scale() {
7404 let config = SolverConfig::default();
7405 let mut world = PhysicsWorld::new(config);
7406
7407 let mut no_grav = RigidBody::new(Vec3Fix::from_int(0, 10, 0), Fix128::ONE);
7409 no_grav.gravity_scale = Fix128::ZERO;
7410 let ng_id = world.add_body(no_grav);
7411
7412 let normal = RigidBody::new(Vec3Fix::from_int(5, 10, 0), Fix128::ONE);
7414 let n_id = world.add_body(normal);
7415
7416 for _ in 0..60 {
7417 world.step(Fix128::from_ratio(1, 60));
7418 }
7419
7420 let ng_y = world.bodies[ng_id].position.y;
7422 assert!(
7423 ng_y > Fix128::from_int(9),
7424 "Zero-gravity body should stay near y=10, got {ng_y:?}"
7425 );
7426
7427 let n_y = world.bodies[n_id].position.y;
7429 assert!(
7430 n_y < Fix128::from_int(10),
7431 "Normal gravity body should fall"
7432 );
7433 }
7434
7435 #[test]
7436 fn test_sdf_ground_collision() {
7437 use crate::math::QuatFix;
7438 use crate::sdf_collider::{ClosureSdf, SdfCollider};
7439
7440 let config = SolverConfig::default();
7441 let mut world = PhysicsWorld::new(config);
7442 world.set_sdf_collision_radius(Fix128::from_ratio(1, 2)); let body = RigidBody::new(Vec3Fix::from_int(0, 5, 0), Fix128::ONE);
7446 let body_id = world.add_body(body);
7447
7448 let ground = ClosureSdf::new(|_x, y, _z| y, |_x, _y, _z| (0.0, 1.0, 0.0));
7450 world.add_sdf_collider(SdfCollider::new_static(
7451 Box::new(ground),
7452 Vec3Fix::ZERO,
7453 QuatFix::IDENTITY,
7454 ));
7455
7456 let dt = Fix128::from_ratio(1, 60);
7458 for _ in 0..120 {
7459 world.step(dt);
7460 }
7461
7462 let pos = world.bodies[body_id].position;
7464 let y = pos.y.to_f32();
7465
7466 assert!(y < 5.0, "Body should have fallen from y=5");
7468 assert!(
7469 y > -1.0,
7470 "Body should not have fallen through SDF ground, y={y}"
7471 );
7472 }
7473
7474 #[test]
7475 fn test_warm_start_factor_default() {
7476 let config = SolverConfig::default();
7477 let expected = Fix128::from_ratio(85, 100);
7479 assert_eq!(config.warm_start_factor, expected);
7480 }
7481
7482 #[test]
7483 fn test_warm_start_factor_convergence() {
7484 let make_world = |wsf: Fix128| {
7486 let config = SolverConfig {
7487 substeps: 4,
7488 iterations: 2, gravity: Vec3Fix::ZERO,
7490 warm_start_factor: wsf,
7491 ..Default::default()
7492 };
7493 let mut world = PhysicsWorld::new(config);
7494 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
7495 let b = world.add_body(RigidBody::new(Vec3Fix::from_int(5, 0, 0), Fix128::ONE));
7496 let constraint =
7497 DistanceConstraint::new(a, b, Vec3Fix::ZERO, Vec3Fix::ZERO, Fix128::from_int(3));
7498 world.add_distance_constraint(constraint);
7499 world
7500 };
7501
7502 let mut world_warm = make_world(Fix128::from_ratio(85, 100));
7503 let mut world_cold = make_world(Fix128::ZERO);
7504
7505 let dt = Fix128::from_ratio(1, 60);
7506 for _ in 0..30 {
7507 world_warm.step(dt);
7508 world_cold.step(dt);
7509 }
7510
7511 let dist_warm = (world_warm.bodies[1].position - world_warm.bodies[0].position)
7513 .length()
7514 .to_f32();
7515 let dist_cold = (world_cold.bodies[1].position - world_cold.bodies[0].position)
7516 .length()
7517 .to_f32();
7518
7519 let target = 3.0_f32;
7520 let err_warm = (dist_warm - target).abs();
7521 let err_cold = (dist_cold - target).abs();
7522 assert!(
7524 err_warm <= err_cold + 0.1,
7525 "Warm-start should converge at least as well: err_warm={err_warm}, err_cold={err_cold}"
7526 );
7527 }
7528
7529 #[test]
7530 fn test_contact_warm_start_cached_lambda() {
7531 let contact = Contact {
7533 depth: Fix128::from_ratio(1, 10),
7534 normal: Vec3Fix::UNIT_Y,
7535 point_a: Vec3Fix::ZERO,
7536 point_b: Vec3Fix::ZERO,
7537 };
7538 let cc = ContactConstraint::new(0, 1, contact);
7539 assert_eq!(cc.cached_lambda, Fix128::ZERO);
7540 }
7541
7542 #[test]
7543 fn test_manual_contact_pushes_body_out_by_depth() {
7544 let config = SolverConfig {
7547 substeps: 1,
7548 iterations: 2,
7549 gravity: Vec3Fix::ZERO,
7550 ..Default::default()
7551 };
7552 let mut world = PhysicsWorld::new(config);
7553 let _a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
7554 let _b = world.add_body(RigidBody::new(
7555 Vec3Fix::from_f32(0.0, 0.05, 0.0),
7556 Fix128::ONE,
7557 ));
7558
7559 let contact = Contact {
7563 depth: Fix128::from_ratio(5, 100), normal: -Vec3Fix::UNIT_Y, point_a: Vec3Fix::ZERO,
7566 point_b: Vec3Fix::from_f32(0.0, 0.05, 0.0),
7567 };
7568 world.add_contact(ContactConstraint::new(0, 1, contact));
7569
7570 let dt = Fix128::from_ratio(1, 60);
7571 for _ in 0..world.config.iterations {
7572 world.solve_contact_constraints(dt);
7573 }
7574
7575 let y = world.bodies[1].position.y.to_f32();
7577 assert!(
7578 (y - 0.10).abs() < 1e-5,
7579 "Body should be pushed up by depth once: y={y}"
7580 );
7581 }
7582
7583 #[cfg(feature = "gpu-solver-bridge")]
7597 struct RecordingBridge {
7598 send_contact_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7599 send_body_state_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7600 dispatch_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7601 send_joints_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7604 send_body_rotations_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7605 dispatch_joint_count: std::sync::Arc<core::sync::atomic::AtomicUsize>,
7606 }
7607
7608 #[cfg(feature = "gpu-solver-bridge")]
7609 impl crate::gpu_bridge::GpuSolverBridge for RecordingBridge {
7610 fn send_island(&mut self, _p: &[[Fix128; 3]], _v: &[[Fix128; 3]]) {}
7611 fn dispatch_iterations(&mut self, _iters: u32, _dt: Fix128) {}
7612 fn recv_island(&self, _p: &mut [[Fix128; 3]], _v: &mut [[Fix128; 3]]) {}
7613 fn assert_bit_exact_vs_cpu(
7614 &self,
7615 _fixture: &crate::gpu_bridge::DiffFixture,
7616 ) -> Result<(), crate::gpu_bridge::GpuDivergence> {
7617 Ok(())
7618 }
7619 fn send_contact_constraints(&mut self, _c: &[ContactConstraint]) {
7620 self.send_contact_count
7621 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7622 }
7623 fn send_body_state(&mut self, _p: &[[Fix128; 3]], _i: &[Fix128]) {
7624 self.send_body_state_count
7625 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7626 }
7627 fn dispatch_contact_solve_iteration(&mut self, _warm_start_factor: Fix128) {
7628 self.dispatch_count
7629 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7630 }
7631 fn recv_contact_constraints(&self, _c: &mut [ContactConstraint]) {}
7632 fn recv_body_positions(&self, _p: &mut [[Fix128; 3]]) {}
7633 fn send_joints(&mut self, _j: &[crate::joint::Joint]) {
7634 self.send_joints_count
7635 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7636 }
7637 fn send_body_rotations(&mut self, _r: &[[Fix128; 4]]) {
7638 self.send_body_rotations_count
7639 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7640 }
7641 fn dispatch_joint_solve_iteration(&mut self, _dt: Fix128) {
7642 self.dispatch_joint_count
7643 .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
7644 }
7645 }
7646
7647 #[cfg(feature = "gpu-solver-bridge")]
7652 fn build_one_contact_world() -> PhysicsWorld {
7653 let mut world = PhysicsWorld::new(SolverConfig {
7654 gravity: Vec3Fix::ZERO,
7655 ..SolverConfig::default()
7656 });
7657 let body_a = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
7658 let body_b = RigidBody::new_dynamic(
7659 Vec3Fix::new(Fix128::from_ratio(19, 10), Fix128::ZERO, Fix128::ZERO),
7660 Fix128::ONE,
7661 );
7662 world.add_body_with_radius(body_a, Fix128::ONE);
7663 world.add_body_with_radius(body_b, Fix128::ONE);
7664 world
7665 }
7666
7667 #[cfg(feature = "gpu-solver-bridge")]
7671 #[test]
7672 fn physics_world_step_auto_routes_through_bridge_when_attached() {
7673 let send_contact_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7674 let send_body_state_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7675 let dispatch_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7676
7677 let bridge = RecordingBridge {
7678 send_contact_count: std::sync::Arc::clone(&send_contact_count),
7679 send_body_state_count: std::sync::Arc::clone(&send_body_state_count),
7680 dispatch_count: std::sync::Arc::clone(&dispatch_count),
7681 send_joints_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7682 send_body_rotations_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7683 dispatch_joint_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7684 };
7685
7686 let mut world = build_one_contact_world();
7687 world.set_gpu_solver_bridge(Some(Box::new(bridge)));
7688 assert!(world.gpu_solver_bridge_installed());
7689
7690 let iters = world.config.iterations;
7695 let dt = Fix128::from_ratio(1, 60);
7696 world.substep(dt);
7697
7698 assert_eq!(
7699 send_contact_count.load(core::sync::atomic::Ordering::SeqCst),
7700 iters,
7701 "send_contact_constraints must be invoked once per PGS iteration"
7702 );
7703 assert_eq!(
7704 send_body_state_count.load(core::sync::atomic::Ordering::SeqCst),
7705 iters,
7706 "send_body_state must be invoked once per PGS iteration"
7707 );
7708 assert_eq!(
7709 dispatch_count.load(core::sync::atomic::Ordering::SeqCst),
7710 iters,
7711 "dispatch_contact_solve_iteration must fire once per PGS iteration"
7712 );
7713 }
7714
7715 #[cfg(feature = "gpu-solver-bridge")]
7720 #[test]
7721 fn physics_world_step_falls_back_to_cpu_when_bridge_detached() {
7722 let mut world = build_one_contact_world();
7723 assert!(!world.gpu_solver_bridge_installed());
7724
7725 let x_before = world.bodies[0].position.x;
7726 let dt = Fix128::from_ratio(1, 60);
7727 world.substep(dt);
7728 let x_after = world.bodies[0].position.x;
7729
7730 assert_ne!(
7734 x_before, x_after,
7735 "CPU contact solver must adjust position when no bridge is installed"
7736 );
7737 }
7738
7739 #[cfg(feature = "gpu-solver-bridge")]
7744 #[test]
7745 fn physics_world_bridge_attach_detach_lifecycle() {
7746 let mut world = PhysicsWorld::new(SolverConfig::default());
7747 assert!(!world.gpu_solver_bridge_installed());
7748
7749 let bridge = RecordingBridge {
7750 send_contact_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7751 send_body_state_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7752 dispatch_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7753 send_joints_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7754 send_body_rotations_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7755 dispatch_joint_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7756 };
7757 world.set_gpu_solver_bridge(Some(Box::new(bridge)));
7758 assert!(world.gpu_solver_bridge_installed());
7759
7760 let taken = world.take_gpu_solver_bridge();
7761 assert!(taken.is_some(), "first take must return the installed box");
7762 assert!(!world.gpu_solver_bridge_installed());
7763
7764 let taken_again = world.take_gpu_solver_bridge();
7765 assert!(
7766 taken_again.is_none(),
7767 "subsequent take must return None when nothing is installed"
7768 );
7769 }
7770
7771 #[cfg(feature = "gpu-solver-bridge")]
7776 fn build_one_ball_joint_world() -> PhysicsWorld {
7777 use crate::joint::{BallJoint, Joint};
7778 use crate::math::Vec3Fix;
7779 let mut world = PhysicsWorld::new(SolverConfig::default());
7780 let body_a = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
7781 let body_b = RigidBody::new_dynamic(
7782 Vec3Fix::new(Fix128::from_int(1), Fix128::ZERO, Fix128::ZERO),
7783 Fix128::ONE,
7784 );
7785 world.add_body(body_a);
7786 world.add_body(body_b);
7787 world.joints.push(Joint::Ball(BallJoint::new(
7788 0,
7789 1,
7790 Vec3Fix::ZERO,
7791 Vec3Fix::ZERO,
7792 )));
7793 world
7794 }
7795
7796 #[cfg(feature = "gpu-solver-bridge")]
7803 #[test]
7804 fn physics_world_substep_auto_routes_joint_solve_through_bridge() {
7805 let send_joints_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7806 let send_body_rotations_count =
7807 std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7808 let dispatch_joint_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7809
7810 let bridge = RecordingBridge {
7811 send_contact_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7812 send_body_state_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7813 dispatch_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7814 send_joints_count: std::sync::Arc::clone(&send_joints_count),
7815 send_body_rotations_count: std::sync::Arc::clone(&send_body_rotations_count),
7816 dispatch_joint_count: std::sync::Arc::clone(&dispatch_joint_count),
7817 };
7818
7819 let mut world = build_one_ball_joint_world();
7820 world.set_gpu_solver_bridge(Some(Box::new(bridge)));
7821
7822 let dt = Fix128::from_ratio(1, 60);
7823 world.substep(dt);
7824
7825 assert_eq!(
7826 send_joints_count.load(core::sync::atomic::Ordering::SeqCst),
7827 1,
7828 "send_joints must be invoked exactly once per substep"
7829 );
7830 assert_eq!(
7831 send_body_rotations_count.load(core::sync::atomic::Ordering::SeqCst),
7832 1,
7833 "send_body_rotations must be invoked exactly once per substep"
7834 );
7835 assert_eq!(
7836 dispatch_joint_count.load(core::sync::atomic::Ordering::SeqCst),
7837 1,
7838 "dispatch_joint_solve_iteration must fire exactly once per substep (single Baumgarte projection, not PGS)"
7839 );
7840 }
7841
7842 #[cfg(feature = "gpu-solver-bridge")]
7847 #[test]
7848 fn physics_world_substep_falls_back_to_cpu_joint_solve_when_bridge_detached() {
7849 let mut world = build_one_ball_joint_world();
7850 assert!(!world.gpu_solver_bridge_installed());
7851
7852 let a_before = world.bodies[0].position.x;
7853 let b_before = world.bodies[1].position.x;
7854 let dt = Fix128::from_ratio(1, 60);
7855 world.substep(dt);
7856 let a_after = world.bodies[0].position.x;
7857 let b_after = world.bodies[1].position.x;
7858
7859 let a_delta = (a_after - a_before).to_f32();
7865 let b_delta = (b_after - b_before).to_f32();
7866 assert!(
7867 a_delta > 0.0,
7868 "body_a should move in +x toward body_b (delta = {a_delta})"
7869 );
7870 assert!(
7871 b_delta < 0.0,
7872 "body_b should move in -x toward body_a (delta = {b_delta})"
7873 );
7874 }
7875
7876 #[cfg(feature = "gpu-solver-bridge")]
7881 #[test]
7882 fn physics_world_substep_joint_solve_bridge_attach_take_lifecycle() {
7883 let dispatch_joint_count = std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0));
7884 let bridge = RecordingBridge {
7885 send_contact_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7886 send_body_state_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7887 dispatch_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7888 send_joints_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7889 send_body_rotations_count: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(0)),
7890 dispatch_joint_count: std::sync::Arc::clone(&dispatch_joint_count),
7891 };
7892
7893 let mut world = build_one_ball_joint_world();
7894 world.set_gpu_solver_bridge(Some(Box::new(bridge)));
7895
7896 let dt = Fix128::from_ratio(1, 60);
7897 world.substep(dt);
7898 assert_eq!(
7899 dispatch_joint_count.load(core::sync::atomic::Ordering::SeqCst),
7900 1,
7901 "first substep must route through bridge"
7902 );
7903
7904 let taken = world.take_gpu_solver_bridge();
7905 assert!(taken.is_some(), "bridge take must return Some");
7906
7907 let a_before = world.bodies[0].position.x;
7910 world.substep(dt);
7911 let a_after = world.bodies[0].position.x;
7912 assert_eq!(
7913 dispatch_joint_count.load(core::sync::atomic::Ordering::SeqCst),
7914 1,
7915 "after take_gpu_solver_bridge, no further bridge dispatch may occur"
7916 );
7917 assert_ne!(
7918 a_before, a_after,
7919 "CPU joint solve should move body_a toward body_b after detach"
7920 );
7921 }
7922
7923 fn hub_world(hub: RigidBody, spokes: usize) -> PhysicsWorld {
7929 let mut world = PhysicsWorld::new(SolverConfig {
7930 gravity: Vec3Fix::ZERO,
7931 ..Default::default()
7932 });
7933 let hub_idx = world.add_body(hub);
7934 for i in 0..spokes {
7935 let spoke = world.add_body(RigidBody::new_dynamic(
7936 Vec3Fix::from_int(i as i64 + 1, 0, 0),
7937 Fix128::ONE,
7938 ));
7939 world.add_distance_constraint(DistanceConstraint::new(
7940 hub_idx,
7941 spoke,
7942 Vec3Fix::ZERO,
7943 Vec3Fix::ZERO,
7944 Fix128::ONE,
7945 ));
7946 }
7947 world
7948 }
7949
7950 #[test]
7951 fn coloring_dynamic_hub_over_64_spokes_gets_one_color_per_constraint() {
7952 for spokes in [65usize, 70, 200] {
7955 let mut world = hub_world(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE), spokes);
7956 world.rebuild_batches();
7957 assert_eq!(
7958 world.num_batches(),
7959 spokes,
7960 "dynamic hub with {spokes} spokes needs {spokes} batches"
7961 );
7962 assert!(world.batches_are_body_disjoint());
7963 }
7964 }
7965
7966 #[test]
7967 fn coloring_static_hub_imposes_no_constraint() {
7968 let mut world = hub_world(RigidBody::new_static(Vec3Fix::ZERO), 200);
7971 world.rebuild_batches();
7972 assert_eq!(world.num_batches(), 1);
7973 assert!(world.batches_are_body_disjoint());
7974 }
7975
7976 #[test]
7977 fn coloring_contacts_and_distances_share_color_space() {
7978 let mut world = PhysicsWorld::new(SolverConfig::default());
7982 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
7983 let b = world.add_body(RigidBody::new_dynamic(
7984 Vec3Fix::from_int(1, 0, 0),
7985 Fix128::ONE,
7986 ));
7987 world.add_distance_constraint(DistanceConstraint::new(
7988 a,
7989 b,
7990 Vec3Fix::ZERO,
7991 Vec3Fix::ZERO,
7992 Fix128::ONE,
7993 ));
7994 world.add_contact(ContactConstraint::new(
7995 a,
7996 b,
7997 Contact {
7998 point_a: Vec3Fix::ZERO,
7999 point_b: Vec3Fix::ZERO,
8000 normal: Vec3Fix::from_int(1, 0, 0),
8001 depth: Fix128::from_ratio(1, 10),
8002 },
8003 ));
8004 world.rebuild_batches();
8005 assert_eq!(world.num_batches(), 2);
8006 assert!(world.batches_are_body_disjoint());
8007 }
8008
8009 #[test]
8010 fn coloring_disjoint_check_detects_violation() {
8011 let mut world = hub_world(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE), 2);
8013 world.rebuild_batches();
8014 assert!(world.batches_are_body_disjoint());
8015 let mut bad = ConstraintBatch::default();
8016 bad.distance_indices.extend([0usize, 1]);
8017 world.constraint_batches = vec![bad];
8018 assert!(!world.batches_are_body_disjoint());
8019 }
8020
8021 #[cfg(feature = "parallel")]
8022 #[test]
8023 fn parallel_step_static_hub_matches_sequential_solver() {
8024 let sub_dt = Fix128::from_ratio(1, 240);
8028 let mut par = hub_world(RigidBody::new_static(Vec3Fix::ZERO), 70);
8029 let mut seq = hub_world(RigidBody::new_static(Vec3Fix::ZERO), 70);
8030 par.rebuild_batches();
8031 seq.rebuild_batches();
8032 assert_eq!(par.num_batches(), 1, "static hub → single batch");
8033 for _ in 0..120 {
8034 par.substep_batched(sub_dt);
8035 seq.substep(sub_dt);
8036 }
8037 for (p, s) in par.bodies.iter().zip(&seq.bodies) {
8038 assert_eq!(p.position, s.position);
8039 assert_eq!(p.velocity, s.velocity);
8040 }
8041 assert!(par.batches_are_body_disjoint());
8042 }
8043
8044 #[cfg(feature = "parallel")]
8045 #[test]
8046 fn parallel_step_rebuilds_when_body_becomes_dynamic_after_coloring() {
8047 let mut world = hub_world(RigidBody::new_static(Vec3Fix::ZERO), 70);
8051 world.rebuild_batches();
8052 assert_eq!(world.num_batches(), 1);
8053 world.bodies[0].inv_mass = Fix128::ONE;
8054 world.solve_constraints_batched(Fix128::from_ratio(1, 60));
8055 assert_eq!(world.num_batches(), 70);
8056 assert!(world.batches_are_body_disjoint());
8057 }
8058
8059 fn quiet_world() -> PhysicsWorld {
8068 let config = SolverConfig {
8069 gravity: Vec3Fix::ZERO,
8070 damping: Fix128::ONE,
8071 ..SolverConfig::default()
8072 };
8073 PhysicsWorld::new(config)
8074 }
8075
8076 fn v3(x: i64, y: i64, z: i64) -> Vec3Fix {
8077 Vec3Fix::from_int(x, y, z)
8078 }
8079
8080 fn r(n: i64, d: i64) -> Fix128 {
8081 Fix128::from_ratio(n, d)
8082 }
8083
8084 #[test]
8085 fn update_velocities_linear_velocity_is_position_delta_times_inv_dt() {
8086 let mut world = quiet_world();
8087 let dyn_idx = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8088 let static_idx = world.add_body(RigidBody::new_static(v3(5, 5, 5)));
8089 let dt = r(1, 4); world.bodies[dyn_idx].prev_position = v3(1, 1, 1);
8093 world.bodies[dyn_idx].position = Vec3Fix::new(
8094 Fix128::ONE + r(1, 4),
8095 Fix128::ONE - r(1, 2),
8096 Fix128::ONE + r(3, 4),
8097 );
8098 world.bodies[static_idx].prev_position = v3(0, 0, 0);
8100 world.bodies[static_idx].position = v3(5, 5, 5);
8101
8102 world.update_velocities(dt);
8103
8104 assert_eq!(world.bodies[dyn_idx].velocity, v3(1, -2, 3));
8105 assert_eq!(world.bodies[static_idx].velocity, Vec3Fix::ZERO);
8106 }
8107
8108 #[test]
8109 fn update_velocities_angular_velocity_from_rotation_delta_positive_w() {
8110 let mut world = quiet_world();
8111 let idx = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8112 let dt = r(1, 4);
8113 let q = QuatFix::from_axis_angle(v3(1, 2, 3), r(1, 2));
8115 world.bodies[idx].prev_rotation = QuatFix::IDENTITY;
8116 world.bodies[idx].rotation = q;
8117
8118 world.update_velocities(dt);
8119
8120 let got = world.bodies[idx].angular_velocity;
8122 let n = crate::det_math::sqrt64(14.0);
8123 let want = [2.0 / n, 4.0 / n, 6.0 / n];
8124 for (g, e) in [got.x, got.y, got.z].iter().zip(want) {
8125 assert!((g.to_f64() - e).abs() < 1e-12, "{} vs {e}", g.to_f64());
8126 }
8127 }
8128
8129 #[test]
8130 fn update_velocities_angular_velocity_negative_w_flips_sign() {
8131 let mut world = quiet_world();
8132 let idx = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8133 let dt = r(1, 4);
8134 let theta = Fix128::PI + Fix128::HALF_PI;
8136 let q = QuatFix::from_axis_angle(v3(1, -2, 3), theta);
8137 assert!(q.w < Fix128::ZERO, "test precondition: w must be negative");
8138 world.bodies[idx].prev_rotation = QuatFix::IDENTITY;
8139 world.bodies[idx].rotation = q;
8140
8141 world.update_velocities(dt);
8142
8143 let got = world.bodies[idx].angular_velocity;
8147 let n = crate::det_math::sqrt64(14.0);
8148 let k = 2.0 * core::f64::consts::PI / n;
8149 let want = [-k, 2.0 * k, -3.0 * k];
8150 for (g, e) in [got.x, got.y, got.z].iter().zip(want) {
8151 assert!((g.to_f64() - e).abs() < 1e-12, "{} vs {e}", g.to_f64());
8152 }
8153 }
8154
8155 #[test]
8156 fn update_velocities_angular_velocity_zero_w_takes_positive_branch() {
8157 let mut world = quiet_world();
8158 let idx = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8159 let dt = r(1, 4);
8160 let q = QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
8162 world.bodies[idx].prev_rotation = QuatFix::IDENTITY;
8163 world.bodies[idx].rotation = q;
8164
8165 world.update_velocities(dt);
8166
8167 let got = world.bodies[idx].angular_velocity;
8169 assert_eq!((got.x, got.y), (Fix128::ZERO, Fix128::ZERO));
8170 let want = 4.0 * core::f64::consts::PI;
8171 assert!(
8172 (got.z.to_f64() - want).abs() < 1e-12,
8173 "{} vs {want}",
8174 got.z.to_f64()
8175 );
8176 }
8177
8178 fn push_contact(
8180 world: &mut PhysicsWorld,
8181 a: usize,
8182 b: usize,
8183 friction: Fix128,
8184 restitution: Fix128,
8185 ) {
8186 let contact = Contact {
8187 depth: r(1, 100),
8188 normal: v3(1, 0, 0),
8189 point_a: Vec3Fix::ZERO,
8190 point_b: Vec3Fix::ZERO,
8191 };
8192 let mut c = ContactConstraint::new(a, b, contact);
8193 c.friction = friction;
8194 c.restitution = restitution;
8195 world.contact_constraints.push(c);
8196 }
8197
8198 fn give_velocity(world: &mut PhysicsWorld, idx: usize, v: Vec3Fix) {
8203 let b = &mut world.bodies[idx];
8204 b.velocity = v;
8205 b.prev_position = b.position - v * r(1, 4);
8206 b.prev_rotation = b.rotation;
8207 }
8208
8209 fn freeze_positions(world: &mut PhysicsWorld) {
8211 for b in &mut world.bodies {
8212 b.prev_position = b.position;
8213 b.prev_rotation = b.rotation;
8214 }
8215 }
8216
8217 #[test]
8218 fn update_velocities_restitution_splits_impulse_by_inverse_mass() {
8219 let mut world = quiet_world();
8220 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8221 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8222 push_contact(&mut world, a, b, Fix128::ZERO, r(1, 2));
8223 freeze_positions(&mut world);
8224 give_velocity(&mut world, a, v3(-1, 0, 0));
8226
8227 world.update_velocities(r(1, 4));
8228
8229 assert_eq!(
8231 world.bodies[a].velocity,
8232 Vec3Fix::new(-r(1, 4), Fix128::ZERO, Fix128::ZERO)
8233 );
8234 assert_eq!(
8235 world.bodies[b].velocity,
8236 Vec3Fix::new(-r(3, 4), Fix128::ZERO, Fix128::ZERO)
8237 );
8238 }
8239
8240 #[test]
8241 fn update_velocities_restitution_skips_separating_contact() {
8242 let mut world = quiet_world();
8243 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8244 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8245 push_contact(&mut world, a, b, Fix128::ZERO, r(1, 2));
8246 freeze_positions(&mut world);
8247 give_velocity(&mut world, a, v3(1, 0, 0));
8249
8250 world.update_velocities(r(1, 4));
8251
8252 assert_eq!(world.bodies[a].velocity, v3(1, 0, 0));
8253 assert_eq!(world.bodies[b].velocity, Vec3Fix::ZERO);
8254 }
8255
8256 #[test]
8257 fn update_velocities_restitution_uses_mass_ratio() {
8258 let mut world = quiet_world();
8259 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8261 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8262 world.bodies[b].inv_mass = Fix128::from_int(3);
8263 push_contact(&mut world, a, b, Fix128::ZERO, Fix128::ZERO);
8264 freeze_positions(&mut world);
8265 give_velocity(&mut world, a, v3(-2, 0, 0)); world.update_velocities(r(1, 4));
8268
8269 assert_eq!(
8271 world.bodies[a].velocity,
8272 Vec3Fix::new(-r(3, 2), Fix128::ZERO, Fix128::ZERO)
8273 );
8274 assert_eq!(
8275 world.bodies[b].velocity,
8276 Vec3Fix::new(-r(3, 2), Fix128::ZERO, Fix128::ZERO)
8277 );
8278 }
8279
8280 #[test]
8281 fn update_velocities_friction_clamped_by_coulomb_limit() {
8282 let mut world = quiet_world();
8283 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8284 let b = world.add_body(RigidBody::new_static(v3(1, 0, 0))); push_contact(&mut world, a, b, r(1, 2), Fix128::ZERO);
8286 world.contact_constraints[0].cached_lambda = Fix128::ONE;
8288 freeze_positions(&mut world);
8289 give_velocity(&mut world, a, v3(4, 3, 0));
8291
8292 world.update_velocities(r(1, 4));
8293
8294 assert_eq!(
8296 world.bodies[a].velocity,
8297 Vec3Fix::new(Fix128::from_int(4), Fix128::ONE, Fix128::ZERO)
8298 );
8299 assert_eq!(world.bodies[b].velocity, Vec3Fix::ZERO);
8300 }
8301
8302 #[test]
8303 fn update_velocities_friction_below_limit_removes_all_tangential_velocity() {
8304 let mut world = quiet_world();
8305 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8306 let b = world.add_body(RigidBody::new_static(v3(1, 0, 0)));
8307 push_contact(&mut world, a, b, Fix128::ONE, Fix128::ZERO);
8308 world.contact_constraints[0].cached_lambda = Fix128::ONE;
8310 freeze_positions(&mut world);
8311 give_velocity(&mut world, a, v3(4, 0, 3));
8312
8313 world.update_velocities(r(1, 4));
8314
8315 assert_eq!(
8317 world.bodies[a].velocity,
8318 Vec3Fix::new(Fix128::from_int(4), Fix128::ZERO, Fix128::ZERO)
8319 );
8320 }
8321
8322 #[test]
8323 fn update_velocities_friction_boundary_equal_to_limit_uses_limit() {
8324 let mut world = quiet_world();
8325 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8326 let b = world.add_body(RigidBody::new_static(v3(1, 0, 0)));
8327 push_contact(&mut world, a, b, Fix128::ONE, Fix128::ZERO);
8328 world.contact_constraints[0].cached_lambda = r(3, 4);
8330 freeze_positions(&mut world);
8331 give_velocity(&mut world, a, v3(3, 3, 0));
8333
8334 world.update_velocities(r(1, 4));
8335
8336 assert_eq!(
8337 world.bodies[a].velocity,
8338 Vec3Fix::new(Fix128::from_int(3), Fix128::ZERO, Fix128::ZERO)
8339 );
8340 }
8341
8342 #[test]
8343 fn update_velocities_sensor_on_either_side_skips_response() {
8344 for sensor_side in 0..2 {
8345 let mut world = quiet_world();
8346 let a = world.add_body(RigidBody::new_dynamic(v3(0, 0, 0), Fix128::ONE));
8347 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
8348 world.bodies[if sensor_side == 0 { a } else { b }].is_sensor = true;
8349 push_contact(&mut world, a, b, r(1, 2), r(1, 2));
8350 freeze_positions(&mut world);
8351 give_velocity(&mut world, a, v3(-1, 2, 0));
8352
8353 world.update_velocities(r(1, 4));
8354
8355 assert_eq!(
8356 world.bodies[a].velocity,
8357 v3(-1, 2, 0),
8358 "sensor side {sensor_side}"
8359 );
8360 assert_eq!(
8361 world.bodies[b].velocity,
8362 Vec3Fix::ZERO,
8363 "sensor side {sensor_side}"
8364 );
8365 }
8366 }
8367
8368 #[test]
8369 fn update_velocities_two_static_bodies_are_skipped() {
8370 let mut world = quiet_world();
8371 let a = world.add_body(RigidBody::new_static(v3(0, 0, 0)));
8372 let b = world.add_body(RigidBody::new_static(v3(1, 0, 0)));
8373 push_contact(&mut world, a, b, Fix128::ONE, Fix128::ONE);
8374 freeze_positions(&mut world);
8376 world.bodies[a].velocity = v3(-1, 1, 0);
8377
8378 world.update_velocities(r(1, 4));
8379
8380 assert_eq!(world.bodies[a].velocity, v3(-1, 1, 0));
8381 }
8382
8383 fn sphere_world(positions: &[Vec3Fix]) -> PhysicsWorld {
8387 let mut world = quiet_world();
8388 for &p in positions {
8389 world.add_body_with_radius(RigidBody::new_static(p), Fix128::from_int(2));
8390 }
8391 world
8392 }
8393
8394 #[test]
8395 fn raycast_hit_distance_is_exact_closed_form() {
8396 let world = sphere_world(&[v3(10, 0, 0)]);
8398 let hit = world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100));
8399 assert_eq!(hit, Some((0, Fix128::from_int(8))));
8400 let hit2 = world.raycast(Vec3Fix::ZERO, v3(3, 0, 0), Fix128::from_int(100));
8402 assert_eq!(hit2, Some((0, Fix128::from_int(8))));
8403 }
8404
8405 #[test]
8406 fn raycast_along_every_axis_and_direction() {
8407 for (dir, pos) in [
8409 (v3(1, 0, 0), v3(10, 0, 0)),
8410 (v3(-1, 0, 0), v3(-10, 0, 0)),
8411 (v3(0, 1, 0), v3(0, 10, 0)),
8412 (v3(0, -1, 0), v3(0, -10, 0)),
8413 (v3(0, 0, 1), v3(0, 0, 10)),
8414 (v3(0, 0, -1), v3(0, 0, -10)),
8415 ] {
8416 let world = sphere_world(&[pos]);
8417 assert_eq!(
8418 world.raycast(Vec3Fix::ZERO, dir, Fix128::from_int(100)),
8419 Some((0, Fix128::from_int(8))),
8420 "dir {dir:?}"
8421 );
8422 assert_eq!(
8424 world.raycast(
8425 Vec3Fix::ZERO,
8426 dir * Fix128::from_int(-1),
8427 Fix128::from_int(100)
8428 ),
8429 None
8430 );
8431 }
8432 }
8433
8434 #[test]
8435 fn raycast_origin_on_surface_hits_at_zero_not_far_side() {
8436 let world = sphere_world(&[v3(10, 0, 0)]);
8438 let hit = world.raycast(v3(8, 0, 0), v3(1, 0, 0), Fix128::from_int(100));
8439 assert_eq!(hit, Some((0, Fix128::ZERO)));
8440 }
8441
8442 #[test]
8443 fn raycast_from_inside_sphere_uses_far_intersection() {
8444 let world = sphere_world(&[v3(10, 0, 0)]);
8446 let hit = world.raycast(v3(10, 0, 0), v3(1, 0, 0), Fix128::from_int(100));
8447 assert_eq!(hit, Some((0, Fix128::from_int(2))));
8448 }
8449
8450 #[test]
8451 fn raycast_max_distance_boundary_inclusive() {
8452 let world = sphere_world(&[v3(10, 0, 0)]);
8453 assert_eq!(
8455 world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(8)),
8456 Some((0, Fix128::from_int(8)))
8457 );
8458 assert_eq!(
8460 world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(7)),
8461 None
8462 );
8463 assert_eq!(
8466 world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(9)),
8467 Some((0, Fix128::from_int(8)))
8468 );
8469 }
8470
8471 #[test]
8472 fn raycast_returns_nearest_regardless_of_body_order() {
8473 let near = v3(10, 0, 0);
8475 let far = v3(20, 0, 0);
8476 let w1 = sphere_world(&[far, near]);
8477 assert_eq!(
8478 w1.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8479 Some((1, Fix128::from_int(8)))
8480 );
8481 let w2 = sphere_world(&[near, far]);
8482 assert_eq!(
8483 w2.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8484 Some((0, Fix128::from_int(8)))
8485 );
8486 }
8487
8488 #[test]
8489 fn raycast_misses_sphere_beside_the_ray() {
8490 let world = sphere_world(&[v3(10, 5, 0)]);
8492 assert_eq!(
8493 world.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8494 None
8495 );
8496 let tangent = sphere_world(&[v3(10, 2, 0)]);
8498 assert_eq!(
8499 tangent.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8500 Some((0, Fix128::from_int(10)))
8501 );
8502 }
8503
8504 #[test]
8505 fn raycast_zero_direction_and_no_collidable_bodies_return_none() {
8506 let world = sphere_world(&[v3(10, 0, 0)]);
8507 assert_eq!(
8508 world.raycast(Vec3Fix::ZERO, Vec3Fix::ZERO, Fix128::from_int(100)),
8509 None
8510 );
8511 let mut no_radius = quiet_world();
8512 no_radius.add_body(RigidBody::new_static(v3(10, 0, 0)));
8513 assert_eq!(
8514 no_radius.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8515 None
8516 );
8517 let mut cleared = sphere_world(&[v3(10, 0, 0)]);
8519 cleared.clear_body_collision_radius(0);
8520 assert_eq!(
8521 cleared.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8522 None
8523 );
8524 cleared.set_body_collision_radius(0, Fix128::from_int(2));
8526 assert_eq!(
8527 cleared.raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(100)),
8528 Some((0, Fix128::from_int(8)))
8529 );
8530 }
8531
8532 fn dc(a: usize, b: usize) -> DistanceConstraint {
8535 DistanceConstraint {
8536 body_a: a,
8537 body_b: b,
8538 local_anchor_a: Vec3Fix::ZERO,
8539 local_anchor_b: Vec3Fix::ZERO,
8540 target_distance: Fix128::ONE,
8541 compliance: Fix128::ZERO,
8542 cached_lambda: Fix128::ZERO,
8543 }
8544 }
8545
8546 fn three_body_world_with_all_pairs() -> PhysicsWorld {
8547 let mut world = quiet_world();
8548 for i in 0..3 {
8549 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
8550 }
8551 world.add_distance_constraint(dc(0, 1));
8552 world.add_distance_constraint(dc(1, 2));
8553 world.add_distance_constraint(dc(0, 2));
8554 world.contact_constraints.push(ContactConstraint::new(
8555 0,
8556 1,
8557 Contact {
8558 depth: Fix128::ZERO,
8559 normal: v3(1, 0, 0),
8560 point_a: Vec3Fix::ZERO,
8561 point_b: Vec3Fix::ZERO,
8562 },
8563 ));
8564 world.contact_constraints.push(ContactConstraint::new(
8565 1,
8566 2,
8567 Contact {
8568 depth: Fix128::ZERO,
8569 normal: v3(1, 0, 0),
8570 point_a: Vec3Fix::ZERO,
8571 point_b: Vec3Fix::ZERO,
8572 },
8573 ));
8574 world.contact_constraints.push(ContactConstraint::new(
8575 0,
8576 2,
8577 Contact {
8578 depth: Fix128::ZERO,
8579 normal: v3(1, 0, 0),
8580 point_a: Vec3Fix::ZERO,
8581 point_b: Vec3Fix::ZERO,
8582 },
8583 ));
8584 world
8585 }
8586
8587 fn dist_pairs(world: &PhysicsWorld) -> Vec<(usize, usize)> {
8588 world
8589 .distance_constraints
8590 .iter()
8591 .map(|c| (c.body_a, c.body_b))
8592 .collect()
8593 }
8594
8595 fn contact_pairs(world: &PhysicsWorld) -> Vec<(usize, usize)> {
8596 world
8597 .contact_constraints
8598 .iter()
8599 .map(|c| (c.body_a, c.body_b))
8600 .collect()
8601 }
8602
8603 #[test]
8604 fn remove_body_middle_drops_its_constraints_and_remaps_last() {
8605 let mut world = three_body_world_with_all_pairs();
8606 let removed = world.remove_body(1).expect("index 1 exists");
8607 assert_eq!(removed.position, v3(1, 0, 0));
8608 assert_eq!(world.bodies.len(), 2);
8609 assert_eq!(world.bodies[1].position, v3(2, 0, 0));
8611 assert_eq!(dist_pairs(&world), vec![(0, 1)]);
8613 assert_eq!(contact_pairs(&world), vec![(0, 1)]);
8614 assert_eq!(world.body_materials.len(), 2);
8615 assert_eq!(world.body_collision_radii.len(), 2);
8616 assert_eq!(world.body_filters.len(), 2);
8617 }
8618
8619 #[test]
8620 fn remove_body_first_remaps_last_into_slot_zero() {
8621 let mut world = three_body_world_with_all_pairs();
8622 world.remove_body(0).expect("index 0 exists");
8623 assert_eq!(world.bodies[0].position, v3(2, 0, 0));
8624 assert_eq!(world.bodies[1].position, v3(1, 0, 0));
8625 assert_eq!(dist_pairs(&world), vec![(1, 0)]);
8627 assert_eq!(contact_pairs(&world), vec![(1, 0)]);
8628 }
8629
8630 #[test]
8631 fn remove_body_last_needs_no_remap() {
8632 let mut world = three_body_world_with_all_pairs();
8633 world.remove_body(2).expect("index 2 exists");
8634 assert_eq!(world.bodies.len(), 2);
8635 assert_eq!(world.bodies[1].position, v3(1, 0, 0));
8636 assert_eq!(dist_pairs(&world), vec![(0, 1)]);
8637 assert_eq!(contact_pairs(&world), vec![(0, 1)]);
8638 }
8639
8640 #[test]
8641 fn remove_body_out_of_range_is_none_and_leaves_world_untouched() {
8642 let mut world = three_body_world_with_all_pairs();
8643 assert!(world.remove_body(3).is_none());
8644 assert!(world.remove_body(usize::MAX).is_none());
8645 assert_eq!(world.bodies.len(), 3);
8646 assert_eq!(dist_pairs(&world), vec![(0, 1), (1, 2), (0, 2)]);
8647 }
8648
8649 #[test]
8650 fn remove_body_never_leaves_self_or_dangling_references() {
8651 for victim in 0..4 {
8653 let mut world = quiet_world();
8654 for i in 0..4 {
8655 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
8656 }
8657 for a in 0..4 {
8658 for b in (a + 1)..4 {
8659 world.add_distance_constraint(dc(a, b));
8660 }
8661 }
8662 world.remove_body(victim).expect("in range");
8663 let n = world.bodies.len();
8664 assert_eq!(n, 3);
8665 assert_eq!(world.distance_constraints.len(), 3, "victim {victim}");
8667 for c in &world.distance_constraints {
8668 assert!(
8669 c.body_a < n && c.body_b < n,
8670 "victim {victim}: dangling {:?}",
8671 (c.body_a, c.body_b)
8672 );
8673 assert_ne!(c.body_a, c.body_b, "victim {victim}: self constraint");
8674 }
8675 let mut xs: Vec<i64> = world.bodies.iter().map(|b| b.position.x.hi).collect();
8677 xs.sort_unstable();
8678 let mut expected: Vec<i64> = (0..4).filter(|&i| i != victim as i64).collect();
8679 expected.sort_unstable();
8680 assert_eq!(xs, expected);
8681 }
8682 }
8683
8684 #[test]
8685 fn remove_body_with_joint_drops_joint_and_rebuilds_islands() {
8686 let mut world = quiet_world();
8687 for i in 0..3 {
8688 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
8689 }
8690 let j = crate::joint::BallJoint::new(1, 2, Vec3Fix::ZERO, Vec3Fix::ZERO);
8691 world.add_joint(Joint::Ball(j));
8692 let j2 = crate::joint::BallJoint::new(0, 2, Vec3Fix::ZERO, Vec3Fix::ZERO);
8693 world.add_joint(Joint::Ball(j2));
8694 assert_eq!(world.joint_count(), 2);
8695 world.remove_body(1).expect("in range");
8696 assert_eq!(world.joint_count(), 1);
8698 assert_eq!(world.joints[0].bodies(), (0, 1));
8699 assert_eq!(world.islands.find(0), world.islands.find(1));
8701 }
8702
8703 fn near(a: Fix128, b: Fix128) -> bool {
8707 let d = (a - b).abs();
8708 d.hi == 0 && d.lo < (1u64 << 4)
8709 }
8710
8711 fn assert_near_vec(a: Vec3Fix, b: Vec3Fix, what: &str) {
8712 assert!(
8713 near(a.x, b.x) && near(a.y, b.y) && near(a.z, b.z),
8714 "{what}: {a:?} vs {b:?}"
8715 );
8716 }
8717
8718 fn two_spheres(dist_num: i64, dist_den: i64, r_a: Fix128, r_b: Fix128) -> PhysicsWorld {
8720 let mut world = quiet_world();
8721 world.add_body_with_radius(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE), r_a);
8722 world.add_body_with_radius(
8723 RigidBody::new_dynamic(
8724 Vec3Fix::new(r(dist_num, dist_den), Fix128::ZERO, Fix128::ZERO),
8725 Fix128::ONE,
8726 ),
8727 r_b,
8728 );
8729 world
8730 }
8731
8732 #[test]
8733 fn detect_collisions_overlapping_spheres_produce_exact_contact() {
8734 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8738 world.detect_collisions();
8739 assert_eq!(world.contact_constraints.len(), 1);
8740 let c = &world.contact_constraints[0];
8741 assert_eq!((c.body_a, c.body_b), (0, 1));
8742 assert!(
8743 near(c.contact.depth, r(1, 2)),
8744 "depth {:?}",
8745 c.contact.depth
8746 );
8747 assert_near_vec(c.contact.normal, v3(-1, 0, 0), "normal");
8748 assert_near_vec(c.contact.point_a, v3(1, 0, 0), "point_a");
8749 assert_near_vec(
8750 c.contact.point_b,
8751 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO),
8752 "point_b",
8753 );
8754 assert_eq!(world.contact_events().len(), 1);
8756 assert!(world.trigger_events().is_empty());
8757 }
8758
8759 #[test]
8760 fn detect_collisions_asymmetric_radii_split_points_correctly() {
8761 let mut world = two_spheres(2, 1, Fix128::from_int(2), r(1, 2));
8763 world.detect_collisions();
8764 assert_eq!(world.contact_constraints.len(), 1);
8765 let c = world.contact_constraints[0].contact;
8766 assert!(near(c.depth, r(1, 2)), "depth {:?}", c.depth);
8767 assert_near_vec(c.point_a, v3(2, 0, 0), "point_a"); assert_near_vec(
8769 c.point_b,
8770 Vec3Fix::new(r(3, 2), Fix128::ZERO, Fix128::ZERO),
8771 "point_b",
8772 ); }
8774
8775 #[test]
8776 fn detect_collisions_touching_exactly_is_not_a_contact() {
8777 let mut world = two_spheres(2, 1, Fix128::ONE, Fix128::ONE);
8779 world.detect_collisions();
8780 assert!(world.contact_constraints.is_empty());
8781 assert!(world.contact_events().is_empty());
8782 let mut world2 = two_spheres(199, 100, Fix128::ONE, Fix128::ONE);
8784 world2.detect_collisions();
8785 assert_eq!(world2.contact_constraints.len(), 1);
8786 }
8787
8788 #[test]
8789 fn detect_collisions_coincident_centers_are_skipped() {
8790 let mut world = two_spheres(0, 1, Fix128::ONE, Fix128::ONE);
8792 world.detect_collisions();
8793 assert!(world.contact_constraints.is_empty());
8794 }
8795
8796 #[test]
8797 fn detect_collisions_relative_velocity_is_reported_along_normal() {
8798 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8799 world.bodies[0].velocity = v3(2, 7, 0); world.bodies[1].velocity = v3(-1, 0, 0);
8801 world.detect_collisions();
8802 let ev = &world.contact_events()[0];
8803 assert!(
8805 near(ev.relative_velocity, Fix128::from_int(-3)),
8806 "rel_vel {:?}",
8807 ev.relative_velocity
8808 );
8809 assert!(near(ev.depth, r(1, 2)), "depth {:?}", ev.depth);
8810 }
8811
8812 #[test]
8813 fn detect_collisions_sensor_reports_trigger_instead_of_constraint() {
8814 for sensor_side in 0..2 {
8815 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8816 world.bodies[sensor_side].is_sensor = true;
8817 world.detect_collisions();
8818 assert!(world.contact_constraints.is_empty(), "side {sensor_side}");
8819 assert_eq!(world.trigger_events().len(), 1, "side {sensor_side}");
8820 assert_eq!(world.contact_events().len(), 1, "side {sensor_side}");
8821 }
8822 }
8823
8824 #[test]
8825 fn detect_collisions_static_static_pair_is_skipped_but_static_dynamic_is_not() {
8826 let mut world = quiet_world();
8827 world.add_body_with_radius(RigidBody::new_static(Vec3Fix::ZERO), Fix128::ONE);
8828 world.add_body_with_radius(
8829 RigidBody::new_static(Vec3Fix::new(r(3, 2), Fix128::ZERO, Fix128::ZERO)),
8830 Fix128::ONE,
8831 );
8832 world.detect_collisions();
8833 assert!(world.contact_constraints.is_empty());
8834
8835 let mut mixed = quiet_world();
8836 mixed.add_body_with_radius(RigidBody::new_static(Vec3Fix::ZERO), Fix128::ONE);
8837 mixed.add_body_with_radius(
8838 RigidBody::new_dynamic(
8839 Vec3Fix::new(r(3, 2), Fix128::ZERO, Fix128::ZERO),
8840 Fix128::ONE,
8841 ),
8842 Fix128::ONE,
8843 );
8844 mixed.detect_collisions();
8845 assert_eq!(mixed.contact_constraints.len(), 1);
8846 }
8847
8848 #[test]
8849 fn detect_collisions_filter_blocks_same_group_and_masked_layers() {
8850 let mut same_group = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8852 let g = CollisionFilter {
8853 layer: 1,
8854 mask: u32::MAX,
8855 group: 7,
8856 };
8857 same_group.set_body_filter(0, g);
8858 same_group.set_body_filter(1, g);
8859 same_group.detect_collisions();
8860 assert!(same_group.contact_constraints.is_empty());
8861
8862 for side in 0..2 {
8864 let mut masked = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8865 masked.set_body_filter(
8866 side,
8867 CollisionFilter {
8868 layer: 1 << 5,
8869 mask: u32::MAX,
8870 group: 0,
8871 },
8872 );
8873 masked.set_body_filter(
8874 1 - side,
8875 CollisionFilter {
8876 layer: 1,
8877 mask: !(1 << 5),
8878 group: 0,
8879 },
8880 );
8881 masked.detect_collisions();
8882 assert!(masked.contact_constraints.is_empty(), "side {side}");
8883 }
8884
8885 let mut ok = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8887 ok.set_body_filter(
8888 0,
8889 CollisionFilter {
8890 layer: 1,
8891 mask: u32::MAX,
8892 group: 1,
8893 },
8894 );
8895 ok.set_body_filter(
8896 1,
8897 CollisionFilter {
8898 layer: 1,
8899 mask: u32::MAX,
8900 group: 2,
8901 },
8902 );
8903 ok.detect_collisions();
8904 assert_eq!(ok.contact_constraints.len(), 1);
8905 }
8906
8907 #[test]
8908 fn detect_collisions_both_sleeping_skipped_one_sleeping_wakes() {
8909 let mut both = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8910 both.islands.sleep_data[0].state = crate::sleeping::SleepState::Sleeping;
8911 both.islands.sleep_data[1].state = crate::sleeping::SleepState::Sleeping;
8912 both.detect_collisions();
8913 assert!(both.contact_constraints.is_empty());
8914
8915 let mut one = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8916 one.islands.sleep_data[0].state = crate::sleeping::SleepState::Sleeping;
8917 one.detect_collisions();
8918 assert_eq!(one.contact_constraints.len(), 1);
8919 assert!(!one.islands.is_sleeping(0));
8921 }
8922
8923 #[test]
8924 fn detect_collisions_needs_two_collidable_bodies() {
8925 let mut single = quiet_world();
8927 single.add_body_with_radius(
8928 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE),
8929 Fix128::ONE,
8930 );
8931 single.detect_collisions();
8932 assert!(single.contact_constraints.is_empty());
8933
8934 let mut one_radius = quiet_world();
8935 one_radius.add_body_with_radius(
8936 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE),
8937 Fix128::ONE,
8938 );
8939 one_radius.add_body(RigidBody::new_dynamic(
8940 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO),
8941 Fix128::ONE,
8942 ));
8943 one_radius.detect_collisions();
8944 assert!(one_radius.contact_constraints.is_empty());
8945 }
8946
8947 #[test]
8948 fn detect_collisions_uses_combined_material_of_pair() {
8949 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
8950 world.detect_collisions();
8951 let c = world.contact_constraints[0];
8952 let combined = world.combined_material(0, 1);
8953 assert_eq!(c.friction, combined.friction);
8954 assert_eq!(c.restitution, combined.restitution);
8955 }
8956
8957 #[test]
8960 fn solve_distance_constraints_rigid_splits_error_by_inverse_mass() {
8961 let mut world = quiet_world();
8964 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
8965 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
8966 world.bodies[b].inv_mass = Fix128::from_int(3);
8967 let mut c = dc(a, b);
8968 c.target_distance = Fix128::from_int(2);
8969 world.add_distance_constraint(c);
8970
8971 world.solve_distance_constraints(r(1, 4));
8972
8973 assert_eq!(
8974 world.bodies[a].position,
8975 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
8976 );
8977 assert_eq!(
8978 world.bodies[b].position,
8979 Vec3Fix::new(r(5, 2), Fix128::ZERO, Fix128::ZERO)
8980 );
8981 assert_eq!(world.distance_constraints[0].cached_lambda, r(1, 2));
8982 }
8983
8984 #[test]
8985 fn solve_distance_constraints_static_side_does_not_move() {
8986 let mut world = quiet_world();
8988 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
8989 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
8990 world.add_distance_constraint(dc(a, b)); world.solve_distance_constraints(r(1, 4));
8993
8994 assert_eq!(world.bodies[a].position, Vec3Fix::ZERO);
8995 assert_eq!(world.bodies[b].position, v3(1, 0, 0));
8996 }
8997
8998 #[test]
8999 fn solve_distance_constraints_negative_error_pushes_apart() {
9000 let mut world = quiet_world();
9002 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9003 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
9004 let mut c = dc(a, b);
9005 c.target_distance = Fix128::from_int(3);
9006 world.add_distance_constraint(c);
9007
9008 world.solve_distance_constraints(r(1, 4));
9009
9010 assert_eq!(world.bodies[a].position, v3(-1, 0, 0));
9011 assert_eq!(world.bodies[b].position, v3(2, 0, 0));
9012 }
9013
9014 #[test]
9015 fn solve_distance_constraints_compliance_softens_correction() {
9016 let mut world = quiet_world();
9019 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9020 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
9021 let mut c = dc(a, b);
9022 c.target_distance = Fix128::from_int(2);
9023 c.compliance = r(1, 16);
9024 world.add_distance_constraint(c);
9025
9026 world.solve_distance_constraints(r(1, 4));
9027
9028 assert_eq!(world.bodies[b].position, v3(3, 0, 0));
9029 assert_eq!(world.distance_constraints[0].cached_lambda, Fix128::ONE);
9030 }
9031
9032 #[test]
9033 fn solve_distance_constraints_accumulates_lambda_xpbd() {
9034 let mut world = quiet_world();
9038 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9039 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
9040 let mut c = dc(a, b);
9041 c.target_distance = Fix128::from_int(2);
9042 c.compliance = r(1, 16);
9043 c.cached_lambda = Fix128::ONE;
9044 world.add_distance_constraint(c);
9045
9046 world.solve_distance_constraints(r(1, 4));
9047
9048 assert_eq!(
9049 world.bodies[b].position,
9050 Vec3Fix::new(r(7, 2), Fix128::ZERO, Fix128::ZERO)
9051 );
9052 assert_eq!(world.distance_constraints[0].cached_lambda, r(3, 2));
9053 }
9054
9055 #[test]
9056 fn reset_lambdas_zeroes_distance_lambda() {
9057 let mut world = quiet_world();
9058 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9059 let b = world.add_body(RigidBody::new_dynamic(v3(4, 0, 0), Fix128::ONE));
9060 let mut c = dc(a, b);
9061 c.cached_lambda = Fix128::from_int(3);
9062 world.add_distance_constraint(c);
9063 world.reset_lambdas();
9064 assert_eq!(world.distance_constraints[0].cached_lambda, Fix128::ZERO);
9065 }
9066
9067 #[test]
9068 fn xpbd_compliant_extension_is_iteration_independent() {
9069 let run = |iters: usize| {
9072 let mut world = quiet_world();
9073 world.config.gravity = v3(0, -10, 0);
9074 world.config.damping = Fix128::ONE;
9075 world.config.substeps = 4;
9076 world.config.iterations = iters;
9077 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9078 let b = world.add_body(RigidBody::new_dynamic(v3(0, -1, 0), Fix128::ONE));
9079 world.bodies[b].linear_damping = r(9, 10); let mut c = dc(a, b);
9081 c.target_distance = Fix128::ONE;
9082 c.compliance = r(1, 100);
9083 world.add_distance_constraint(c);
9084 for _ in 0..600 {
9085 world.step(r(1, 60));
9086 }
9087 (-world.bodies[b].position.y - Fix128::ONE).to_f64()
9088 };
9089 let ext: Vec<f64> = [1usize, 2, 4, 8, 16].iter().map(|&i| run(i)).collect();
9090 for (i, e) in ext.iter().enumerate() {
9091 assert!(
9092 (e - 0.1).abs() < 0.01,
9093 "iterations index {i}: extension {e} m, expected 0.1 ± 0.01 (all: {ext:?})"
9094 );
9095 }
9096 }
9097
9098 #[test]
9099 fn solve_distance_constraints_anchor_offsets_are_rotated_into_world() {
9100 let mut world = quiet_world();
9103 let a = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9104 let b = world.add_body(RigidBody::new_dynamic(v3(0, 2, 0), Fix128::ONE));
9105 world.bodies[b].rotation =
9106 QuatFix::new(Fix128::ZERO, Fix128::ZERO, Fix128::ONE, Fix128::ZERO);
9107 let mut c = dc(a, b);
9108 c.local_anchor_b = v3(0, 1, 0);
9109 world.add_distance_constraint(c);
9110
9111 world.solve_distance_constraints(r(1, 4));
9112
9113 assert_eq!(world.bodies[b].position, v3(0, 2, 0));
9114 let mut unrotated = quiet_world();
9116 let a2 = unrotated.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9117 let b2 = unrotated.add_body(RigidBody::new_dynamic(v3(0, 2, 0), Fix128::ONE));
9118 let mut c2 = dc(a2, b2);
9119 c2.local_anchor_b = v3(0, 1, 0);
9120 unrotated.add_distance_constraint(c2);
9121 unrotated.solve_distance_constraints(r(1, 4));
9122 assert_near_vec(unrotated.bodies[b2].position, v3(0, 0, 0), "unrotated B");
9123 }
9125
9126 #[test]
9127 fn solve_distance_constraints_skips_coincident_and_double_static() {
9128 let mut world = quiet_world();
9129 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9130 let b = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE)); world.add_distance_constraint(dc(a, b));
9132 world.solve_distance_constraints(r(1, 4));
9133 assert_eq!(world.bodies[a].position, Vec3Fix::ZERO);
9134 assert_eq!(world.bodies[b].position, Vec3Fix::ZERO);
9135 assert_eq!(world.distance_constraints[0].cached_lambda, Fix128::ZERO);
9136
9137 let mut statics = quiet_world();
9138 let s1 = statics.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9139 let s2 = statics.add_body(RigidBody::new_static(v3(5, 0, 0)));
9140 statics.add_distance_constraint(dc(s1, s2));
9141 statics.solve_distance_constraints(r(1, 4));
9142 assert_eq!(statics.bodies[s2].position, v3(5, 0, 0));
9143 assert_eq!(statics.distance_constraints[0].cached_lambda, Fix128::ZERO);
9144 }
9145
9146 fn contact_world(inv_a: Fix128, inv_b: Fix128, depth: Fix128) -> PhysicsWorld {
9149 let mut world = quiet_world();
9150 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9151 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
9152 world.bodies[a].inv_mass = inv_a;
9153 world.bodies[b].inv_mass = inv_b;
9154 let contact = Contact {
9155 depth,
9156 normal: v3(1, 0, 0),
9157 point_a: Vec3Fix::ZERO,
9158 point_b: Vec3Fix::ZERO,
9159 };
9160 world
9161 .contact_constraints
9162 .push(ContactConstraint::new(a, b, contact));
9163 world
9164 }
9165
9166 #[test]
9167 fn solve_contact_constraints_pushes_apart_by_depth_weighted_by_inverse_mass() {
9168 let mut world = contact_world(Fix128::ONE, Fix128::from_int(3), Fix128::ONE);
9170 world.solve_contact_constraints(r(1, 4));
9171 assert_eq!(
9172 world.bodies[0].position,
9173 Vec3Fix::new(r(1, 4), Fix128::ZERO, Fix128::ZERO)
9174 );
9175 assert_eq!(
9176 world.bodies[1].position,
9177 Vec3Fix::new(r(1, 4), Fix128::ZERO, Fix128::ZERO)
9178 );
9179 assert_eq!(world.contact_constraints[0].cached_lambda, Fix128::ONE);
9180 }
9181
9182 #[test]
9183 fn solve_contact_constraints_zero_or_negative_depth_is_skipped() {
9184 for depth in [Fix128::ZERO, -r(1, 2)] {
9185 let mut world = contact_world(Fix128::ONE, Fix128::ONE, depth);
9186 world.solve_contact_constraints(r(1, 4));
9187 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO, "depth {depth:?}");
9188 assert_eq!(world.bodies[1].position, v3(1, 0, 0), "depth {depth:?}");
9189 assert_eq!(world.contact_constraints[0].cached_lambda, Fix128::ZERO);
9190 }
9191 }
9192
9193 #[test]
9194 fn solve_contact_constraints_static_side_absorbs_nothing() {
9195 let mut world = contact_world(Fix128::ZERO, Fix128::ONE, r(1, 2));
9197 world.solve_contact_constraints(r(1, 4));
9198 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO);
9199 assert_eq!(
9200 world.bodies[1].position,
9201 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9202 );
9203 let mut both = contact_world(Fix128::ZERO, Fix128::ZERO, r(1, 2));
9205 both.solve_contact_constraints(r(1, 4));
9206 assert_eq!(both.bodies[1].position, v3(1, 0, 0));
9207 assert_eq!(both.contact_constraints[0].cached_lambda, Fix128::ZERO);
9208 }
9209
9210 #[test]
9211 fn solve_contact_constraints_accumulates_lambda_and_pushes_depth_once() {
9212 let mut world = contact_world(Fix128::ONE, Fix128::ONE, r(1, 2));
9214 world.contact_constraints[0].cached_lambda = Fix128::ONE;
9215 world.solve_contact_constraints(r(1, 4));
9216 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO);
9217 assert_eq!(world.contact_constraints[0].cached_lambda, Fix128::ONE);
9218
9219 let mut edge = contact_world(Fix128::ONE, Fix128::ONE, r(1, 2));
9221 edge.contact_constraints[0].cached_lambda = r(1, 2);
9222 edge.solve_contact_constraints(r(1, 4));
9223 assert_eq!(edge.bodies[0].position, Vec3Fix::ZERO);
9224
9225 let mut partial = contact_world(Fix128::ONE, Fix128::ONE, r(1, 2));
9227 partial.contact_constraints[0].cached_lambda = r(1, 4);
9228 partial.solve_contact_constraints(r(1, 4));
9229 assert_eq!(
9230 partial.bodies[0].position,
9231 Vec3Fix::new(r(1, 8), Fix128::ZERO, Fix128::ZERO)
9232 );
9233 assert_eq!(partial.contact_constraints[0].cached_lambda, r(1, 2));
9234
9235 let mut iters = contact_world(Fix128::ONE, Fix128::ONE, r(1, 2));
9237 for _ in 0..4 {
9238 iters.solve_contact_constraints(r(1, 4));
9239 }
9240 assert_eq!(
9241 iters.bodies[0].position,
9242 Vec3Fix::new(r(1, 4), Fix128::ZERO, Fix128::ZERO)
9243 );
9244 assert_eq!(
9245 iters.bodies[1].position,
9246 Vec3Fix::new(r(3, 4), Fix128::ZERO, Fix128::ZERO)
9247 );
9248 }
9249
9250 #[test]
9251 fn solve_contact_constraints_sensor_skips_response() {
9252 for side in 0..2 {
9253 let mut world = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9254 world.bodies[side].is_sensor = true;
9255 world.solve_contact_constraints(r(1, 4));
9256 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO, "side {side}");
9257 assert_eq!(world.bodies[1].position, v3(1, 0, 0), "side {side}");
9258 }
9259 }
9260
9261 #[cfg(feature = "std")]
9262 #[test]
9263 fn solve_contact_constraints_pre_solve_hook_can_discard_and_modifier_can_rescale() {
9264 let mut vetoed = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9266 vetoed.add_pre_solve_hook(Box::new(|_a, _b, _c| false));
9267 vetoed.apply_contact_filters();
9268 vetoed.solve_contact_constraints(r(1, 4));
9269 assert_eq!(vetoed.bodies[0].position, Vec3Fix::ZERO);
9270
9271 let mut allowed = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9273 allowed.add_pre_solve_hook(Box::new(|_a, _b, _c| true));
9274 allowed.apply_contact_filters();
9275 allowed.solve_contact_constraints(r(1, 4));
9276 assert_eq!(
9277 allowed.bodies[0].position,
9278 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9279 );
9280
9281 struct Halve;
9283 impl ContactModifier for Halve {
9284 fn modify_contact(
9285 &self,
9286 _a: usize,
9287 _b: usize,
9288 c: &mut Contact,
9289 _f: &mut Fix128,
9290 _r: &mut Fix128,
9291 ) -> bool {
9292 c.depth = c.depth.half();
9293 true
9294 }
9295 }
9296 let mut halved = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9297 halved.add_contact_modifier(Box::new(Halve));
9298 halved.apply_contact_filters();
9299 halved.solve_contact_constraints(r(1, 4));
9300 assert_eq!(
9301 halved.bodies[0].position,
9302 Vec3Fix::new(r(1, 4), Fix128::ZERO, Fix128::ZERO)
9303 );
9304
9305 struct Discard;
9307 impl ContactModifier for Discard {
9308 fn modify_contact(
9309 &self,
9310 _a: usize,
9311 _b: usize,
9312 _c: &mut Contact,
9313 _f: &mut Fix128,
9314 _r: &mut Fix128,
9315 ) -> bool {
9316 false
9317 }
9318 }
9319 let mut discarded = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9320 discarded.add_contact_modifier(Box::new(Discard));
9321 discarded.apply_contact_filters();
9322 discarded.solve_contact_constraints(r(1, 4));
9323 assert_eq!(discarded.bodies[0].position, Vec3Fix::ZERO);
9324 }
9325
9326 fn loaded_body() -> RigidBody {
9330 let mut b = RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE);
9331 b.inv_mass = Fix128::from_int(2);
9332 b.inv_inertia = v3(1, 2, 4);
9333 b.velocity = v3(10, 20, 30);
9334 b.angular_velocity = v3(-1, -2, -3);
9335 b
9336 }
9337
9338 #[test]
9339 fn add_force_scales_by_inverse_mass_and_dt() {
9340 let mut b = loaded_body();
9341 b.add_force(v3(4, -8, 16), r(1, 4)); assert_eq!(b.velocity, v3(12, 16, 38));
9343 assert_eq!(b.angular_velocity, v3(-1, -2, -3));
9344 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9345 st.add_force(v3(4, 4, 4), Fix128::ONE);
9346 assert_eq!(st.velocity, Vec3Fix::ZERO);
9347 }
9348
9349 #[test]
9350 fn add_torque_scales_each_axis_by_inverse_inertia_and_dt() {
9351 let mut b = loaded_body();
9352 b.add_torque(v3(8, 8, 8), r(1, 4)); assert_eq!(b.angular_velocity, v3(1, 2, 5));
9354 assert_eq!(b.velocity, v3(10, 20, 30));
9355 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9356 st.add_torque(v3(8, 8, 8), Fix128::ONE);
9357 assert_eq!(st.angular_velocity, Vec3Fix::ZERO);
9358 }
9359
9360 #[test]
9361 fn apply_impulse_scales_by_inverse_mass_only() {
9362 let mut b = loaded_body();
9363 b.apply_impulse(v3(1, 2, 3)); assert_eq!(b.velocity, v3(12, 24, 36));
9365 assert_eq!(b.angular_velocity, v3(-1, -2, -3));
9366 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9367 st.apply_impulse(v3(1, 1, 1));
9368 assert_eq!(st.velocity, Vec3Fix::ZERO);
9369 }
9370
9371 #[test]
9372 fn apply_impulse_at_adds_lever_arm_torque() {
9373 let mut b = loaded_body();
9376 b.apply_impulse_at(v3(0, 3, 0), v3(2, 2, 3));
9377 assert_eq!(b.velocity, v3(10, 26, 30));
9378 assert_eq!(b.angular_velocity, v3(-1, -2, 9));
9379
9380 let mut c = loaded_body();
9382 c.apply_impulse_at(v3(0, 0, 5), v3(1, 3, 3));
9383 assert_eq!(c.angular_velocity, v3(4, -2, -3));
9384 assert_eq!(c.velocity, v3(10, 20, 40));
9385
9386 let mut d = loaded_body();
9388 d.apply_impulse_at(v3(7, 0, 0), v3(1, 2, 4));
9389 assert_eq!(d.angular_velocity, v3(-1, 12, -3));
9390
9391 let mut e = loaded_body();
9393 e.apply_impulse_at(v3(7, 0, 0), v3(1, 2, 3));
9394 assert_eq!(e.angular_velocity, v3(-1, -2, -3));
9395 assert_eq!(e.velocity, v3(24, 20, 30));
9396
9397 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9398 st.apply_impulse_at(v3(1, 1, 1), v3(1, 0, 0));
9399 assert_eq!(st.velocity, Vec3Fix::ZERO);
9400 assert_eq!(st.angular_velocity, Vec3Fix::ZERO);
9401 }
9402
9403 fn snapshot_world() -> PhysicsWorld {
9406 let mut world = quiet_world();
9407 let mut a = RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE);
9408 a.velocity = v3(4, 5, 6);
9409 a.angular_velocity = v3(7, 8, 9);
9410 a.rotation = QuatFix::from_axis_angle(v3(1, 1, 0), r(1, 3));
9411 world.add_body(a);
9412 let mut b = RigidBody::new_dynamic(v3(-1, -2, -3), Fix128::ONE);
9413 b.velocity = Vec3Fix::new(r(1, 3), r(-2, 7), r(5, 11));
9414 world.add_body(b);
9415 world
9416 }
9417
9418 #[test]
9419 fn deserialize_state_roundtrip_restores_every_field_bit_exact() {
9420 let src = snapshot_world();
9421 let bytes = src.serialize_state();
9422 assert_eq!(bytes.len(), 12 + 2 * 208 + 2 * 5 + 1 + 8);
9424 let mut dst = quiet_world();
9425 dst.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9426 dst.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9427 assert!(dst.deserialize_state(&bytes));
9428 for i in 0..2 {
9429 assert_eq!(dst.bodies[i].position, src.bodies[i].position, "pos {i}");
9430 assert_eq!(dst.bodies[i].velocity, src.bodies[i].velocity, "vel {i}");
9431 assert_eq!(dst.bodies[i].rotation, src.bodies[i].rotation, "rot {i}");
9432 assert_eq!(
9433 dst.bodies[i].angular_velocity, src.bodies[i].angular_velocity,
9434 "ang {i}"
9435 );
9436 }
9437 assert_eq!(dst.body_collision_radii.len(), 2);
9439 assert_eq!(dst.body_filters.len(), 2);
9440 assert_eq!(dst.body_materials.len(), 2);
9441 }
9442
9443 #[test]
9444 fn deserialize_state_every_single_byte_flip_changes_some_field() {
9445 let src = snapshot_world();
9455 let bytes = src.serialize_state();
9456 let body_end = 12 + 2 * 208;
9457 let flag_pos = bytes.len() - 9;
9458
9459 for pos in 0..12 {
9461 let mut bad = bytes.clone();
9462 bad[pos] ^= 0x01;
9463 let mut dst = snapshot_world();
9464 assert!(
9465 !dst.deserialize_state(&bad),
9466 "header byte {pos} を壊しても受け付けている (silent に無視される byte がある)"
9467 );
9468 }
9469
9470 {
9475 let pos = flag_pos;
9476 let mut bad = bytes.clone();
9477 bad[pos] ^= 0x01;
9478 let mut dst = snapshot_world();
9479 assert!(dst.deserialize_state(&bad), "flag byte {pos}");
9480 assert_ne!(
9481 dst.overflow_detected(),
9482 src.overflow_detected(),
9483 "flag byte {pos} を壊しても overflow_detected が変わらない"
9484 );
9485 }
9486
9487 for pos in bytes.len() - 8..bytes.len() {
9491 let mut bad = bytes.clone();
9492 bad[pos] ^= 0x01;
9493 let mut dst = snapshot_world();
9494 assert!(
9495 !dst.deserialize_state(&bad),
9496 "fingerprint byte {pos} を壊しても受け付けている"
9497 );
9498 assert_eq!(dst.bodies[0].position, src.bodies[0].position);
9500 }
9501
9502 for pos in body_end..flag_pos {
9504 let mut bad = bytes.clone();
9505 bad[pos] ^= 0x01;
9506 let mut dst = snapshot_world();
9507 assert!(dst.deserialize_state(&bad), "sleep byte {pos}");
9508 let i = (pos - body_end) / 5;
9509 assert_ne!(
9510 dst.islands.sleep_data[i], src.islands.sleep_data[i],
9511 "sleep byte {pos} は body {i} の sleep_data を変えるべき"
9512 );
9513 }
9514
9515 for pos in 12..body_end {
9517 let mut bad = bytes.clone();
9518 bad[pos] ^= 0x01;
9519 let mut dst = snapshot_world();
9520 assert!(dst.deserialize_state(&bad), "byte {pos}");
9521 let body = (pos - 12) / 208;
9522 let field = ((pos - 12) % 208) / 48; let changed_body = (0..2)
9524 .filter(|&i| {
9525 let (s, d) = (&src.bodies[i], &dst.bodies[i]);
9526 s.position != d.position
9527 || s.velocity != d.velocity
9528 || s.rotation != d.rotation
9529 || s.angular_velocity != d.angular_velocity
9530 })
9531 .collect::<Vec<_>>();
9532 assert_eq!(
9533 changed_body,
9534 vec![body],
9535 "byte {pos} must change only body {body}"
9536 );
9537 let (s, d) = (&src.bodies[body], &dst.bodies[body]);
9538 let off = (pos - 12) % 208;
9539 match off {
9540 0..=47 => assert!(
9541 s.position != d.position && s.velocity == d.velocity,
9542 "byte {pos} → position"
9543 ),
9544 48..=95 => assert!(
9545 s.velocity != d.velocity && s.position == d.position,
9546 "byte {pos} → velocity"
9547 ),
9548 96..=159 => assert!(
9549 s.rotation != d.rotation && s.angular_velocity == d.angular_velocity,
9550 "byte {pos} → rotation"
9551 ),
9552 _ => assert!(
9553 s.angular_velocity != d.angular_velocity && s.rotation == d.rotation,
9554 "byte {pos} → angular"
9555 ),
9556 }
9557 let _ = field;
9558 }
9559 }
9560
9561 #[test]
9562 fn deserialize_state_rejects_short_or_mismatched_input() {
9563 let src = snapshot_world();
9564 let bytes = src.serialize_state();
9565 let mut dst = snapshot_world();
9566 assert!(!dst.deserialize_state(&bytes[..11])); assert!(!dst.deserialize_state(&bytes[..12 + 208 + 100])); assert!(
9569 !dst.deserialize_state(&bytes[..bytes.len() - 1]),
9570 "末尾 (v3 の fingerprint) が 1 byte 欠けても拒否する (長さ検査が body 数から一意に決まる)"
9571 );
9572 let mut legacy = 2u32.to_le_bytes().to_vec();
9575 legacy.extend_from_slice(&bytes[12..12 + 2 * 208]);
9576 assert!(
9577 !dst.deserialize_state(&legacy),
9578 "旧 format の blob は magic 不一致で拒否されるべき"
9579 );
9580 let mut v1 = bytes.clone();
9583 v1[4] = 1;
9584 v1.truncate(v1.len() - 1);
9585 assert!(
9586 !dst.deserialize_state(&v1),
9587 "v1 blob は version 不一致で拒否されるべき"
9588 );
9589 let mut one_body = quiet_world();
9590 one_body.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9591 assert!(!one_body.deserialize_state(&bytes)); assert_eq!(one_body.bodies[0].position, Vec3Fix::ZERO);
9594 let mut exact = quiet_world();
9596 exact.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9597 let mut one = PhysicsWorld::STATE_MAGIC.to_vec();
9598 one.extend_from_slice(&PhysicsWorld::STATE_VERSION.to_le_bytes());
9599 one.extend_from_slice(&0u16.to_le_bytes());
9600 one.extend_from_slice(&1u32.to_le_bytes());
9601 one.extend_from_slice(&bytes[12..12 + 208]);
9602 one.extend_from_slice(&bytes[12 + 2 * 208..12 + 2 * 208 + 5]);
9603 one.push(0); one.extend_from_slice(&exact.population_fingerprint().to_le_bytes()); assert!(exact.deserialize_state(&one));
9606 assert_eq!(exact.bodies[0].position, v3(1, 2, 3));
9607 }
9608
9609 #[test]
9612 fn integrate_positions_applies_gravity_damping_and_predicts_position() {
9613 let mut world = quiet_world();
9614 world.config.gravity = v3(0, -8, 0);
9615 world.config.damping = r(1, 2);
9616 let i = world.add_body(RigidBody::new_dynamic(v3(1, 1, 1), Fix128::ONE));
9617 world.bodies[i].velocity = v3(4, 0, 0);
9618 world.bodies[i].gravity_scale = r(1, 2);
9619 world.bodies[i].linear_damping = r(1, 2);
9620 let dt = r(1, 4);
9621
9622 world.integrate_positions(dt);
9623
9624 let b = &world.bodies[i];
9627 assert_eq!(
9628 b.velocity,
9629 Vec3Fix::new(Fix128::from_int(4), -Fix128::ONE, Fix128::ZERO)
9630 );
9631 assert_eq!(
9632 b.position,
9633 Vec3Fix::new(Fix128::from_int(2), r(3, 4), Fix128::ONE)
9634 );
9635 assert_eq!(b.prev_position, v3(1, 1, 1));
9636
9637 world.apply_frame_damping();
9639 assert_eq!(
9640 world.bodies[i].velocity,
9641 Vec3Fix::new(Fix128::ONE, -r(1, 4), Fix128::ZERO)
9642 );
9643 }
9644
9645 #[test]
9646 fn integrate_positions_angular_damping_and_rotation_prediction() {
9647 let mut world = quiet_world();
9648 world.config.damping = Fix128::ONE;
9649 let i = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9650 world.bodies[i].angular_velocity = v3(0, 0, 2);
9651 world.bodies[i].angular_damping = r(1, 2); let dt = r(1, 4);
9653
9654 world.integrate_positions(dt);
9655
9656 let b = &world.bodies[i];
9657 assert_eq!(b.angular_velocity, v3(0, 0, 2));
9659 let expected = QuatFix::from_axis_angle(v3(0, 0, 1), r(1, 2))
9661 .mul(QuatFix::IDENTITY)
9662 .normalize();
9663 assert_eq!(b.rotation, expected);
9664 assert_eq!(b.prev_rotation, QuatFix::IDENTITY);
9665 assert!(b.rotation != QuatFix::IDENTITY);
9666 world.apply_frame_damping();
9667 assert_eq!(world.bodies[i].angular_velocity, v3(0, 0, 1));
9668 }
9669
9670 #[test]
9671 fn apply_frame_damping_skips_static_kinematic_and_sleeping() {
9672 let mut world = quiet_world();
9673 world.config.damping = r(1, 2);
9674 let st = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
9675 let mut kin = RigidBody::new_static(Vec3Fix::ZERO);
9676 kin.body_type = BodyType::Kinematic;
9677 let ki = world.add_body(kin);
9678 let dy = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9679 for &i in &[st, ki, dy] {
9680 world.bodies[i].velocity = v3(2, 0, 0);
9681 world.bodies[i].angular_velocity = v3(0, 4, 0);
9682 }
9683 world.apply_frame_damping();
9684 assert_eq!(world.bodies[st].velocity, v3(2, 0, 0));
9685 assert_eq!(world.bodies[ki].velocity, v3(2, 0, 0));
9686 assert_eq!(world.bodies[dy].velocity, v3(1, 0, 0));
9687 assert_eq!(world.bodies[dy].angular_velocity, v3(0, 2, 0));
9688 }
9689
9690 #[test]
9691 fn free_fall_is_substep_independent_with_frame_damping() {
9692 let fall = |substeps: usize| {
9695 let mut world = quiet_world();
9696 world.config.gravity = v3(0, -10, 0);
9697 world.config.damping = Fix128::ONE;
9698 world.config.substeps = substeps;
9699 let b = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9700 for _ in 0..60 {
9701 world.step(r(1, 60));
9702 }
9703 world.bodies[b].position.y.to_f64()
9704 };
9705 let ys: Vec<f64> = [1usize, 2, 4, 8, 16].iter().map(|&n| fall(n)).collect();
9706 for y in &ys {
9707 assert!(
9709 (y + 5.0).abs() < 0.1,
9710 "free fall y = {y}, expected -5.0 ± 0.1 (all: {ys:?})"
9711 );
9712 }
9713 let mut world = PhysicsWorld::new(PhysicsConfig::default());
9715 let b = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9716 for _ in 0..60 {
9717 world.step(r(1, 60));
9718 }
9719 let y = world.bodies[b].position.y.to_f64();
9720 assert!(y < -3.5, "default config fell only to y = {y}");
9721 }
9722
9723 #[test]
9724 fn integrate_positions_static_kinematic_and_sleeping_paths() {
9725 let mut world = quiet_world();
9726 world.config.gravity = v3(0, -8, 0);
9727 let st = world.add_body(RigidBody::new_static(v3(0, 5, 0)));
9728 let mut kin = RigidBody::new_static(v3(0, 0, 0));
9729 kin.body_type = BodyType::Kinematic;
9730 kin.set_kinematic_target(v3(2, 0, 0), QuatFix::IDENTITY);
9731 let ki = world.add_body(kin);
9732 let sl = world.add_body(RigidBody::new_dynamic(v3(9, 9, 9), Fix128::ONE));
9733 world.islands.sleep_data[sl].state = crate::sleeping::SleepState::Sleeping;
9734 let dt = r(1, 4);
9735
9736 world.integrate_positions(dt);
9737
9738 assert_eq!(world.bodies[st].position, v3(0, 5, 0));
9740 assert_eq!(world.bodies[st].velocity, Vec3Fix::ZERO);
9741 assert_eq!(world.bodies[ki].position, v3(2, 0, 0));
9743 assert_eq!(world.bodies[ki].velocity, v3(8, 0, 0));
9744 assert_eq!(world.bodies[ki].prev_position, Vec3Fix::ZERO);
9745 assert_eq!(world.bodies[sl].position, v3(9, 9, 9));
9747 assert_eq!(world.bodies[sl].prev_position, v3(9, 9, 9));
9748 assert_eq!(world.bodies[sl].velocity, Vec3Fix::ZERO);
9749 }
9750
9751 #[test]
9754 fn step_free_fall_matches_closed_form_over_substeps() {
9755 let mut world = quiet_world();
9758 world.config.gravity = v3(0, -8, 0);
9759 world.config.damping = Fix128::ONE;
9760 world.config.substeps = 4;
9761 world.config.iterations = 1;
9762 let i = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9763
9764 world.step(Fix128::ONE);
9765
9766 assert_eq!(world.bodies[i].velocity, v3(0, -8, 0));
9767 assert_eq!(world.bodies[i].position, v3(0, -5, 0));
9768 let mut single = quiet_world();
9770 single.config.gravity = v3(0, -8, 0);
9771 single.config.damping = Fix128::ONE;
9772 single.config.substeps = 1;
9773 let j = single.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9774 single.step(Fix128::ONE);
9775 assert_eq!(single.bodies[j].position, v3(0, -8, 0));
9776 }
9777
9778 #[test]
9781 fn combined_material_uses_registered_pair_override_and_ignores_out_of_range() {
9782 let mut world = quiet_world();
9783 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9784 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
9785 let metal = world.material_table.register_metal();
9786 world
9787 .material_table
9788 .set_pair_override(0, metal, r(1, 8), r(7, 8));
9789 world.set_body_material(b, metal);
9790 let c = world.combined_material(a, b);
9791 assert_eq!((c.friction, c.restitution), (r(1, 8), r(7, 8)));
9792 let c2 = world.combined_material(b, a);
9794 assert_eq!((c2.friction, c2.restitution), (r(1, 8), r(7, 8)));
9795 let d = world.combined_material(a, 99);
9797 let dd = world.material_table.combine(0, 0);
9798 assert_eq!((d.friction, d.restitution), (dd.friction, dd.restitution));
9799 world.set_body_material(99, metal);
9801 assert_eq!(world.body_materials.len(), 2);
9802 }
9803
9804 #[test]
9805 fn body_setters_ignore_out_of_range_and_apply_in_range() {
9806 let mut world = quiet_world();
9807 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9808 world.set_body_collision_radius(a, r(3, 2));
9809 assert_eq!(world.body_collision_radii[a], Some(r(3, 2)));
9810 world.set_body_collision_radius(5, Fix128::ONE);
9811 assert_eq!(world.body_collision_radii.len(), 1);
9812 world.clear_body_collision_radius(5);
9813 assert_eq!(world.body_collision_radii[a], Some(r(3, 2)));
9814 world.clear_body_collision_radius(a);
9815 assert_eq!(world.body_collision_radii[a], None);
9816
9817 let f = CollisionFilter {
9818 layer: 4,
9819 mask: 8,
9820 group: 2,
9821 };
9822 world.set_body_filter(a, f);
9823 assert_eq!(world.body_filters[a], f);
9824 world.set_body_filter(5, CollisionFilter::DEFAULT);
9825 assert_eq!(world.body_filters[a], f);
9826 }
9827
9828 #[test]
9829 fn active_body_count_excludes_sleeping() {
9830 let mut world = quiet_world();
9831 for i in 0..3 {
9832 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
9833 }
9834 assert_eq!(world.active_body_count(), 3);
9835 world.islands.sleep_data[1].state = crate::sleeping::SleepState::Sleeping;
9836 assert_eq!(world.active_body_count(), 2);
9837 world.islands.sleep_data[0].state = crate::sleeping::SleepState::Sleeping;
9838 world.islands.sleep_data[2].state = crate::sleeping::SleepState::Sleeping;
9839 assert_eq!(world.active_body_count(), 0);
9840 }
9841
9842 #[cfg(feature = "std")]
9845 #[test]
9846 fn clear_pre_solve_hooks_restores_normal_contact_response() {
9847 let mut world = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9849 world.add_pre_solve_hook(Box::new(|_a, _b, _c| false));
9850 world.add_pre_solve_hook(Box::new(|_a, _b, _c| false));
9851 assert_eq!(world.pre_solve_hooks.len(), 2);
9852 world.apply_contact_filters();
9853 world.solve_contact_constraints(r(1, 4));
9854 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO);
9855
9856 world.clear_pre_solve_hooks();
9858 assert!(world.pre_solve_hooks.is_empty());
9859 world.apply_contact_filters();
9860 world.solve_contact_constraints(r(1, 4));
9861 assert_eq!(
9862 world.bodies[0].position,
9863 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9864 );
9865 assert_eq!(
9866 world.bodies[1].position,
9867 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9868 );
9869 }
9870
9871 #[cfg(feature = "std")]
9872 #[test]
9873 fn clear_contact_modifiers_restores_normal_contact_response() {
9874 struct Discard;
9875 impl ContactModifier for Discard {
9876 fn modify_contact(
9877 &self,
9878 _a: usize,
9879 _b: usize,
9880 _c: &mut Contact,
9881 _f: &mut Fix128,
9882 _r: &mut Fix128,
9883 ) -> bool {
9884 false
9885 }
9886 }
9887 let mut world = contact_world(Fix128::ONE, Fix128::ONE, Fix128::ONE);
9888 world.add_contact_modifier(Box::new(Discard));
9889 assert_eq!(world.contact_modifiers.len(), 1);
9890 world.apply_contact_filters();
9891 world.solve_contact_constraints(r(1, 4));
9892 assert_eq!(world.bodies[0].position, Vec3Fix::ZERO);
9893
9894 world.clear_contact_modifiers();
9895 assert!(world.contact_modifiers.is_empty());
9896 world.apply_contact_filters();
9897 world.solve_contact_constraints(r(1, 4));
9898 assert_eq!(
9899 world.bodies[0].position,
9900 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
9901 );
9902 }
9903
9904 #[test]
9905 fn set_angular_velocity_overwrites_and_drives_rotation_prediction() {
9906 let mut world = quiet_world();
9907 let i = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
9908 world.bodies[i].angular_velocity = v3(5, 5, 5);
9909 world.bodies[i].set_angular_velocity(v3(0, 0, 2));
9910 assert_eq!(world.bodies[i].angular_velocity, v3(0, 0, 2));
9911 let mut st = RigidBody::new_static(Vec3Fix::ZERO);
9913 st.set_angular_velocity(v3(1, 0, 0));
9914 assert_eq!(st.angular_velocity, v3(1, 0, 0));
9915
9916 world.integrate_positions(r(1, 4));
9918 let expected = QuatFix::from_axis_angle(v3(0, 0, 1), r(1, 2))
9919 .mul(QuatFix::IDENTITY)
9920 .normalize();
9921 assert_eq!(world.bodies[i].rotation, expected);
9922 }
9923
9924 #[test]
9925 fn with_angular_damping_is_stored_and_applied_once_per_frame_by_step() {
9926 let body = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_angular_damping(r(1, 2));
9927 assert_eq!(body.angular_damping, r(1, 2));
9928 assert_eq!(body.linear_damping, Fix128::ONE);
9929
9930 for substeps in [1usize, 4] {
9933 let mut undamped = quiet_world();
9934 undamped.config.substeps = substeps;
9935 let u = undamped.add_body(
9936 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_velocity(v3(3, 0, 0)),
9937 );
9938 undamped.bodies[u].set_angular_velocity(v3(0, 0, 2));
9939
9940 let mut damped = quiet_world();
9941 damped.config.substeps = substeps;
9942 let d = damped.add_body(
9943 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE)
9944 .with_velocity(v3(3, 0, 0))
9945 .with_angular_damping(r(1, 2)),
9946 );
9947 damped.bodies[d].set_angular_velocity(v3(0, 0, 2));
9948
9949 undamped.step(r(1, 4));
9950 damped.step(r(1, 4));
9951
9952 let expected = undamped.bodies[u].angular_velocity * Fix128::ONE * r(1, 2);
9953 assert_eq!(
9954 damped.bodies[d].angular_velocity, expected,
9955 "substeps {substeps}"
9956 );
9957 assert_eq!(
9959 damped.bodies[d].velocity, undamped.bodies[u].velocity,
9960 "substeps {substeps}"
9961 );
9962 let ratio = damped.bodies[d].angular_velocity.z.to_f64()
9963 / undamped.bodies[u].angular_velocity.z.to_f64();
9964 assert!(
9965 (ratio - 0.5).abs() < 1e-9,
9966 "substeps {substeps}: ratio {ratio}"
9967 );
9968 }
9969 }
9970
9971 #[test]
9972 fn with_sensor_stores_flag_and_sensor_body_skips_contact_response() {
9973 let sensor = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_sensor(true);
9974 assert!(sensor.is_sensor);
9975 assert!(!sensor.with_sensor(false).is_sensor);
9976
9977 let mut world = quiet_world();
9979 let a =
9980 world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_sensor(true));
9981 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
9982 let contact = Contact {
9983 depth: Fix128::ONE,
9984 normal: v3(1, 0, 0),
9985 point_a: Vec3Fix::ZERO,
9986 point_b: Vec3Fix::ZERO,
9987 };
9988 world
9989 .contact_constraints
9990 .push(ContactConstraint::new(a, b, contact));
9991 world.solve_contact_constraints(r(1, 4));
9992 assert_eq!(world.bodies[a].position, Vec3Fix::ZERO);
9993 assert_eq!(world.bodies[b].position, v3(1, 0, 0));
9994
9995 let mut auto = quiet_world();
9997 auto.add_body_with_radius(
9998 RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE).with_sensor(true),
9999 Fix128::ONE,
10000 );
10001 auto.add_body_with_radius(
10002 RigidBody::new_dynamic(
10003 Vec3Fix::new(r(3, 2), Fix128::ZERO, Fix128::ZERO),
10004 Fix128::ONE,
10005 ),
10006 Fix128::ONE,
10007 );
10008 auto.detect_collisions();
10009 assert!(auto.contact_constraints.is_empty());
10010 assert_eq!(auto.trigger_events().len(), 1);
10011 }
10012
10013 #[test]
10014 fn raycast_far_intersection_off_center_and_max_distance_cull_inside_aabb() {
10015 let world = sphere_world(&[v3(10, 0, 0)]);
10017 assert_eq!(
10018 world.raycast(v3(9, 0, 0), v3(1, 0, 0), Fix128::from_int(100)),
10019 Some((0, Fix128::from_int(3)))
10020 );
10021 let mut big = quiet_world();
10023 big.add_body_with_radius(RigidBody::new_static(v3(10, 0, 0)), Fix128::from_int(3));
10024 assert_eq!(big.raycast(v3(0, 2, 0), v3(1, 0, 0), r(15, 2)), None);
10025 assert!(big
10026 .raycast(v3(0, 2, 0), v3(1, 0, 0), Fix128::from_int(8))
10027 .is_some());
10028 }
10029
10030 #[test]
10038 fn rigid_body_setters_and_mass_speed_report_exact_values() {
10039 let mut body = RigidBody::new(v3(1, 1, 1), Fix128::from_int(4));
10040 assert_eq!(body.inv_mass, r(1, 4));
10041 assert_eq!(body.mass(), Fix128::from_int(4));
10042 assert_eq!(RigidBody::new_static(Vec3Fix::ZERO).mass(), Fix128::ZERO);
10043
10044 body.set_velocity(v3(3, -4, 0));
10045 assert_eq!(body.velocity, v3(3, -4, 0));
10046 assert_eq!(body.speed(), Fix128::from_int(5));
10047
10048 body.set_position(v3(7, -8, 9));
10049 assert_eq!(body.position, v3(7, -8, 9));
10050 assert_eq!(body.prev_position, v3(7, -8, 9));
10051
10052 let q = QuatFix::from_axis_angle(v3(0, 1, 0), r(1, 3));
10053 assert_ne!(q, QuatFix::IDENTITY);
10054 body.set_rotation(q);
10055 assert_eq!(body.rotation, q);
10056 assert_eq!(body.prev_rotation, q);
10057 }
10058
10059 #[test]
10064 fn apply_impulse_at_and_add_torque_multiply_x_by_inverse_inertia() {
10065 let mut body = RigidBody::new(v3(5, 5, 5), Fix128::ONE);
10066 body.inv_inertia = v3(4, 4, 4);
10067 body.apply_impulse_at(v3(0, 0, 2), v3(5, 6, 5));
10068 assert_eq!(body.velocity, v3(0, 0, 2));
10069 assert_eq!(body.angular_velocity, v3(8, 0, 0));
10070
10071 let mut spun = RigidBody::new(Vec3Fix::ZERO, Fix128::ONE);
10072 spun.inv_inertia = v3(4, 4, 4);
10073 spun.add_torque(v3(2, 0, 0), r(1, 2));
10074 assert_eq!(spun.angular_velocity, v3(4, 0, 0));
10075 }
10076
10077 #[test]
10083 fn body_count_and_get_body_accessors_track_the_body_vector() {
10084 let mut world = quiet_world();
10085 assert_eq!(world.body_count(), 0);
10086 assert!(world.get_body(0).is_none());
10087 assert!(world.get_body_mut(0).is_none());
10088
10089 world.add_body(RigidBody::new_dynamic(v3(3, 4, 5), Fix128::ONE));
10090 world.add_body(RigidBody::new_dynamic(v3(6, 7, 8), Fix128::ONE));
10091 assert_eq!(world.body_count(), 2);
10092 assert_eq!(world.get_body(0).map(|b| b.position), Some(v3(3, 4, 5)));
10093 assert_eq!(world.get_body(1).map(|b| b.position), Some(v3(6, 7, 8)));
10094 assert!(world.get_body(2).is_none());
10095
10096 world
10097 .get_body_mut(1)
10098 .expect("body 1 exists")
10099 .set_velocity(v3(-1, -2, -3));
10100 assert_eq!(world.bodies[1].velocity, v3(-1, -2, -3));
10101 assert!(world.get_body_mut(2).is_none());
10102
10103 assert!(world.remove_body(0).is_some());
10104 assert_eq!(world.body_count(), 1);
10105 }
10106
10107 #[test]
10114 fn index_equal_to_body_count_is_out_of_range_for_every_setter() {
10115 let mut world = quiet_world();
10116 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10117 let len = world.body_count();
10118 let metal = world.material_table.register_metal();
10119
10120 world.set_body_material(len, metal);
10121 world.set_body_collision_radius(len, Fix128::ONE);
10122 world.clear_body_collision_radius(len);
10123 world.set_body_filter(len, CollisionFilter::NONE);
10124 assert_eq!(
10125 world.body_materials,
10126 vec![crate::material::DEFAULT_MATERIAL]
10127 );
10128 assert_eq!(world.body_collision_radii, vec![None]);
10129 assert_eq!(world.body_filters, vec![CollisionFilter::DEFAULT]);
10130
10131 world.set_body_material(a, metal);
10132 let default_metal = world
10133 .material_table
10134 .combine(crate::material::DEFAULT_MATERIAL, metal);
10135 let default_default = world.material_table.combine(0, 0);
10136 assert_ne!(default_metal.friction, default_default.friction);
10137 for (x, y, want) in [
10138 (len, a, default_metal),
10139 (a, len, default_metal),
10140 (len, len, default_default),
10141 ] {
10142 let c = world.combined_material(x, y);
10143 assert_eq!(
10144 (c.friction, c.restitution),
10145 (want.friction, want.restitution),
10146 "({x}, {y}) must treat the out-of-range side as the default material"
10147 );
10148 }
10149 let custom = CollisionFilter {
10150 layer: 1 << 3,
10151 mask: 1 << 9,
10152 group: 5,
10153 };
10154 world.set_body_filter(a, custom);
10155 assert_eq!(world.body_filter(a), custom);
10156 assert_eq!(world.body_filter(len), CollisionFilter::DEFAULT);
10157 }
10158
10159 #[test]
10164 fn contact_cache_frame_lifecycle_ages_and_prunes_manifolds() {
10165 let mut world = quiet_world();
10166 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10167 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10168 push_contact(&mut world, a, b, r(1, 2), r(1, 2));
10169 let c = world.contact_constraints.pop().expect("pushed");
10170 world.add_contact(c);
10171 assert_eq!(world.contact_constraints.len(), 1);
10172 assert_eq!(world.contact_cache.manifold_count(), 1);
10173 assert_eq!(world.contact_cache.manifolds[0].stale_frames, 0);
10174
10175 for _ in 0..4 {
10176 world.begin_frame();
10177 }
10178 assert_eq!(world.contact_cache.manifolds[0].stale_frames, 4);
10179 world.end_frame();
10180 assert_eq!(world.contact_cache.manifold_count(), 0);
10181
10182 world.clear_contacts();
10183 assert!(world.contact_constraints.is_empty());
10184 }
10185
10186 #[test]
10190 fn add_and_remove_joint_roundtrip() {
10191 use crate::joint::{BallJoint, Joint};
10192 let mut world = quiet_world();
10193 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10194 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10195 let j0 = world.add_joint(Joint::Ball(BallJoint::new(
10196 a,
10197 b,
10198 Vec3Fix::ZERO,
10199 Vec3Fix::ZERO,
10200 )));
10201 let j1 = world.add_joint(Joint::Ball(BallJoint::new(
10202 b,
10203 a,
10204 v3(1, 0, 0),
10205 Vec3Fix::ZERO,
10206 )));
10207 assert_eq!((j0, j1), (0, 1));
10208 assert_eq!(world.joint_count(), 2);
10209 assert!(world.remove_joint(2).is_none());
10210 assert_eq!(world.joint_count(), 2);
10211 let removed = world.remove_joint(0).expect("joint 0 exists");
10212 assert_eq!(removed.bodies(), (a, b));
10213 assert_eq!(world.joint_count(), 1);
10214 assert_eq!(world.joints[0].bodies(), (b, a));
10215 assert!(world.remove_joint(1).is_none());
10216 }
10217
10218 #[test]
10225 fn force_field_add_remove_and_step_applies_directional_force() {
10226 use crate::force::{ForceField, ForceFieldInstance};
10227 let mut world = quiet_world();
10228 let body = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10229 let wind = ForceFieldInstance::new(ForceField::Directional {
10230 direction: Vec3Fix::UNIT_X,
10231 strength: Fix128::from_int(2),
10232 });
10233 let drag = ForceFieldInstance::new(ForceField::Drag {
10234 coefficient: Fix128::ONE,
10235 });
10236 assert_eq!(world.add_force_field(wind), 0);
10237 assert_eq!(world.add_force_field(drag), 1);
10238 assert_eq!(world.force_fields.len(), 2);
10239 assert!(world.remove_force_field(2).is_none());
10240 assert!(matches!(
10241 world.remove_force_field(1).map(|f| f.field),
10242 Some(ForceField::Drag { .. })
10243 ));
10244 assert!(world.remove_force_field(1).is_none());
10245 assert_eq!(world.force_fields.len(), 1);
10246
10247 world.step(r(1, 4));
10248 assert_eq!(
10249 world.bodies[body].velocity,
10250 Vec3Fix::new(r(1, 2), Fix128::ZERO, Fix128::ZERO)
10251 );
10252 assert_eq!(
10253 world.bodies[body].position,
10254 Vec3Fix::new(r(1, 8), Fix128::ZERO, Fix128::ZERO)
10255 );
10256 }
10257
10258 #[test]
10262 fn park_proxy_live_count_tracks_the_tree() {
10263 let mut w = PhysicsWorld::new(PhysicsConfig {
10264 gravity: Vec3Fix::ZERO,
10265 ..PhysicsConfig::default()
10266 });
10267 let r = Fix128::from_ratio(1, 2);
10268 for i in 0..20 {
10269 w.add_body_with_radius(
10270 RigidBody::new_dynamic(Vec3Fix::from_int(i * 3, 0, 0), Fix128::ONE),
10271 r,
10272 );
10273 w.islands.sleep_data[i as usize].state = SleepState::Sleeping;
10274 }
10275 let check = |w: &PhysicsWorld, ctx: &str| {
10276 assert_eq!(w.park.proxy_live, w.park.tree.proxy_count(), "{ctx}");
10277 assert_eq!(
10278 w.park.proxy_live,
10279 w.park.proxies.iter().flatten().count(),
10280 "{ctx}"
10281 );
10282 };
10283 w.step(Fix128::from_ratio(1, 60));
10284 check(&w, "parked");
10285 assert_eq!(w.park.proxy_live, 20);
10286 w.wake_body(0);
10288 w.bodies[0].set_position(Vec3Fix::new(
10289 Fix128::from_ratio(22, 10),
10290 Fix128::ZERO,
10291 Fix128::ZERO,
10292 ));
10293 w.step(Fix128::from_ratio(1, 60));
10294 check(&w, "contact");
10295 assert!(w.stage_work().unparked > 0);
10296 w.wake_body(7);
10297 w.step(Fix128::from_ratio(1, 60));
10298 check(&w, "manual wake");
10299 w.remove_body(3);
10300 w.step(Fix128::from_ratio(1, 60));
10301 check(&w, "remove");
10302 w.set_sleep_skip(false);
10303 check(&w, "off");
10304 assert_eq!(w.park.proxy_live, 0);
10305 }
10306
10307 #[test]
10312 fn is_sleeping_and_wake_body_reflect_island_state() {
10313 let mut world = quiet_world();
10314 let floor = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10315 let mover = world.add_body(RigidBody::new_dynamic(v3(0, 5, 0), Fix128::ONE));
10316 world.bodies[mover].velocity = v3(5, 0, 0);
10317 world.step(r(1, 4));
10318 assert!(world.is_sleeping(floor));
10319 assert!(!world.is_sleeping(mover));
10320 assert!(!world.is_sleeping(99));
10321 world.wake_body(floor);
10322 assert!(!world.is_sleeping(floor));
10323 assert!(!world.is_sleeping(mover));
10324 }
10325
10326 #[test]
10329 fn set_sleep_config_changes_frames_to_sleep() {
10330 let cfg = SleepConfig {
10331 frames_to_sleep: 2,
10332 ..SleepConfig::default()
10333 };
10334 let mut quick = quiet_world();
10335 let idle = quick.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10336 quick.set_sleep_config(cfg);
10337 assert_eq!(quick.islands.config, cfg);
10338 quick.step(r(1, 4));
10339 assert!(!quick.is_sleeping(idle));
10340 quick.step(r(1, 4));
10341 assert!(quick.is_sleeping(idle));
10342
10343 let mut slow = quiet_world();
10344 let idle2 = slow.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10345 slow.step(r(1, 4));
10346 slow.step(r(1, 4));
10347 assert!(!slow.is_sleeping(idle2));
10348 }
10349
10350 #[test]
10354 fn drain_events_return_the_events_and_empty_the_collector() {
10355 let mut world = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
10356 world.detect_collisions();
10357 assert_eq!(world.contact_events().len(), 1);
10358 let drained = world.drain_contact_events();
10359 assert_eq!(drained.len(), 1);
10360 assert_eq!((drained[0].body_a, drained[0].body_b), (0, 1));
10361 assert_eq!(drained[0].event_type, crate::event::ContactEventType::Begin);
10362 assert!(world.contact_events().is_empty());
10363 assert!(world.drain_contact_events().is_empty());
10364
10365 let mut sensor = two_spheres(3, 2, Fix128::ONE, Fix128::ONE);
10366 sensor.bodies[1].is_sensor = true;
10367 sensor.detect_collisions();
10368 let triggers = sensor.drain_trigger_events();
10369 assert_eq!(triggers.len(), 1);
10370 assert!(triggers[0].entered);
10371 assert!(sensor.trigger_events().is_empty());
10372 assert!(sensor.drain_trigger_events().is_empty());
10373 }
10374
10375 #[test]
10379 fn step_rebuilds_joint_islands_from_scratch() {
10380 use crate::joint::{BallJoint, Joint};
10381 let mut world = quiet_world();
10382 let a = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10383 let b = world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10384 let c = world.add_body(RigidBody::new_dynamic(v3(9, 0, 0), Fix128::ONE));
10385 world.add_joint(Joint::Ball(BallJoint::new(
10386 a,
10387 b,
10388 Vec3Fix::ZERO,
10389 Vec3Fix::ZERO,
10390 )));
10391 world.islands.reset_unions();
10393 assert_ne!(world.islands.find(a), world.islands.find(b));
10394 world.step(r(1, 4));
10395 assert_eq!(world.islands.find(a), world.islands.find(b));
10396 assert_ne!(world.islands.find(a), world.islands.find(c));
10397 }
10398
10399 #[test]
10404 fn integrate_positions_kinematic_velocity_is_target_minus_position_over_dt() {
10405 let mut world = quiet_world();
10406 let k = world.add_body(RigidBody::new_kinematic(v3(1, 2, 3)));
10407 world.bodies[k].set_kinematic_target(v3(5, 2, 3), QuatFix::IDENTITY);
10408 world.integrate_positions(r(1, 4));
10409 assert_eq!(world.bodies[k].velocity, v3(16, 0, 0));
10410 assert_eq!(world.bodies[k].position, v3(5, 2, 3));
10411 assert_eq!(world.bodies[k].prev_position, v3(1, 2, 3));
10412 }
10413
10414 #[cfg(feature = "std")]
10422 #[test]
10423 fn advance_kinematic_targets_for_tgs_matches_integrate_positions_closed_form() {
10424 let mut world = quiet_world();
10425 let k = world.add_body(RigidBody::new_kinematic(v3(1, 2, 3)));
10426 world.bodies[k].set_kinematic_target(v3(5, 2, 3), QuatFix::IDENTITY);
10427 world.advance_kinematic_targets_for_tgs(r(1, 4));
10428 assert_eq!(world.bodies[k].velocity, v3(16, 0, 0));
10429 assert_eq!(world.bodies[k].position, v3(5, 2, 3));
10430 assert_eq!(world.bodies[k].prev_position, v3(1, 2, 3));
10431 }
10432
10433 #[cfg(feature = "std")]
10436 #[test]
10437 fn advance_kinematic_targets_for_tgs_leaves_dynamic_bodies_untouched() {
10438 let mut world = quiet_world();
10439 let d = world.add_body(RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE));
10440 world.bodies[d].velocity = v3(7, 8, 9);
10441 world.advance_kinematic_targets_for_tgs(r(1, 4));
10442 assert_eq!(world.bodies[d].position, v3(1, 2, 3));
10443 assert_eq!(world.bodies[d].velocity, v3(7, 8, 9));
10444 }
10445
10446 #[cfg(feature = "std")]
10451 #[test]
10452 fn advance_kinematic_targets_for_tgs_without_a_target_stays_at_rest() {
10453 let mut world = quiet_world();
10454 let k = world.add_body(RigidBody::new_kinematic(v3(1, 2, 3)));
10455 world.advance_kinematic_targets_for_tgs(r(1, 4));
10456 assert_eq!(world.bodies[k].position, v3(1, 2, 3));
10457 assert_eq!(world.bodies[k].velocity, Vec3Fix::ZERO);
10458 assert_eq!(world.bodies[k].prev_position, v3(1, 2, 3));
10459 }
10460
10461 #[test]
10472 fn update_velocities_friction_and_restitution_split_between_two_dynamic_bodies() {
10473 let mut world = quiet_world();
10474 let a = world.add_body(RigidBody::new_dynamic(v3(0, 1, 0), Fix128::ONE));
10475 let b = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10476 world.bodies[b].inv_mass = Fix128::from_int(3);
10477 let contact = Contact {
10478 depth: r(1, 100),
10479 normal: v3(0, 1, 0),
10480 point_a: Vec3Fix::ZERO,
10481 point_b: Vec3Fix::ZERO,
10482 };
10483 let mut c = ContactConstraint::new(a, b, contact);
10484 c.friction = r(1, 2);
10485 c.restitution = r(1, 2);
10486 c.cached_lambda = r(1, 4);
10488 world.contact_constraints.push(c);
10489 freeze_positions(&mut world);
10490 give_velocity(&mut world, a, v3(3, -4, 0));
10491 give_velocity(&mut world, b, v3(1, -2, 0));
10492
10493 world.update_velocities(r(1, 4));
10494
10495 assert_near_vec(
10496 world.bodies[a].velocity,
10497 Vec3Fix::new(r(23, 8), r(-13, 4), Fix128::ZERO),
10498 "A",
10499 );
10500 assert_near_vec(
10501 world.bodies[b].velocity,
10502 Vec3Fix::new(r(11, 8), r(-17, 4), Fix128::ZERO),
10503 "B",
10504 );
10505 }
10506
10507 fn half_epsilon() -> Fix128 {
10510 Fix128 {
10511 hi: 0,
10512 lo: W_SUM_EPSILON.lo >> 1,
10513 }
10514 }
10515
10516 #[test]
10521 fn update_velocities_w_sum_epsilon_boundary() {
10522 for (inv_mass, expected_y) in [(W_SUM_EPSILON, 2), (half_epsilon(), -4)] {
10523 let mut world = quiet_world();
10524 let a = world.add_body(RigidBody::new_dynamic(v3(0, 1, 0), Fix128::ONE));
10525 let b = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10526 world.bodies[a].inv_mass = inv_mass;
10527 let contact = Contact {
10528 depth: r(1, 100),
10529 normal: v3(0, 1, 0),
10530 point_a: Vec3Fix::ZERO,
10531 point_b: Vec3Fix::ZERO,
10532 };
10533 let mut c = ContactConstraint::new(a, b, contact);
10534 c.friction = Fix128::ZERO;
10535 c.restitution = r(1, 2);
10536 world.contact_constraints.push(c);
10537 freeze_positions(&mut world);
10538 give_velocity(&mut world, a, v3(0, -4, 0));
10539
10540 world.update_velocities(r(1, 4));
10541
10542 assert_eq!(
10543 world.bodies[a].velocity,
10544 v3(0, expected_y, 0),
10545 "inv_mass {inv_mass:?}"
10546 );
10547 }
10548 }
10549
10550 #[test]
10556 fn solve_distance_constraints_w_sum_epsilon_boundary() {
10557 for (inv_mass, expected) in [(W_SUM_EPSILON, v3(0, 1, 0)), (half_epsilon(), v3(0, 4, 0))] {
10558 let mut world = quiet_world();
10559 let a = world.add_body(RigidBody::new_dynamic(v3(0, 4, 0), Fix128::ONE));
10560 let b = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10561 world.bodies[a].inv_mass = inv_mass;
10562 world.add_distance_constraint(dc(a, b));
10563 world.solve_distance_constraints(r(1, 4));
10564 assert_near_vec(world.bodies[a].position, expected, "A position");
10565 assert_eq!(world.bodies[b].position, Vec3Fix::ZERO);
10566 }
10567 }
10568
10569 #[test]
10573 fn solve_contact_constraints_w_sum_epsilon_boundary() {
10574 for (inv_mass, expected) in [
10575 (W_SUM_EPSILON, v3(1, 0, 0)),
10576 (half_epsilon(), Vec3Fix::ZERO),
10577 ] {
10578 let mut world = contact_world(inv_mass, Fix128::ZERO, Fix128::ONE);
10579 world.solve_contact_constraints(r(1, 4));
10580 assert_eq!(world.bodies[0].position, expected, "inv_mass {inv_mass:?}");
10581 assert_eq!(world.bodies[1].position, v3(1, 0, 0));
10582 }
10583 }
10584
10585 #[test]
10592 fn solve_distance_constraints_anchor_a_offset_and_inverse_mass_scaling() {
10593 let mut world = quiet_world();
10594 let a = world.add_body(RigidBody::new_dynamic(v3(0, 2, 0), Fix128::ONE));
10595 let b = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10596 world.bodies[a].inv_mass = Fix128::from_int(2);
10597 let mut c = dc(a, b);
10598 c.local_anchor_a = v3(0, 1, 0);
10599 world.add_distance_constraint(c);
10600
10601 world.solve_distance_constraints(r(1, 4));
10602
10603 assert_near_vec(world.bodies[a].position, Vec3Fix::ZERO, "A");
10604 assert_eq!(world.bodies[b].position, Vec3Fix::ZERO);
10605 assert_eq!(world.distance_constraints[0].cached_lambda, Fix128::ONE);
10606 }
10607
10608 #[test]
10615 fn detect_collisions_three_bodies_squared_distance_cull_uses_squared_radius() {
10616 let mut world = quiet_world();
10617 let radius = r(3, 2);
10618 world.add_body_with_radius(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE), radius);
10619 world.add_body_with_radius(RigidBody::new_dynamic(v3(2, 1, 1), Fix128::ONE), radius);
10620 world.add_body_with_radius(RigidBody::new_dynamic(v3(50, 50, 50), Fix128::ONE), radius);
10621 world.detect_collisions();
10622 assert_eq!(world.contact_constraints.len(), 1);
10623 let c = world.contact_constraints[0];
10624 assert_eq!((c.body_a, c.body_b), (0, 1));
10625 let expected_depth = Fix128::from_int(3) - Fix128::from_int(6).sqrt();
10626 assert!(
10627 near(c.contact.depth, expected_depth),
10628 "depth {:?} vs {expected_depth:?}",
10629 c.contact.depth
10630 );
10631 assert_eq!(world.contact_events().len(), 1);
10632 }
10633
10634 #[test]
10639 fn remove_sdf_collider_boundary_and_sdf_radius_setter() {
10640 use crate::sdf_collider::{ClosureSdf, SdfCollider};
10641 let mut world = quiet_world();
10642 let ground = ClosureSdf::new(|_x, y, _z| y, |_x, _y, _z| (0.0, 1.0, 0.0));
10643 assert_eq!(
10644 world.add_sdf_collider(SdfCollider::new_static(
10645 Box::new(ground),
10646 Vec3Fix::ZERO,
10647 QuatFix::IDENTITY,
10648 )),
10649 0
10650 );
10651 assert!(world.remove_sdf_collider(1).is_none());
10652 assert_eq!(world.sdf_colliders.len(), 1);
10653 assert!(world.remove_sdf_collider(0).is_some());
10654 assert!(world.sdf_colliders.is_empty());
10655 assert!(world.remove_sdf_collider(0).is_none());
10656
10657 world.set_sdf_collision_radius(r(3, 4));
10658 assert_eq!(world.sdf_collision_radius, r(3, 4));
10659 }
10660
10661 #[test]
10664 fn deserialize_state_accepts_header_only_snapshot_of_empty_world() {
10665 let src = quiet_world();
10666 let bytes = src.serialize_state();
10667 assert_eq!(bytes.len(), 21);
10669 let mut dst = quiet_world();
10670 assert!(dst.deserialize_state(&bytes));
10671 assert_eq!(dst.body_count(), 0);
10672 }
10673
10674 #[test]
10679 fn deserialize_state_extends_short_parallel_arrays_to_body_count() {
10680 let mut world = quiet_world();
10681 world.add_body(RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE));
10682 world.set_body_collision_radius(0, r(1, 2));
10683 world
10684 .bodies
10685 .push(RigidBody::new_dynamic(v3(4, 5, 6), Fix128::ONE));
10686 world
10687 .bodies
10688 .push(RigidBody::new_dynamic(v3(7, 8, 9), Fix128::ONE));
10689 assert_eq!(world.body_collision_radii.len(), 1);
10690 let bytes = world.serialize_state();
10691 assert!(world.deserialize_state(&bytes));
10692 assert_eq!(world.body_collision_radii, vec![Some(r(1, 2)), None, None]);
10693 assert_eq!(world.body_filters, vec![CollisionFilter::DEFAULT; 3]);
10694 assert_eq!(
10695 world.body_materials,
10696 vec![crate::material::DEFAULT_MATERIAL; 3]
10697 );
10698 assert_eq!(world.islands.sleep_data.len(), 3);
10699 assert_eq!(world.bodies[2].position, v3(7, 8, 9));
10700 }
10701
10702 #[test]
10705 fn observe_body_returns_none_out_of_range_and_fields_for_in_range() {
10706 let mut world = quiet_world();
10707 let mut a = RigidBody::new_dynamic(v3(1, 2, 3), Fix128::ONE);
10708 a.velocity = v3(4, 5, 6);
10709 a.angular_velocity = v3(7, 8, 9);
10710 world.add_body(a);
10711
10712 assert!(world.observe_body(1).is_none(), "範囲外 index は None");
10713
10714 let obs = world.observe_body(0).expect("範囲内 index は Some");
10715 assert_eq!(obs.body_index, 0);
10716 assert_eq!(obs.position, v3(1, 2, 3));
10717 assert_eq!(obs.velocity, v3(4, 5, 6));
10718 assert_eq!(obs.angular_velocity, v3(7, 8, 9));
10719 assert!(!obs.sleeping);
10720 assert!(!obs.in_contact, "接触していない body は in_contact = false");
10721 }
10722
10723 #[test]
10724 fn observe_bodies_returns_one_entry_per_body_in_index_order() {
10725 let mut world = quiet_world();
10726 world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10727 world.add_body(RigidBody::new_dynamic(v3(2, 0, 0), Fix128::ONE));
10728 world.add_body(RigidBody::new_dynamic(v3(3, 0, 0), Fix128::ONE));
10729
10730 let obs = world.observe_bodies();
10731 assert_eq!(obs.len(), 3);
10732 for (i, o) in obs.iter().enumerate() {
10733 assert_eq!(o.body_index, i);
10734 assert_eq!(o.position, world.bodies[i].position);
10735 }
10736 }
10737
10738 #[test]
10739 fn observe_body_reports_in_contact_from_this_frame_events() {
10740 let mut world = PhysicsWorld::new(PhysicsConfig {
10743 gravity: Vec3Fix::ZERO,
10744 ..PhysicsConfig::default()
10745 });
10746 let radius = Fix128::from_int(2);
10747 let a = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
10748 world.add_body_with_radius(a, radius);
10749 let b = RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE);
10750 world.add_body_with_radius(b, radius);
10751 let c = RigidBody::new_dynamic(v3(100, 100, 100), Fix128::ONE);
10752 world.add_body_with_radius(c, Fix128::from_ratio(1, 10));
10753
10754 world.step(Fix128::from_ratio(1, 60));
10755
10756 assert!(world.observe_body(0).unwrap().in_contact);
10757 assert!(world.observe_body(1).unwrap().in_contact);
10758 assert!(
10759 !world.observe_body(2).unwrap().in_contact,
10760 "遠くの body は接触していないはず"
10761 );
10762 }
10763
10764 #[test]
10770 fn batches_are_body_disjoint_checks_second_body_and_self_constraints() {
10771 let mut world = quiet_world();
10772 for i in 0..3i64 {
10773 world.add_body(RigidBody::new_dynamic(v3(i, 0, 0), Fix128::ONE));
10774 }
10775 world.add_distance_constraint(dc(1, 0));
10776 world.add_distance_constraint(dc(2, 0));
10777 world.rebuild_batches();
10778 assert_eq!(world.num_batches(), 2);
10779 assert!(world.batches_are_body_disjoint());
10780
10781 let mut shared_b = ConstraintBatch::default();
10782 shared_b.distance_indices.extend([0usize, 1]);
10783 world.constraint_batches = vec![shared_b];
10784 assert!(!world.batches_are_body_disjoint());
10785
10786 world.distance_constraints.push(dc(1, 1));
10787 let mut self_only = ConstraintBatch::default();
10788 self_only.distance_indices.push(2);
10789 world.constraint_batches = vec![self_only];
10790 assert!(world.batches_are_body_disjoint());
10791
10792 let mut self_and_other = ConstraintBatch::default();
10793 self_and_other.distance_indices.extend([2usize, 0]);
10794 world.constraint_batches = vec![self_and_other];
10795 assert!(!world.batches_are_body_disjoint());
10796 }
10797
10798 #[test]
10805 fn raycast_ties_keep_first_candidate_and_nearer_later_candidate_replaces() {
10806 let mut tie = quiet_world();
10807 tie.add_body_with_radius(
10808 RigidBody::new_static(Vec3Fix::new(Fix128::from_int(5), r(1, 2), Fix128::ZERO)),
10809 Fix128::ONE,
10810 );
10811 tie.add_body_with_radius(
10812 RigidBody::new_static(Vec3Fix::new(Fix128::from_int(5), r(-1, 2), Fix128::ZERO)),
10813 Fix128::ONE,
10814 );
10815 let hit = tie
10816 .raycast(Vec3Fix::ZERO, v3(1, 0, 0), Fix128::from_int(20))
10817 .expect("both spheres straddle the ray");
10818 assert_eq!(hit.0, 1);
10819 let expected_t = Fix128::from_int(5) - r(3, 4).sqrt();
10821 assert!(near(hit.1, expected_t), "t {:?} vs {expected_t:?}", hit.1);
10822
10823 let far_first = sphere_world(&[v3(5, 0, 0), v3(10, 0, 0)]);
10824 assert_eq!(
10825 far_first.raycast(v3(20, 0, 0), v3(-1, 0, 0), Fix128::from_int(100)),
10826 Some((1, Fix128::from_int(8)))
10827 );
10828 }
10829
10830 #[test]
10833 fn physics_world_debug_lists_type_and_counts() {
10834 let mut world = quiet_world();
10835 world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
10836 world.add_body(RigidBody::new_dynamic(v3(1, 0, 0), Fix128::ONE));
10837 let text = format!("{world:?}");
10838 assert!(text.starts_with("PhysicsWorld"), "{text}");
10839 assert!(text.contains("bodies: 2"), "{text}");
10840 assert!(text.contains("joints: 0"), "{text}");
10841 }
10842
10843 fn half_unit_box() -> crate::shape::Shape {
10847 crate::shape::Shape::Box {
10848 half_extents: Vec3Fix::from_int(1, 1, 1),
10849 }
10850 }
10851
10852 #[test]
10860 fn colliders_overlap_follows_the_closed_form_gaps() {
10861 let mut world = quiet_world();
10862 let shaped = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10863 assert!(world.set_body_shape(shaped, &half_unit_box()));
10864 assert!(!world.set_body_shape(99, &half_unit_box()));
10865 let near = world.add_body_with_radius(
10866 RigidBody::new(
10867 Vec3Fix::new(Fix128::from_ratio(3, 2), Fix128::ZERO, Fix128::ZERO),
10868 Fix128::ONE,
10869 ),
10870 Fix128::ONE,
10871 );
10872 let far = world.add_body_with_radius(
10873 RigidBody::new(
10874 Vec3Fix::new(Fix128::from_ratio(5, 2), Fix128::ZERO, Fix128::ZERO),
10875 Fix128::ONE,
10876 ),
10877 Fix128::ONE,
10878 );
10879 let bare = world.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE));
10880 assert!(world.colliders_overlap(shaped, near));
10882 assert!(world.colliders_overlap(near, shaped));
10883 assert!(!world.colliders_overlap(shaped, far));
10884 assert!(!world.colliders_overlap(far, shaped));
10885 assert!(world.colliders_overlap(near, far));
10887 assert!(!world.colliders_overlap(shaped, bare));
10889 assert!(!world.colliders_overlap(near, bare));
10890 assert!(!world.colliders_overlap(near, 99));
10891 assert!(world.set_body_shape(far, &half_unit_box()));
10894 assert!(!world.colliders_overlap(shaped, far));
10895 assert!(world.set_body_shape(near, &half_unit_box()));
10896 assert!(world.colliders_overlap(shaped, near));
10897 }
10898
10899 #[test]
10904 fn sphere_rests_on_a_shaped_box_at_face_plus_radius() {
10905 for box_first in [true, false] {
10906 let mut world = PhysicsWorld::new(SolverConfig::default());
10907 let ball = || {
10908 RigidBody::new(
10909 Vec3Fix::new(Fix128::ZERO, Fix128::from_ratio(8, 5), Fix128::ZERO),
10910 Fix128::ONE,
10911 )
10912 };
10913 let (floor, b) = if box_first {
10914 let f = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10915 let b = world.add_body_with_radius(ball(), Fix128::from_ratio(1, 2));
10916 (f, b)
10917 } else {
10918 let b = world.add_body_with_radius(ball(), Fix128::from_ratio(1, 2));
10919 let f = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
10920 (f, b)
10921 };
10922 assert!(world.set_body_shape(floor, &half_unit_box()));
10923 for _ in 0..120 {
10924 world.step(Fix128::from_ratio(1, 60));
10925 }
10926 let y = world.bodies[b].position.y.to_f64();
10927 assert!((y - 1.5).abs() < 0.02, "box_first {box_first}: y {y}");
10928 assert!(world.bodies[b].position.x.abs() < Fix128::from_ratio(1, 1000));
10929 }
10930 }
10931
10932 #[test]
10937 fn joint_motor_accessors_and_removal_remap() {
10938 let mut world = quiet_world();
10939 for x in 0..3 {
10940 world.add_body(RigidBody::new(Vec3Fix::from_int(x, 0, 0), Fix128::ONE));
10941 }
10942 let ball = |a, b| {
10943 Joint::Ball(crate::joint::BallJoint::new(
10944 a,
10945 b,
10946 Vec3Fix::ZERO,
10947 Vec3Fix::ZERO,
10948 ))
10949 };
10950 let j0 = world.add_joint(ball(0, 1));
10951 let j1 = world.add_joint(ball(1, 2));
10952 let pd = crate::motor::PdController::new(Fix128::ONE, Fix128::ONE, Fix128::from_int(10));
10953 let m0 = world.add_joint_motor(j0, pd);
10954 let m1 = world.add_joint_motor(j1, pd);
10955 assert!(world.set_joint_motor_velocity_target(m1, Fix128::from_int(2)));
10956 assert!(!world.set_joint_motor_velocity_target(9, Fix128::ONE));
10957 assert_eq!(
10958 world.joint_motor_mut(m1).unwrap().controller.mode,
10959 crate::motor::MotorMode::Velocity
10960 );
10961 assert!(world.disable_joint_motor(m1));
10962 assert!(!world.disable_joint_motor(9));
10963 assert_eq!(
10964 world.joint_motor_mut(m1).unwrap().controller.mode,
10965 crate::motor::MotorMode::Off
10966 );
10967 assert!(world.joint_motor_mut(9).is_none());
10968
10969 let r0 = world.add_joint_motor_3d(
10970 j0,
10971 Vec3Fix::from_int(1, 1, 1),
10972 Vec3Fix::from_int(1, 1, 1),
10973 Fix128::from_int(5),
10974 );
10975 let r1 = world.add_joint_motor_3d(
10976 j1,
10977 Vec3Fix::from_int(1, 1, 1),
10978 Vec3Fix::from_int(1, 1, 1),
10979 Fix128::from_int(5),
10980 );
10981 let target = QuatFix::from_axis_angle(Vec3Fix::from_int(0, 1, 0), Fix128::from_ratio(1, 4));
10982 assert!(world.set_joint_motor_3d_rotation_target(r1, target));
10983 assert!(!world.set_joint_motor_3d_rotation_target(9, target));
10984 assert_eq!(
10985 world.joint_motor_3d_mut(r1).unwrap().target_rotation,
10986 target
10987 );
10988 assert!(world.joint_motor_3d_mut(9).is_none());
10989 let _ = (m0, r0);
10990
10991 assert!(world.remove_joint(7).is_none());
10992 assert!(world.remove_joint(j0).is_some());
10993 assert_eq!(world.joint_motors.len(), 1);
10995 assert_eq!(world.joint_motors[0].joint_index, 0);
10996 assert_eq!(world.joint_motors_3d.len(), 1);
10997 assert_eq!(world.joint_motors_3d[0].0, 0);
10998 assert_eq!(world.joint_motors_3d[0].1.target_rotation, target);
10999 }
11000
11001 #[test]
11008 fn contact_forces_are_lambda_over_w_h_squared() {
11009 let mut world = PhysicsWorld::new(SolverConfig::default());
11010 let a = world.add_body(RigidBody::new(Vec3Fix::from_int(0, 1, 0), Fix128::ONE));
11011 let b = world.add_body(RigidBody::new_static(Vec3Fix::ZERO));
11012 let s = world.add_body(RigidBody::new_static(Vec3Fix::from_int(5, 0, 0)));
11013 let contact = Contact {
11014 depth: Fix128::from_ratio(1, 100),
11015 normal: Vec3Fix::from_int(0, 1, 0),
11016 point_a: Vec3Fix::from_int(0, 1, 0),
11017 point_b: Vec3Fix::ZERO,
11018 };
11019 let mut c = ContactConstraint::new(a, b, contact);
11020 c.cached_lambda = Fix128::from_ratio(1, 2);
11021 let mut dead = ContactConstraint::new(b, s, contact);
11022 dead.cached_lambda = Fix128::ONE;
11023 world.contact_constraints = vec![c, dead];
11024 let dt = Fix128::ONE;
11025 let forces = world.contact_forces(dt);
11026 assert_eq!(forces.len(), 1);
11027 assert_eq!(forces[0].0, Vec3Fix::from_int(0, 1, 0));
11028 assert_eq!(forces[0].1, Vec3Fix::from_int(0, 1, 0));
11029 assert_eq!(forces[0].2, Fix128::from_int(32));
11030 assert_eq!(
11031 world.contact_constraint_force(&world.contact_constraints[0], dt),
11032 Some(Fix128::from_int(32))
11033 );
11034 assert_eq!(
11035 world.contact_constraint_force(&world.contact_constraints[1], dt),
11036 None
11037 );
11038 let arrows = world.contact_arrows(dt);
11039 assert_eq!(arrows.len(), 1);
11040 assert_eq!(arrows[0].force_magnitude, Fix128::from_int(32));
11041 let friction = world.contact_friction_arrows(dt);
11042 assert_eq!(friction.len(), 2);
11043 let mu_f = (Fix128::from_int(32) * Fix128::from_ratio(3, 10)).to_f64();
11044 for f in &friction {
11045 assert!((f.force_magnitude.to_f64() - 9.6).abs() < 1e-9 && (mu_f - 9.6).abs() < 1e-9);
11046 }
11047 let cones = world.contact_friction_cones(dt);
11048 assert_eq!(cones.len(), 1);
11049 assert_eq!(cones[0].height, Fix128::from_int(32));
11050 }
11051
11052 #[test]
11055 fn static_collider_add_remove_count() {
11056 let mut world = quiet_world();
11057 let plane = |h: i64| {
11058 crate::static_collider::StaticCollider::Plane(
11059 crate::plane_collider::PlaneCollider::new(
11060 Vec3Fix::from_int(0, 1, 0),
11061 Fix128::from_int(h),
11062 ),
11063 )
11064 };
11065 assert_eq!(world.add_static_collider(plane(0)), 0);
11066 assert_eq!(world.add_static_collider(plane(1)), 1);
11067 assert_eq!(world.static_collider_count(), 2);
11068 assert!(world.remove_static_collider(2).is_none());
11069 assert!(world.remove_static_collider(0).is_some());
11070 assert_eq!(world.static_collider_count(), 1);
11071 let c = world.static_colliders_slice()[0]
11074 .collide_sphere(Vec3Fix::from_int(0, 1, 0), Fix128::from_ratio(1, 2))
11075 .expect("touching the plane at y = 1");
11076 assert!((c.depth.to_f64() - 0.5).abs() < 1e-9, "{c:?}");
11077 }
11078
11079 #[test]
11085 fn dynamic_tree_and_hybrid_broadphases_find_the_pairs() {
11086 for kind in [Broadphase::DynamicTree, Broadphase::Hybrid] {
11087 let mut world = quiet_world();
11088 world.set_broadphase(kind);
11089 assert_eq!(world.broadphase(), kind);
11090 let a =
11091 world.add_body_with_radius(RigidBody::new(Vec3Fix::ZERO, Fix128::ONE), Fix128::ONE);
11092 let b = world.add_body_with_radius(
11093 RigidBody::new(
11094 Vec3Fix::new(Fix128::from_ratio(3, 2), Fix128::ZERO, Fix128::ZERO),
11095 Fix128::ONE,
11096 ),
11097 Fix128::ONE,
11098 );
11099 let far = world.add_body_with_radius(
11100 RigidBody::new(Vec3Fix::from_int(50, 0, 0), Fix128::ONE),
11101 Fix128::ONE,
11102 );
11103 let bare = world.add_body(RigidBody::new(Vec3Fix::from_int(-50, 0, 0), Fix128::ONE));
11104 world.step(Fix128::from_ratio(1, 60));
11105 let gap = (world.bodies[b].position - world.bodies[a].position).length();
11106 assert!(gap > Fix128::from_ratio(3, 2), "{kind:?}: gap {gap:?}");
11107 assert_eq!(world.bodies[far].position, Vec3Fix::from_int(50, 0, 0));
11108 if kind == Broadphase::DynamicTree {
11109 assert_eq!(world.broadphase_stats().proxies, 3);
11110 let bx = world.broadphase_proxy_aabb(far).expect("proxy");
11111 assert!(bx.min.x <= Fix128::from_int(49) && bx.max.x >= Fix128::from_int(51));
11112 assert!(world.broadphase_proxy_aabb(bare).is_none());
11113 assert!(world.broadphase_proxy_aabb(99).is_none());
11114 }
11115 }
11116 }
11117
11118 struct ConstantPush {
11120 body: usize,
11121 force: Vec3Fix,
11122 fail: bool,
11123 }
11124
11125 impl crate::world_participant::Participant for ConstantPush {
11126 fn kind(&self) -> crate::world_participant::ParticipantKind {
11127 crate::world_participant::ParticipantKind::new(0x5055_5348)
11128 }
11129 fn substep(
11130 &mut self,
11131 ctx: &mut crate::world_participant::SubstepCtx<'_>,
11132 _h: Fix128,
11133 ) -> Result<(), crate::world_participant::ParticipantFault> {
11134 if self.fail {
11135 return Err(crate::world_participant::ParticipantFault::InvalidState);
11136 }
11137 ctx.add_force(self.body, self.force)
11138 .map_err(|_| crate::world_participant::ParticipantFault::InvalidState)
11139 }
11140 fn observe(&self, _: &mut crate::world_participant::ObservationSink) {}
11141 fn write_state(&self, _: &mut Vec<u8>) {}
11142 fn check_state(&self, _: &[u8]) -> Result<(), crate::world_participant::StateError> {
11143 Ok(())
11144 }
11145 fn read_state(&mut self, _: &[u8]) {}
11146 }
11147
11148 #[test]
11154 fn tgs_participant_force_gives_f_dt_over_m() {
11155 for with_joint in [false, true] {
11156 for fail in [false, true] {
11157 let mut world = PhysicsWorld::new(SolverConfig {
11158 gravity: Vec3Fix::ZERO,
11159 damping: Fix128::ONE,
11160 solver_backend: SolverBackend::Tgs,
11161 ..Default::default()
11162 });
11163 let body = world.add_body(RigidBody::new(Vec3Fix::ZERO, Fix128::from_int(2)));
11164 if with_joint {
11165 let other =
11166 world.add_body(RigidBody::new(Vec3Fix::from_int(0, 10, 0), Fix128::ONE));
11167 let anchor = world.add_body(RigidBody::new_static(Vec3Fix::from_int(0, 12, 0)));
11168 world.add_joint(Joint::Ball(crate::joint::BallJoint::new(
11169 other,
11170 anchor,
11171 Vec3Fix::ZERO,
11172 Vec3Fix::from_int(0, -2, 0),
11173 )));
11174 }
11175 world
11176 .add_participant(Box::new(ConstantPush {
11177 body,
11178 force: Vec3Fix::from_int(4, 0, 0),
11179 fail,
11180 }))
11181 .expect("register");
11182 world.step(Fix128::from_ratio(1, 64));
11183 let v = world.bodies[body].velocity;
11184 if fail {
11185 assert_eq!(v, Vec3Fix::ZERO, "joint {with_joint}");
11186 assert!(
11187 matches!(
11188 world.fault(),
11189 Some(crate::world_participant::WorldFault::Participant {
11190 index: 0,
11191 ..
11192 })
11193 ),
11194 "joint {with_joint}: {:?}",
11195 world.fault()
11196 );
11197 } else {
11198 assert!(
11199 (v.x.to_f64() - 1.0 / 32.0).abs() < 1e-12,
11200 "joint {with_joint}: v {v:?}"
11201 );
11202 assert!(
11203 v.y.is_zero() && v.z.is_zero(),
11204 "joint {with_joint}: v {v:?}"
11205 );
11206 assert_eq!(world.fault(), None);
11207 }
11208 }
11209 }
11210 }
11211
11212 #[test]
11217 fn checked_world_inv_inertia_apply_of_an_isotropic_body_is_c_tau() {
11218 let c = Fix128::from_ratio(5, 2);
11219 let tau = Vec3Fix::new(
11220 Fix128::from_ratio(3, 7),
11221 Fix128::from_ratio(-11, 13),
11222 Fix128::from_ratio(1, 3),
11223 );
11224 let expected = Vec3Fix::new(tau.x * c, tau.y * c, tau.z * c);
11225 let mut differ = 0;
11226 for i in 0..16_i64 {
11227 let axis = Vec3Fix::new(
11228 Fix128::from_ratio(1 + i, 3),
11229 Fix128::from_ratio(2 - i, 5),
11230 Fix128::from_ratio(3 + 2 * i, 7),
11231 )
11232 .normalize();
11233 for k in 1..=4_i64 {
11234 let mut b = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
11235 b.inv_inertia = Vec3Fix::new(c, c, c);
11236 b.rotation =
11237 QuatFix::from_axis_angle(axis, Fix128::from_ratio(7 * k + i, 9)).normalize();
11238 let got = b.checked_world_inv_inertia_apply(tau);
11239 assert_eq!(got, Some(b.world_inv_inertia_apply(tau)));
11240 differ += usize::from(got != Some(expected));
11241 }
11242 }
11243 assert_eq!(differ, 0, "{differ}/64 orientations differ from c·τ");
11244 let mut big = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
11245 big.inv_inertia = Vec3Fix::new(
11246 Fix128::from_int(4),
11247 Fix128::from_int(4),
11248 Fix128::from_int(4),
11249 );
11250 let huge = Vec3Fix::new(Fix128::from_int(1_i64 << 62), Fix128::ZERO, Fix128::ZERO);
11251 assert_eq!(big.checked_world_inv_inertia_apply(huge), None);
11252 }
11253}
11254
11255#[cfg(all(test, loom))]
11270mod loom_tests {
11271 use super::*;
11272 use loom::cell::UnsafeCell;
11273 use loom::sync::Arc;
11274 use loom::thread;
11275
11276 fn v3(x: i64, y: i64, z: i64) -> Vec3Fix {
11277 Vec3Fix::from_int(x, y, z)
11278 }
11279
11280 fn batches_of(world: &mut PhysicsWorld) -> Vec<Vec<(Option<usize>, Option<usize>)>> {
11283 world.rebuild_batches();
11284 let dynamic = |i: usize| (!world.bodies[i].inv_mass.is_zero()).then_some(i);
11285 world
11286 .constraint_batches
11287 .iter()
11288 .map(|b| {
11289 let mut pairs = Vec::new();
11290 for &ci in &b.distance_indices {
11291 let c = world.distance_constraints[ci];
11292 pairs.push((dynamic(c.body_a), dynamic(c.body_b)));
11293 }
11294 for &ci in &b.contact_indices {
11295 let c = world.contact_constraints[ci];
11296 pairs.push((dynamic(c.body_a), dynamic(c.body_b)));
11297 }
11298 pairs
11299 })
11300 .collect()
11301 }
11302
11303 fn check_batches(batches: Vec<Vec<(Option<usize>, Option<usize>)>>, n_bodies: usize) {
11304 for batch in batches {
11305 for chunk in batch.chunks(3) {
11307 let chunk: Vec<_> = chunk.to_vec();
11308 let n = n_bodies;
11309 loom::model(move || {
11310 let slots: Arc<Vec<UnsafeCell<u32>>> =
11311 Arc::new((0..n).map(|_| UnsafeCell::new(0)).collect());
11312 let handles: Vec<_> = chunk
11313 .iter()
11314 .copied()
11315 .map(|(a, b)| {
11316 let slots = Arc::clone(&slots);
11317 thread::spawn(move || {
11318 for body in [a, b].into_iter().flatten() {
11319 slots[body].with_mut(|p| unsafe { *p += 1 });
11322 }
11323 })
11324 })
11325 .collect();
11326 for h in handles {
11327 h.join().unwrap();
11328 }
11329 });
11330 }
11331 }
11332 }
11333
11334 #[test]
11335 fn loom_batches_touch_disjoint_dynamic_bodies_static_floor_hub() {
11336 let mut world = PhysicsWorld::new(SolverConfig {
11338 gravity: Vec3Fix::ZERO,
11339 ..SolverConfig::default()
11340 });
11341 let floor =
11342 world.add_body_with_radius(RigidBody::new_static(Vec3Fix::ZERO), Fix128::from_int(4));
11343 let mut ids = Vec::new();
11344 for i in 0..6i64 {
11345 let angle = Fix128::from_ratio(i, 6) * Fix128::TWO_PI;
11346 let (s, c) = angle.sin_cos();
11347 let pos = Vec3Fix::new(
11348 c * Fix128::from_int(4),
11349 s * Fix128::from_int(4),
11350 Fix128::ZERO,
11351 );
11352 ids.push(
11353 world.add_body_with_radius(RigidBody::new_dynamic(pos, Fix128::ONE), Fix128::ONE),
11354 );
11355 }
11356 for w in ids.windows(2).take(2) {
11357 world.add_distance_constraint(DistanceConstraint {
11358 body_a: w[0],
11359 body_b: w[1],
11360 local_anchor_a: Vec3Fix::ZERO,
11361 local_anchor_b: Vec3Fix::ZERO,
11362 target_distance: Fix128::from_int(4),
11363 compliance: Fix128::ZERO,
11364 cached_lambda: Fix128::ZERO,
11365 });
11366 }
11367 let _ = floor;
11368 world.clear_contacts();
11369 world.detect_collisions();
11370 assert!(
11371 !world.contact_constraints.is_empty(),
11372 "spheres must touch the floor"
11373 );
11374 let n = world.bodies.len();
11375 check_batches(batches_of(&mut world), n);
11376 }
11377
11378 #[test]
11379 fn loom_batches_touch_disjoint_dynamic_bodies_dynamic_hub() {
11380 let mut world = PhysicsWorld::new(SolverConfig {
11383 gravity: Vec3Fix::ZERO,
11384 ..SolverConfig::default()
11385 });
11386 let hub = world.add_body(RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE));
11387 for i in 0..5i64 {
11388 let spoke = world.add_body(RigidBody::new_dynamic(v3(3 * (i + 1), 0, 0), Fix128::ONE));
11389 world.add_distance_constraint(DistanceConstraint {
11390 body_a: hub,
11391 body_b: spoke,
11392 local_anchor_a: Vec3Fix::ZERO,
11393 local_anchor_b: Vec3Fix::ZERO,
11394 target_distance: Fix128::from_int(3 * (i + 1)),
11395 compliance: Fix128::ZERO,
11396 cached_lambda: Fix128::ZERO,
11397 });
11398 }
11399 let n = world.bodies.len();
11400 let batches = batches_of(&mut world);
11401 assert_eq!(batches.len(), 5, "hub forces one batch per spoke");
11402 check_batches(batches, n);
11403 }
11404}