1use super::{fnv1a_fold, ConstraintBatch, ContactConstraint, DistanceConstraint, PhysicsWorld};
29use super::{BodyType, Broadphase, RigidBody, SolverBackend, SolverConfig};
30use crate::body_collider::BodyCollider;
31use crate::collider::{Capsule, Contact, ConvexHull, Sphere, AABB};
32use crate::compound::{CompoundChild, CompoundShape, ShapeRef};
33use crate::contact_cache::{BodyPairKey, CachedContactPoint, ContactCache, ContactManifold};
34use crate::event::{ContactEvent, ContactEventType, EventCollector, TriggerEvent};
35use crate::filter::CollisionFilter;
36use crate::force::{ForceField, ForceFieldInstance};
37use crate::joint::{
38 BallJoint, ConeTwistJoint, D6Joint, D6Motion, FixedJoint, HingeJoint, Joint, SliderJoint,
39 SpringJoint,
40};
41use crate::material::{
42 CombineRule, MaterialTable, PairOverride, PhysicsMaterial, MATERIAL_ID_CAPACITY,
43};
44use crate::math::{Fix128, QuatFix, Vec3Fix};
45use crate::semantics::PHYSICS_SEMANTICS_ID;
46use crate::shape::Shape;
47use crate::sleeping::{IslandManager, SleepConfig, SleepData, SleepState};
48use crate::static_collider::StaticCollider;
49use crate::world_participant::WorldFault;
50
51#[cfg(not(feature = "std"))]
52use alloc::collections::{BTreeMap, BTreeSet};
53#[cfg(not(feature = "std"))]
54use alloc::vec::Vec;
55#[cfg(feature = "std")]
56use std::collections::{BTreeMap, BTreeSet};
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85#[non_exhaustive]
86pub enum WorldSnapshotError {
87 Truncated,
89 BadMagic,
91 UnsupportedVersion {
94 found: u16,
96 supported: u16,
98 },
99 ReservedNotZero,
101 TrailingBytes {
103 extra: usize,
105 },
106 ChecksumMismatch {
108 stored: u64,
110 computed: u64,
112 },
113 InvalidValue {
118 section: &'static str,
120 },
121 DanglingIndex {
124 section: &'static str,
126 index: usize,
128 len: usize,
130 },
131 SdfFieldCountMismatch {
134 snapshot: usize,
136 world: usize,
138 },
139 CallbackCountMismatch {
142 section: &'static str,
144 snapshot: usize,
146 world: usize,
148 },
149 ParticipantMismatch(crate::world_participant::ParticipantMismatch),
153 ParticipantState {
157 index: usize,
159 error: crate::world_participant::StateError,
161 },
162 FieldState(crate::world_participant::StateError),
166 SemanticsMismatch {
171 stored: [u8; 32],
173 expected: [u8; 32],
175 },
176 LawIdMismatch {
179 stored: [u8; 32],
181 expected: [u8; 32],
183 },
184}
185
186#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189#[non_exhaustive]
190pub enum LawCheck {
191 Verified,
195 SemanticsVerified,
199 Unpinned,
203}
204
205struct Hex<'a>(&'a [u8; 32]);
207
208impl core::fmt::Display for Hex<'_> {
209 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210 for b in self.0 {
211 write!(f, "{b:02x}")?;
212 }
213 Ok(())
214 }
215}
216
217impl core::fmt::Display for WorldSnapshotError {
218 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219 match self {
220 Self::Truncated => write!(f, "world snapshot is truncated"),
221 Self::BadMagic => write!(f, "not a world snapshot (bad magic)"),
222 Self::UnsupportedVersion { found, supported } => write!(
223 f,
224 "world snapshot version {found} is not supported (expected {supported})"
225 ),
226 Self::ReservedNotZero => write!(f, "world snapshot reserved header bytes are not 0"),
227 Self::TrailingBytes { extra } => {
228 write!(f, "world snapshot has {extra} trailing bytes")
229 }
230 Self::ChecksumMismatch { stored, computed } => write!(
231 f,
232 "world snapshot checksum mismatch (stored {stored:#018x}, computed {computed:#018x})"
233 ),
234 Self::InvalidValue { section } => {
235 write!(f, "world snapshot has an invalid value in {section}")
236 }
237 Self::DanglingIndex { section, index, len } => write!(
238 f,
239 "world snapshot {section} refers to index {index} of {len}"
240 ),
241 Self::SdfFieldCountMismatch { snapshot, world } => write!(
242 f,
243 "world snapshot has {snapshot} SDF colliders, target world holds {world} SDF fields"
244 ),
245 Self::CallbackCountMismatch {
246 section,
247 snapshot,
248 world,
249 } => write!(
250 f,
251 "world snapshot was taken with {snapshot} {section}, target world has {world}"
252 ),
253 Self::ParticipantMismatch(m) => write!(
254 f,
255 "world snapshot participants differ from the target world's: {m:?}"
256 ),
257 Self::ParticipantState { index, error } => write!(
258 f,
259 "participant {index} of the target world refused its snapshot payload: {error:?}"
260 ),
261 Self::FieldState(error) => write!(
262 f,
263 "world snapshot fields differ from the target world's: {error:?}"
264 ),
265 Self::SemanticsMismatch { stored, expected } => write!(
266 f,
267 "world snapshot was written under semantics {}, this binary steps under {}",
268 Hex(stored),
269 Hex(expected)
270 ),
271 Self::LawIdMismatch { stored, expected } => write!(
272 f,
273 "world snapshot law id {} differs from the expected {}",
274 Hex(stored),
275 Hex(expected)
276 ),
277 }
278 }
279}
280
281#[cfg(feature = "std")]
282impl std::error::Error for WorldSnapshotError {}
283
284type Res<T> = Result<T, WorldSnapshotError>;
285
286const HEADER_LEN: usize = 16;
288const HEADER_LEN_V4: usize = HEADER_LEN + 64;
291const SEMANTICS_ID_AT: usize = HEADER_LEN;
293const LAW_ID_AT: usize = HEADER_LEN + 32;
295
296fn header_len(version: u16) -> usize {
298 if version >= 4 {
299 HEADER_LEN_V4
300 } else {
301 HEADER_LEN
302 }
303}
304const CHECKSUM_LEN: usize = 8;
305
306struct W(Vec<u8>);
309
310impl W {
311 fn u8(&mut self, v: u8) {
312 self.0.push(v);
313 }
314 fn bool(&mut self, v: bool) {
315 self.0.push(u8::from(v));
316 }
317 fn u16(&mut self, v: u16) {
318 self.0.extend_from_slice(&v.to_le_bytes());
319 }
320 fn u32(&mut self, v: u32) {
321 self.0.extend_from_slice(&v.to_le_bytes());
322 }
323 fn i32(&mut self, v: i32) {
324 self.0.extend_from_slice(&v.to_le_bytes());
325 }
326 fn u64(&mut self, v: u64) {
327 self.0.extend_from_slice(&v.to_le_bytes());
328 }
329 fn usize(&mut self, v: usize) {
330 self.u64(v as u64);
331 }
332 fn f32(&mut self, v: f32) {
333 self.u32(v.to_bits());
334 }
335 fn fix(&mut self, v: Fix128) {
336 self.0.extend_from_slice(&v.hi.to_le_bytes());
337 self.0.extend_from_slice(&v.lo.to_le_bytes());
338 }
339 fn vec3(&mut self, v: Vec3Fix) {
340 self.fix(v.x);
341 self.fix(v.y);
342 self.fix(v.z);
343 }
344 fn quat(&mut self, q: QuatFix) {
345 self.fix(q.x);
346 self.fix(q.y);
347 self.fix(q.z);
348 self.fix(q.w);
349 }
350 fn opt_fix(&mut self, v: Option<Fix128>) {
351 match v {
352 Some(x) => {
353 self.u8(1);
354 self.fix(x);
355 }
356 None => self.u8(0),
357 }
358 }
359 fn aabb(&mut self, b: AABB) {
360 self.vec3(b.min);
361 self.vec3(b.max);
362 }
363}
364
365struct R<'a> {
368 data: &'a [u8],
369 pos: usize,
370}
371
372impl<'a> R<'a> {
373 fn take(&mut self, n: usize) -> Res<&'a [u8]> {
374 let end = self
375 .pos
376 .checked_add(n)
377 .ok_or(WorldSnapshotError::Truncated)?;
378 if end > self.data.len() {
379 return Err(WorldSnapshotError::Truncated);
380 }
381 let s = &self.data[self.pos..end];
382 self.pos = end;
383 Ok(s)
384 }
385 fn arr<const N: usize>(&mut self) -> Res<[u8; N]> {
386 let mut a = [0u8; N];
387 a.copy_from_slice(self.take(N)?);
388 Ok(a)
389 }
390 fn u8(&mut self) -> Res<u8> {
391 Ok(self.take(1)?[0])
392 }
393 fn bool(&mut self, section: &'static str) -> Res<bool> {
394 match self.u8()? {
395 0 => Ok(false),
396 1 => Ok(true),
397 _ => Err(WorldSnapshotError::InvalidValue { section }),
398 }
399 }
400 fn u16(&mut self) -> Res<u16> {
401 Ok(u16::from_le_bytes(self.arr()?))
402 }
403 fn u32(&mut self) -> Res<u32> {
404 Ok(u32::from_le_bytes(self.arr()?))
405 }
406 fn i32(&mut self) -> Res<i32> {
407 Ok(i32::from_le_bytes(self.arr()?))
408 }
409 fn u64(&mut self) -> Res<u64> {
410 Ok(u64::from_le_bytes(self.arr()?))
411 }
412 fn usize(&mut self, section: &'static str) -> Res<usize> {
413 usize::try_from(self.u64()?).map_err(|_| WorldSnapshotError::InvalidValue { section })
414 }
415 fn len(&mut self, section: &'static str, min_size: usize) -> Res<usize> {
419 let n = self.usize(section)?;
420 let remaining = self.data.len() - self.pos;
421 if n.checked_mul(min_size.max(1))
422 .is_none_or(|need| need > remaining)
423 {
424 return Err(WorldSnapshotError::Truncated);
425 }
426 Ok(n)
427 }
428 fn f32(&mut self) -> Res<f32> {
429 Ok(f32::from_bits(self.u32()?))
430 }
431 fn fix(&mut self) -> Res<Fix128> {
432 let hi = i64::from_le_bytes(self.arr()?);
433 let lo = u64::from_le_bytes(self.arr()?);
434 Ok(Fix128 { hi, lo })
435 }
436 fn vec3(&mut self) -> Res<Vec3Fix> {
437 Ok(Vec3Fix::new(self.fix()?, self.fix()?, self.fix()?))
438 }
439 fn quat(&mut self) -> Res<QuatFix> {
440 Ok(QuatFix {
441 x: self.fix()?,
442 y: self.fix()?,
443 z: self.fix()?,
444 w: self.fix()?,
445 })
446 }
447 fn opt_fix(&mut self, section: &'static str) -> Res<Option<Fix128>> {
448 Ok(if self.bool(section)? {
449 Some(self.fix()?)
450 } else {
451 None
452 })
453 }
454 fn aabb(&mut self) -> Res<AABB> {
455 Ok(AABB {
456 min: self.vec3()?,
457 max: self.vec3()?,
458 })
459 }
460}
461
462const fn invalid(section: &'static str) -> WorldSnapshotError {
463 WorldSnapshotError::InvalidValue { section }
464}
465
466fn w_config(w: &mut W, c: &SolverConfig) {
469 w.usize(c.substeps);
470 w.usize(c.iterations);
471 w.vec3(c.gravity);
472 w.fix(c.damping);
473 w.fix(c.warm_start_factor);
474 w.u8(match c.solver_backend {
475 SolverBackend::Xpbd => 0,
476 SolverBackend::Tgs => 1,
477 });
478}
479
480fn r_config(r: &mut R<'_>) -> Res<SolverConfig> {
481 const S: &str = "config";
482 Ok(SolverConfig {
483 substeps: r.usize(S)?,
484 iterations: r.usize(S)?,
485 gravity: r.vec3()?,
486 damping: r.fix()?,
487 warm_start_factor: r.fix()?,
488 solver_backend: match r.u8()? {
489 0 => SolverBackend::Xpbd,
490 1 => SolverBackend::Tgs,
491 _ => return Err(invalid(S)),
492 },
493 })
494}
495
496fn w_body(w: &mut W, b: &RigidBody) {
497 w.vec3(b.position);
498 w.vec3(b.velocity);
499 w.fix(b.inv_mass);
500 w.vec3(b.inv_inertia);
501 w.vec3(b.prev_position);
502 w.quat(b.rotation);
503 w.vec3(b.angular_velocity);
504 w.quat(b.prev_rotation);
505 w.fix(b.restitution);
506 w.fix(b.friction);
507 w.fix(b.gravity_scale);
508 w.fix(b.linear_damping);
509 w.fix(b.angular_damping);
510 w.bool(b.is_sensor);
511 w.u8(b.body_type as u8);
512 match b.kinematic_target {
513 Some((p, q)) => {
514 w.u8(1);
515 w.vec3(p);
516 w.quat(q);
517 }
518 None => w.u8(0),
519 }
520}
521
522fn r_body(r: &mut R<'_>) -> Res<RigidBody> {
523 const S: &str = "bodies";
524 Ok(RigidBody {
525 position: r.vec3()?,
526 velocity: r.vec3()?,
527 inv_mass: r.fix()?,
528 inv_inertia: r.vec3()?,
529 prev_position: r.vec3()?,
530 rotation: r.quat()?,
531 angular_velocity: r.vec3()?,
532 prev_rotation: r.quat()?,
533 restitution: r.fix()?,
534 friction: r.fix()?,
535 gravity_scale: r.fix()?,
536 linear_damping: r.fix()?,
537 angular_damping: r.fix()?,
538 is_sensor: r.bool(S)?,
539 body_type: match r.u8()? {
540 0 => BodyType::Dynamic,
541 1 => BodyType::Static,
542 2 => BodyType::Kinematic,
543 _ => return Err(invalid(S)),
544 },
545 kinematic_target: if r.bool(S)? {
546 Some((r.vec3()?, r.quat()?))
547 } else {
548 None
549 },
550 })
551}
552
553fn w_contact(w: &mut W, c: &Contact) {
554 w.fix(c.depth);
555 w.vec3(c.normal);
556 w.vec3(c.point_a);
557 w.vec3(c.point_b);
558}
559
560fn r_contact(r: &mut R<'_>) -> Res<Contact> {
561 Ok(Contact {
562 depth: r.fix()?,
563 normal: r.vec3()?,
564 point_a: r.vec3()?,
565 point_b: r.vec3()?,
566 })
567}
568
569fn w_joint(w: &mut W, j: &Joint) {
570 match j {
571 Joint::Ball(j) => {
572 w.u8(0);
573 w.usize(j.body_a);
574 w.usize(j.body_b);
575 w.vec3(j.local_anchor_a);
576 w.vec3(j.local_anchor_b);
577 w.fix(j.compliance);
578 w.opt_fix(j.break_force);
579 }
580 Joint::Hinge(j) => {
581 w.u8(1);
582 w.usize(j.body_a);
583 w.usize(j.body_b);
584 w.vec3(j.local_anchor_a);
585 w.vec3(j.local_anchor_b);
586 w.vec3(j.local_axis_a);
587 w.vec3(j.local_axis_b);
588 w.opt_fix(j.angle_min);
589 w.opt_fix(j.angle_max);
590 w.fix(j.compliance);
591 w.fix(j.angular_compliance);
592 w.opt_fix(j.break_force);
593 }
594 Joint::Fixed(j) => {
595 w.u8(2);
596 w.usize(j.body_a);
597 w.usize(j.body_b);
598 w.vec3(j.local_anchor_a);
599 w.vec3(j.local_anchor_b);
600 w.quat(j.relative_rotation);
601 w.fix(j.compliance);
602 w.fix(j.angular_compliance);
603 w.opt_fix(j.break_force);
604 }
605 Joint::Slider(j) => {
606 w.u8(3);
607 w.usize(j.body_a);
608 w.usize(j.body_b);
609 w.vec3(j.local_axis);
610 w.vec3(j.local_anchor_a);
611 w.vec3(j.local_anchor_b);
612 w.opt_fix(j.limit_min);
613 w.opt_fix(j.limit_max);
614 w.fix(j.compliance);
615 w.opt_fix(j.break_force);
616 }
617 Joint::Spring(j) => {
618 w.u8(4);
619 w.usize(j.body_a);
620 w.usize(j.body_b);
621 w.vec3(j.local_anchor_a);
622 w.vec3(j.local_anchor_b);
623 w.fix(j.rest_length);
624 w.fix(j.stiffness);
625 w.fix(j.damping);
626 w.opt_fix(j.break_force);
627 }
628 Joint::D6(j) => {
629 w.u8(5);
630 w.usize(j.body_a);
631 w.usize(j.body_b);
632 w.vec3(j.local_anchor_a);
633 w.vec3(j.local_anchor_b);
634 w.quat(j.local_frame_a);
635 w.quat(j.local_frame_b);
636 for m in [
637 j.linear_x,
638 j.linear_y,
639 j.linear_z,
640 j.angular_x,
641 j.angular_y,
642 j.angular_z,
643 ] {
644 w.u8(match m {
645 D6Motion::Locked => 0,
646 D6Motion::Free => 1,
647 D6Motion::Limited => 2,
648 });
649 }
650 w.vec3(j.linear_limit_min);
651 w.vec3(j.linear_limit_max);
652 w.vec3(j.angular_limit_min);
653 w.vec3(j.angular_limit_max);
654 w.fix(j.compliance);
655 w.fix(j.angular_compliance);
656 w.opt_fix(j.break_force);
657 }
658 Joint::ConeTwist(j) => {
659 w.u8(6);
660 w.usize(j.body_a);
661 w.usize(j.body_b);
662 w.vec3(j.local_anchor_a);
663 w.vec3(j.local_anchor_b);
664 w.vec3(j.twist_axis_a);
665 w.vec3(j.twist_axis_b);
666 w.fix(j.cone_limit);
667 w.fix(j.twist_limit);
668 w.fix(j.compliance);
669 w.fix(j.angular_compliance);
670 w.opt_fix(j.break_force);
671 }
672 }
673}
674
675fn r_d6_motion(r: &mut R<'_>) -> Res<D6Motion> {
676 match r.u8()? {
677 0 => Ok(D6Motion::Locked),
678 1 => Ok(D6Motion::Free),
679 2 => Ok(D6Motion::Limited),
680 _ => Err(invalid("joints")),
681 }
682}
683
684fn r_joint(r: &mut R<'_>) -> Res<Joint> {
685 const S: &str = "joints";
686 Ok(match r.u8()? {
687 0 => Joint::Ball(BallJoint {
688 body_a: r.usize(S)?,
689 body_b: r.usize(S)?,
690 local_anchor_a: r.vec3()?,
691 local_anchor_b: r.vec3()?,
692 compliance: r.fix()?,
693 break_force: r.opt_fix(S)?,
694 }),
695 1 => Joint::Hinge(HingeJoint {
696 body_a: r.usize(S)?,
697 body_b: r.usize(S)?,
698 local_anchor_a: r.vec3()?,
699 local_anchor_b: r.vec3()?,
700 local_axis_a: r.vec3()?,
701 local_axis_b: r.vec3()?,
702 angle_min: r.opt_fix(S)?,
703 angle_max: r.opt_fix(S)?,
704 compliance: r.fix()?,
705 angular_compliance: r.fix()?,
706 break_force: r.opt_fix(S)?,
707 }),
708 2 => Joint::Fixed(FixedJoint {
709 body_a: r.usize(S)?,
710 body_b: r.usize(S)?,
711 local_anchor_a: r.vec3()?,
712 local_anchor_b: r.vec3()?,
713 relative_rotation: r.quat()?,
714 compliance: r.fix()?,
715 angular_compliance: r.fix()?,
716 break_force: r.opt_fix(S)?,
717 }),
718 3 => Joint::Slider(SliderJoint {
719 body_a: r.usize(S)?,
720 body_b: r.usize(S)?,
721 local_axis: r.vec3()?,
722 local_anchor_a: r.vec3()?,
723 local_anchor_b: r.vec3()?,
724 limit_min: r.opt_fix(S)?,
725 limit_max: r.opt_fix(S)?,
726 compliance: r.fix()?,
727 break_force: r.opt_fix(S)?,
728 }),
729 4 => Joint::Spring(SpringJoint {
730 body_a: r.usize(S)?,
731 body_b: r.usize(S)?,
732 local_anchor_a: r.vec3()?,
733 local_anchor_b: r.vec3()?,
734 rest_length: r.fix()?,
735 stiffness: r.fix()?,
736 damping: r.fix()?,
737 break_force: r.opt_fix(S)?,
738 }),
739 5 => Joint::D6(D6Joint {
740 body_a: r.usize(S)?,
741 body_b: r.usize(S)?,
742 local_anchor_a: r.vec3()?,
743 local_anchor_b: r.vec3()?,
744 local_frame_a: r.quat()?,
745 local_frame_b: r.quat()?,
746 linear_x: r_d6_motion(r)?,
747 linear_y: r_d6_motion(r)?,
748 linear_z: r_d6_motion(r)?,
749 angular_x: r_d6_motion(r)?,
750 angular_y: r_d6_motion(r)?,
751 angular_z: r_d6_motion(r)?,
752 linear_limit_min: r.vec3()?,
753 linear_limit_max: r.vec3()?,
754 angular_limit_min: r.vec3()?,
755 angular_limit_max: r.vec3()?,
756 compliance: r.fix()?,
757 angular_compliance: r.fix()?,
758 break_force: r.opt_fix(S)?,
759 }),
760 6 => Joint::ConeTwist(ConeTwistJoint {
761 body_a: r.usize(S)?,
762 body_b: r.usize(S)?,
763 local_anchor_a: r.vec3()?,
764 local_anchor_b: r.vec3()?,
765 twist_axis_a: r.vec3()?,
766 twist_axis_b: r.vec3()?,
767 cone_limit: r.fix()?,
768 twist_limit: r.fix()?,
769 compliance: r.fix()?,
770 angular_compliance: r.fix()?,
771 break_force: r.opt_fix(S)?,
772 }),
773 _ => return Err(invalid(S)),
774 })
775}
776
777fn w_force_field(w: &mut W, f: &ForceFieldInstance) {
778 match f.field {
779 ForceField::Directional {
780 direction,
781 strength,
782 } => {
783 w.u8(0);
784 w.vec3(direction);
785 w.fix(strength);
786 }
787 ForceField::Point {
788 center,
789 strength,
790 repulsive,
791 max_force,
792 } => {
793 w.u8(1);
794 w.vec3(center);
795 w.fix(strength);
796 w.bool(repulsive);
797 w.fix(max_force);
798 }
799 ForceField::Drag { coefficient } => {
800 w.u8(2);
801 w.fix(coefficient);
802 }
803 ForceField::Buoyancy {
804 surface_y,
805 density,
806 drag,
807 } => {
808 w.u8(3);
809 w.fix(surface_y);
810 w.fix(density);
811 w.fix(drag);
812 }
813 ForceField::Vortex {
814 center,
815 axis,
816 strength,
817 falloff_radius,
818 } => {
819 w.u8(4);
820 w.vec3(center);
821 w.vec3(axis);
822 w.fix(strength);
823 w.fix(falloff_radius);
824 }
825 ForceField::Explosion {
826 center,
827 strength,
828 radius,
829 falloff_power,
830 } => {
831 w.u8(5);
832 w.vec3(center);
833 w.fix(strength);
834 w.fix(radius);
835 w.fix(falloff_power);
836 }
837 ForceField::Magnetic {
838 position,
839 moment,
840 strength,
841 } => {
842 w.u8(6);
843 w.vec3(position);
844 w.vec3(moment);
845 w.fix(strength);
846 }
847 }
848 match &f.affected_bodies {
849 Some(list) => {
850 w.u8(1);
851 w.usize(list.len());
852 for &i in list {
853 w.usize(i);
854 }
855 }
856 None => w.u8(0),
857 }
858 w.bool(f.enabled);
859}
860
861fn r_force_field(r: &mut R<'_>) -> Res<ForceFieldInstance> {
862 const S: &str = "force_fields";
863 let field = match r.u8()? {
864 0 => ForceField::Directional {
865 direction: r.vec3()?,
866 strength: r.fix()?,
867 },
868 1 => ForceField::Point {
869 center: r.vec3()?,
870 strength: r.fix()?,
871 repulsive: r.bool(S)?,
872 max_force: r.fix()?,
873 },
874 2 => ForceField::Drag {
875 coefficient: r.fix()?,
876 },
877 3 => ForceField::Buoyancy {
878 surface_y: r.fix()?,
879 density: r.fix()?,
880 drag: r.fix()?,
881 },
882 4 => ForceField::Vortex {
883 center: r.vec3()?,
884 axis: r.vec3()?,
885 strength: r.fix()?,
886 falloff_radius: r.fix()?,
887 },
888 5 => ForceField::Explosion {
889 center: r.vec3()?,
890 strength: r.fix()?,
891 radius: r.fix()?,
892 falloff_power: r.fix()?,
893 },
894 6 => ForceField::Magnetic {
895 position: r.vec3()?,
896 moment: r.vec3()?,
897 strength: r.fix()?,
898 },
899 _ => return Err(invalid(S)),
900 };
901 let affected_bodies = if r.bool(S)? {
902 let n = r.len(S, 8)?;
903 let mut v = Vec::with_capacity(n);
904 for _ in 0..n {
905 v.push(r.usize(S)?);
906 }
907 Some(v)
908 } else {
909 None
910 };
911 Ok(ForceFieldInstance {
912 field,
913 affected_bodies,
914 enabled: r.bool(S)?,
915 })
916}
917
918fn w_static_collider(w: &mut W, c: &StaticCollider) {
919 match c {
920 StaticCollider::Plane(p) => {
921 w.u8(0);
922 w.vec3(p.normal);
923 w.fix(p.offset);
924 }
925 StaticCollider::HeightField(h) => {
926 w.u8(1);
927 w.usize(h.heights.len());
928 for &v in &h.heights {
929 w.fix(v);
930 }
931 w.u32(h.width);
932 w.u32(h.depth);
933 w.fix(h.spacing);
934 w.vec3(h.origin);
935 }
936 StaticCollider::TriMesh(m) => {
937 w.u8(2);
938 w.usize(m.triangles.len());
939 for t in &m.triangles {
940 w.vec3(t.v0);
941 w.vec3(t.v1);
942 w.vec3(t.v2);
943 }
944 w.usize(m.bvh.nodes.len());
945 for n in &m.bvh.nodes {
946 for v in n.aabb_min {
947 w.i32(v);
948 }
949 w.u32(n.first_child_or_prim);
950 for v in n.aabb_max {
951 w.i32(v);
952 }
953 w.u32(n.prim_count_escape);
954 }
955 w.usize(m.bvh.primitives.len());
956 for &p in &m.bvh.primitives {
957 w.u32(p);
958 }
959 w.aabb(m.bvh.bounds);
960 w.aabb(m.bounds);
961 }
962 }
963}
964
965fn r_static_collider(r: &mut R<'_>) -> Res<StaticCollider> {
966 const S: &str = "static_colliders";
967 Ok(match r.u8()? {
968 0 => StaticCollider::Plane(crate::plane_collider::PlaneCollider {
969 normal: r.vec3()?,
970 offset: r.fix()?,
971 }),
972 1 => {
973 let n = r.len(S, 16)?;
974 let mut heights = Vec::with_capacity(n);
975 for _ in 0..n {
976 heights.push(r.fix()?);
977 }
978 StaticCollider::HeightField(crate::heightfield::HeightField {
979 heights,
980 width: r.u32()?,
981 depth: r.u32()?,
982 spacing: r.fix()?,
983 origin: r.vec3()?,
984 })
985 }
986 2 => {
987 let n = r.len(S, 144)?;
988 let mut triangles = Vec::with_capacity(n);
989 for _ in 0..n {
990 triangles.push(crate::trimesh::Triangle {
991 v0: r.vec3()?,
992 v1: r.vec3()?,
993 v2: r.vec3()?,
994 });
995 }
996 let n = r.len(S, 32)?;
997 let mut nodes = Vec::with_capacity(n);
998 for _ in 0..n {
999 let aabb_min = [r.i32()?, r.i32()?, r.i32()?];
1000 let first_child_or_prim = r.u32()?;
1001 let aabb_max = [r.i32()?, r.i32()?, r.i32()?];
1002 let prim_count_escape = r.u32()?;
1003 nodes.push(crate::bvh::BvhNode {
1004 aabb_min,
1005 first_child_or_prim,
1006 aabb_max,
1007 prim_count_escape,
1008 });
1009 }
1010 let n = r.len(S, 4)?;
1011 let mut primitives = Vec::with_capacity(n);
1012 for _ in 0..n {
1013 primitives.push(r.u32()?);
1014 }
1015 let bvh = crate::bvh::LinearBvh {
1016 nodes,
1017 primitives,
1018 bounds: r.aabb()?,
1019 };
1020 StaticCollider::TriMesh(crate::trimesh::TriMesh {
1021 triangles,
1022 bvh,
1023 bounds: r.aabb()?,
1024 })
1025 }
1026 _ => return Err(invalid(S)),
1027 })
1028}
1029
1030fn w_shape(w: &mut W, s: &Shape) {
1031 match *s {
1032 Shape::Box { half_extents } => {
1033 w.u8(0);
1034 w.vec3(half_extents);
1035 }
1036 Shape::Cylinder {
1037 radius,
1038 half_height,
1039 } => {
1040 w.u8(1);
1041 w.fix(radius);
1042 w.fix(half_height);
1043 }
1044 Shape::Cone {
1045 radius,
1046 half_height,
1047 } => {
1048 w.u8(2);
1049 w.fix(radius);
1050 w.fix(half_height);
1051 }
1052 Shape::Ellipsoid { radii } => {
1053 w.u8(3);
1054 w.vec3(radii);
1055 }
1056 Shape::Wedge {
1057 width,
1058 height,
1059 depth,
1060 } => {
1061 w.u8(4);
1062 w.fix(width);
1063 w.fix(height);
1064 w.fix(depth);
1065 }
1066 Shape::Torus {
1067 major_radius,
1068 minor_radius,
1069 } => {
1070 w.u8(5);
1071 w.fix(major_radius);
1072 w.fix(minor_radius);
1073 }
1074 }
1075}
1076
1077fn r_shape(r: &mut R<'_>) -> Res<Shape> {
1078 Ok(match r.u8()? {
1079 0 => Shape::Box {
1080 half_extents: r.vec3()?,
1081 },
1082 1 => Shape::Cylinder {
1083 radius: r.fix()?,
1084 half_height: r.fix()?,
1085 },
1086 2 => Shape::Cone {
1087 radius: r.fix()?,
1088 half_height: r.fix()?,
1089 },
1090 3 => Shape::Ellipsoid { radii: r.vec3()? },
1091 4 => Shape::Wedge {
1092 width: r.fix()?,
1093 height: r.fix()?,
1094 depth: r.fix()?,
1095 },
1096 5 => Shape::Torus {
1097 major_radius: r.fix()?,
1098 minor_radius: r.fix()?,
1099 },
1100 _ => return Err(invalid("body_colliders")),
1101 })
1102}
1103
1104fn w_body_collider(w: &mut W, c: Option<&BodyCollider>) {
1105 match c {
1106 None => w.u8(0),
1107 Some(BodyCollider::Shape(s)) => {
1108 w.u8(1);
1109 w_shape(w, s);
1110 }
1111 Some(BodyCollider::Compound(c)) => {
1112 w.u8(2);
1113 w.usize(c.children.len());
1114 for ch in &c.children {
1115 match &ch.shape {
1116 ShapeRef::Sphere(s) => {
1117 w.u8(0);
1118 w.vec3(s.center);
1119 w.fix(s.radius);
1120 }
1121 ShapeRef::Capsule(s) => {
1122 w.u8(1);
1123 w.vec3(s.a);
1124 w.vec3(s.b);
1125 w.fix(s.radius);
1126 }
1127 ShapeRef::Box(b) => {
1128 w.u8(2);
1129 w.vec3(b.center);
1130 w.vec3(b.half_extents);
1131 w.quat(b.rotation);
1132 }
1133 ShapeRef::ConvexHull(h) => {
1134 w.u8(3);
1135 w.usize(h.vertices.len());
1136 for &v in &h.vertices {
1137 w.vec3(v);
1138 }
1139 }
1140 }
1141 w.vec3(ch.local_position);
1142 w.quat(ch.local_rotation);
1143 }
1144 w.aabb(c.cached_aabb);
1145 w.bool(c.dirty);
1146 }
1147 }
1148}
1149
1150fn r_body_collider(r: &mut R<'_>) -> Res<Option<BodyCollider>> {
1151 const S: &str = "body_colliders";
1152 Ok(match r.u8()? {
1153 0 => None,
1154 1 => Some(BodyCollider::Shape(r_shape(r)?)),
1155 2 => {
1156 let n = r.len(S, 1)?;
1157 let mut children = Vec::with_capacity(n);
1158 for _ in 0..n {
1159 let shape = match r.u8()? {
1160 0 => ShapeRef::Sphere(Sphere {
1161 center: r.vec3()?,
1162 radius: r.fix()?,
1163 }),
1164 1 => ShapeRef::Capsule(Capsule {
1165 a: r.vec3()?,
1166 b: r.vec3()?,
1167 radius: r.fix()?,
1168 }),
1169 2 => ShapeRef::Box(crate::box_collider::OrientedBox {
1170 center: r.vec3()?,
1171 half_extents: r.vec3()?,
1172 rotation: r.quat()?,
1173 }),
1174 3 => {
1175 let m = r.len(S, 48)?;
1176 let mut vertices = Vec::with_capacity(m);
1177 for _ in 0..m {
1178 vertices.push(r.vec3()?);
1179 }
1180 ShapeRef::ConvexHull(ConvexHull { vertices })
1181 }
1182 _ => return Err(invalid(S)),
1183 };
1184 children.push(CompoundChild {
1185 shape,
1186 local_position: r.vec3()?,
1187 local_rotation: r.quat()?,
1188 });
1189 }
1190 let mut c = CompoundShape::new();
1191 c.children = children;
1192 c.cached_aabb = r.aabb()?;
1193 c.dirty = r.bool(S)?;
1194 Some(BodyCollider::Compound(c))
1195 }
1196 _ => return Err(invalid(S)),
1197 })
1198}
1199
1200fn w_contact_cache(w: &mut W, c: &ContactCache) {
1201 w.usize(c.manifolds.len());
1202 for m in &c.manifolds {
1203 w.u32(m.pair.body_a);
1204 w.u32(m.pair.body_b);
1205 w.usize(m.points.len());
1206 for p in &m.points {
1207 w.vec3(p.local_point_a);
1208 w.vec3(p.local_point_b);
1209 w.vec3(p.normal);
1210 w.fix(p.depth);
1211 w.fix(p.lambda_n);
1212 w.fix(p.lambda_t1);
1213 w.fix(p.lambda_t2);
1214 w.u32(p.age);
1215 }
1216 w.vec3(m.normal);
1217 w.fix(m.friction);
1218 w.fix(m.restitution);
1219 w.u32(m.stale_frames);
1220 }
1221 w.u32(c.max_stale_frames);
1222 w.fix(c.warm_start_factor);
1223}
1224
1225fn r_contact_cache(r: &mut R<'_>) -> Res<ContactCache> {
1226 const S: &str = "contact_cache";
1227 let n = r.len(S, 8)?;
1228 let mut manifolds = Vec::with_capacity(n);
1229 for _ in 0..n {
1230 let pair = BodyPairKey {
1231 body_a: r.u32()?,
1232 body_b: r.u32()?,
1233 };
1234 let k = r.len(S, 212)?;
1235 let mut points = Vec::with_capacity(k);
1236 for _ in 0..k {
1237 points.push(CachedContactPoint {
1238 local_point_a: r.vec3()?,
1239 local_point_b: r.vec3()?,
1240 normal: r.vec3()?,
1241 depth: r.fix()?,
1242 lambda_n: r.fix()?,
1243 lambda_t1: r.fix()?,
1244 lambda_t2: r.fix()?,
1245 age: r.u32()?,
1246 });
1247 }
1248 manifolds.push(ContactManifold {
1249 pair,
1250 points,
1251 normal: r.vec3()?,
1252 friction: r.fix()?,
1253 restitution: r.fix()?,
1254 stale_frames: r.u32()?,
1255 });
1256 }
1257 let mut cache = ContactCache::new();
1258 cache.manifolds = manifolds;
1259 cache.max_stale_frames = r.u32()?;
1260 cache.warm_start_factor = r.fix()?;
1261 #[cfg(feature = "std")]
1263 {
1264 cache.pair_index.clear();
1265 for (idx, m) in cache.manifolds.iter().enumerate() {
1266 cache.pair_index.insert(m.pair, idx);
1267 }
1268 }
1269 Ok(cache)
1270}
1271
1272const fn combine_tag(c: CombineRule) -> u8 {
1273 match c {
1274 CombineRule::Average => 0,
1275 CombineRule::Min => 1,
1276 CombineRule::Max => 2,
1277 CombineRule::Multiply => 3,
1278 }
1279}
1280
1281fn r_combine(r: &mut R<'_>) -> Res<CombineRule> {
1282 match r.u8()? {
1283 0 => Ok(CombineRule::Average),
1284 1 => Ok(CombineRule::Min),
1285 2 => Ok(CombineRule::Max),
1286 3 => Ok(CombineRule::Multiply),
1287 _ => Err(invalid("material_table")),
1288 }
1289}
1290
1291fn w_material_table(w: &mut W, t: &MaterialTable) {
1292 w.usize(t.materials.len());
1293 for m in &t.materials {
1294 w.u16(m.id);
1295 w.fix(m.static_friction);
1296 w.fix(m.dynamic_friction);
1297 w.fix(m.restitution);
1298 w.u8(combine_tag(m.friction_combine));
1299 w.u8(combine_tag(m.restitution_combine));
1300 }
1301 w.usize(t.pair_overrides.len());
1302 for p in &t.pair_overrides {
1303 w.u16(p.mat_a);
1304 w.u16(p.mat_b);
1305 w.fix(p.friction);
1306 w.fix(p.restitution);
1307 }
1308 w.u8(combine_tag(t.default_friction_combine));
1309 w.u8(combine_tag(t.default_restitution_combine));
1310}
1311
1312fn r_material_table(r: &mut R<'_>) -> Res<MaterialTable> {
1318 const S: &str = "material_table";
1319 let n = r.len(S, 52)?;
1320 if n == 0 || n > MATERIAL_ID_CAPACITY {
1323 return Err(invalid(S));
1324 }
1325 let mut materials = Vec::with_capacity(n);
1326 for i in 0..n {
1327 let id = r.u16()?;
1328 if usize::from(id) != i {
1329 return Err(invalid(S));
1330 }
1331 materials.push(PhysicsMaterial {
1332 id,
1333 static_friction: r.fix()?,
1334 dynamic_friction: r.fix()?,
1335 restitution: r.fix()?,
1336 friction_combine: r_combine(r)?,
1337 restitution_combine: r_combine(r)?,
1338 });
1339 }
1340 let n = r.len(S, 36)?;
1341 let mut pair_overrides = Vec::with_capacity(n);
1342 for _ in 0..n {
1343 pair_overrides.push(PairOverride {
1344 mat_a: r.u16()?,
1345 mat_b: r.u16()?,
1346 friction: r.fix()?,
1347 restitution: r.fix()?,
1348 });
1349 }
1350 let mut t = MaterialTable::new();
1351 t.materials = materials;
1352 t.pair_overrides = pair_overrides;
1353 t.default_friction_combine = r_combine(r)?;
1354 t.default_restitution_combine = r_combine(r)?;
1355 Ok(t)
1356}
1357
1358fn w_pair(w: &mut W, p: (usize, usize)) {
1359 w.usize(p.0);
1360 w.usize(p.1);
1361}
1362
1363fn r_pair(r: &mut R<'_>) -> Res<(usize, usize)> {
1364 Ok((r.usize("events")?, r.usize("events")?))
1365}
1366
1367fn w_events(w: &mut W, e: &EventCollector) {
1368 w.usize(e.contact_events.len());
1369 for c in &e.contact_events {
1370 w.usize(c.body_a);
1371 w.usize(c.body_b);
1372 w.u8(match c.event_type {
1373 ContactEventType::Begin => 0,
1374 ContactEventType::Persist => 1,
1375 ContactEventType::End => 2,
1376 });
1377 w.vec3(c.normal);
1378 w.vec3(c.point);
1379 w.fix(c.depth);
1380 w.fix(c.relative_velocity);
1381 }
1382 w.usize(e.trigger_events.len());
1383 for t in &e.trigger_events {
1384 w.usize(t.trigger_body);
1385 w.usize(t.other_body);
1386 w.bool(t.entered);
1387 }
1388 w.usize(e.prev_pairs.len());
1389 for &p in &e.prev_pairs {
1390 w_pair(w, p);
1391 }
1392 w.usize(e.curr_pairs.len());
1393 for &p in &e.curr_pairs {
1394 w_pair(w, p);
1395 }
1396 w.usize(e.prev_triggers.len());
1397 for &(k, v) in &e.prev_triggers {
1398 w_pair(w, k);
1399 w_pair(w, v);
1400 }
1401 w.usize(e.curr_triggers.len());
1402 for (&k, &v) in &e.curr_triggers {
1403 w_pair(w, k);
1404 w_pair(w, v);
1405 }
1406}
1407
1408fn r_events(r: &mut R<'_>) -> Res<EventCollector> {
1409 const S: &str = "events";
1410 let mut e = EventCollector::new();
1411 let n = r.len(S, 145)?;
1412 for _ in 0..n {
1413 e.contact_events.push(ContactEvent {
1414 body_a: r.usize(S)?,
1415 body_b: r.usize(S)?,
1416 event_type: match r.u8()? {
1417 0 => ContactEventType::Begin,
1418 1 => ContactEventType::Persist,
1419 2 => ContactEventType::End,
1420 _ => return Err(invalid(S)),
1421 },
1422 normal: r.vec3()?,
1423 point: r.vec3()?,
1424 depth: r.fix()?,
1425 relative_velocity: r.fix()?,
1426 });
1427 }
1428 let n = r.len(S, 17)?;
1429 for _ in 0..n {
1430 e.trigger_events.push(TriggerEvent {
1431 trigger_body: r.usize(S)?,
1432 other_body: r.usize(S)?,
1433 entered: r.bool(S)?,
1434 });
1435 }
1436 let n = r.len(S, 16)?;
1437 for _ in 0..n {
1438 e.prev_pairs.push(r_pair(r)?);
1439 }
1440 let n = r.len(S, 16)?;
1441 let mut curr_pairs = BTreeSet::new();
1442 for _ in 0..n {
1443 curr_pairs.insert(r_pair(r)?);
1444 }
1445 e.curr_pairs = curr_pairs;
1446 let n = r.len(S, 32)?;
1447 for _ in 0..n {
1448 let k = r_pair(r)?;
1449 let v = r_pair(r)?;
1450 e.prev_triggers.push((k, v));
1451 }
1452 let n = r.len(S, 32)?;
1453 let mut curr_triggers = BTreeMap::new();
1454 for _ in 0..n {
1455 let k = r_pair(r)?;
1456 let v = r_pair(r)?;
1457 curr_triggers.insert(k, v);
1458 }
1459 e.curr_triggers = curr_triggers;
1460 Ok(e)
1461}
1462
1463fn w_tree(w: &mut W, t: &crate::dynamic_bvh::DynamicAabbTree) {
1464 w.usize(t.nodes.len());
1465 for n in &t.nodes {
1466 w.aabb(n.aabb);
1467 w.u32(n.parent);
1468 w.u32(n.left);
1469 w.u32(n.right);
1470 w.i32(n.height);
1471 w.u32(n.user_data);
1472 w.bool(n.is_leaf);
1473 }
1474 w.usize(t.free_list.len());
1475 for &f in &t.free_list {
1476 w.u32(f);
1477 }
1478 w.u32(t.root);
1479 w.fix(t.margin);
1480 let (l1, l2, linf) = t.metric.weights();
1481 w.fix(l1);
1482 w.fix(l2);
1483 w.fix(linf);
1484}
1485
1486fn r_tree(r: &mut R<'_>) -> Res<crate::dynamic_bvh::DynamicAabbTree> {
1487 const S: &str = "broadphase_tree";
1488 let mut t = crate::dynamic_bvh::DynamicAabbTree::new();
1489 let n = r.len(S, 117)?;
1490 for _ in 0..n {
1491 t.nodes.push(crate::dynamic_bvh::DynamicNode {
1492 aabb: r.aabb()?,
1493 parent: r.u32()?,
1494 left: r.u32()?,
1495 right: r.u32()?,
1496 height: r.i32()?,
1497 user_data: r.u32()?,
1498 is_leaf: r.bool(S)?,
1499 });
1500 }
1501 let n = r.len(S, 4)?;
1502 for _ in 0..n {
1503 t.free_list.push(r.u32()?);
1504 }
1505 t.root = r.u32()?;
1506 t.margin = r.fix()?;
1507 let (l1, l2, linf) = (r.fix()?, r.fix()?, r.fix()?);
1508 t.metric = crate::metric::MetricWeights::new(l1, l2, linf).map_err(|_| invalid(S))?;
1509 Ok(t)
1510}
1511
1512struct Decoded {
1516 config: SolverConfig,
1517 bodies: Vec<RigidBody>,
1518 distance_constraints: Vec<DistanceConstraint>,
1519 contact_constraints: Vec<ContactConstraint>,
1520 sdf_poses: Vec<(Vec3Fix, QuatFix, Fix128, usize, QuatFix, f32, f32)>,
1522 sdf_collision_radius: Fix128,
1523 static_colliders: Vec<StaticCollider>,
1524 constraint_batches: Vec<ConstraintBatch>,
1525 batches_dirty: bool,
1526 batch_static_bodies: Vec<bool>,
1527 contact_cache: ContactCache,
1528 material_table: MaterialTable,
1529 body_materials: Vec<u16>,
1530 pre_solve_hooks: usize,
1531 contact_modifiers: usize,
1532 gpu_solver_bridge: usize,
1533 joints: Vec<Joint>,
1534 force_fields: Vec<ForceFieldInstance>,
1535 events: EventCollector,
1536 sleep_config: SleepConfig,
1537 sleep_data: Vec<SleepData>,
1538 body_collision_radii: Vec<Option<Fix128>>,
1539 broadphase: Broadphase,
1540 broadphase_tree: crate::dynamic_bvh::DynamicAabbTree,
1541 broadphase_proxies: Vec<Option<u32>>,
1542 body_colliders: Vec<Option<BodyCollider>>,
1543 body_filters: Vec<CollisionFilter>,
1544 overflow_detected: bool,
1545 tgs_cache: (Vec<(u64, [Fix128; 3])>, u64, u64),
1547 participants: Vec<(u32, Vec<u8>)>,
1549 fault: Option<WorldFault>,
1551 fields: Vec<u8>,
1554 ccd: super::WorldCcdConfig,
1556 ids: Option<([u8; 32], [u8; 32])>,
1558}
1559
1560const SDF_STATIC_TAG: u8 = 0;
1563
1564impl PhysicsWorld {
1565 pub const WORLD_SNAPSHOT_MAGIC: [u8; 4] = *b"APWS";
1568
1569 pub const WORLD_SNAPSHOT_VERSION: u16 = 4;
1579
1580 #[must_use]
1706 pub fn snapshot_world(&self) -> Vec<u8> {
1707 let mut w = W(Vec::new());
1708 w.0.extend_from_slice(&Self::WORLD_SNAPSHOT_MAGIC);
1709 w.u16(Self::WORLD_SNAPSHOT_VERSION);
1710 w.u16(0);
1711 w.u64(0); w.0.extend_from_slice(&PHYSICS_SEMANTICS_ID);
1713 w.0.extend_from_slice(&self.law_id(&PHYSICS_SEMANTICS_ID));
1714
1715 w_config(&mut w, &self.config);
1716
1717 w.usize(self.bodies.len());
1718 for b in &self.bodies {
1719 w_body(&mut w, b);
1720 }
1721
1722 w.usize(self.distance_constraints.len());
1723 for c in &self.distance_constraints {
1724 w.usize(c.body_a);
1725 w.usize(c.body_b);
1726 w.vec3(c.local_anchor_a);
1727 w.vec3(c.local_anchor_b);
1728 w.fix(c.target_distance);
1729 w.fix(c.compliance);
1730 w.fix(c.cached_lambda);
1731 }
1732
1733 w.usize(self.contact_constraints.len());
1734 for c in &self.contact_constraints {
1735 w.usize(c.body_a);
1736 w.usize(c.body_b);
1737 w_contact(&mut w, &c.contact);
1738 w.fix(c.friction);
1739 w.fix(c.restitution);
1740 w.fix(c.cached_lambda);
1741 }
1742
1743 w.usize(self.sdf_colliders.len());
1744 for s in &self.sdf_colliders {
1745 w.vec3(s.position);
1746 w.quat(s.rotation);
1747 w.fix(s.scale);
1748 if s.body_index == crate::sdf_collider::SDF_STATIC {
1749 w.u8(SDF_STATIC_TAG);
1750 } else {
1751 w.u8(1);
1752 w.usize(s.body_index);
1753 }
1754 w.quat(s.inv_rotation);
1755 w.f32(s.scale_f32);
1756 w.f32(s.inv_scale_f32);
1757 }
1758 w.fix(self.sdf_collision_radius);
1759
1760 w.usize(self.static_colliders.len());
1761 for c in &self.static_colliders {
1762 w_static_collider(&mut w, c);
1763 }
1764
1765 w.usize(self.constraint_batches.len());
1766 for b in &self.constraint_batches {
1767 w.usize(b.distance_indices.len());
1768 for &i in &b.distance_indices {
1769 w.usize(i);
1770 }
1771 w.usize(b.contact_indices.len());
1772 for &i in &b.contact_indices {
1773 w.usize(i);
1774 }
1775 }
1776 w.bool(self.batches_dirty);
1777 w.usize(self.batch_static_bodies.len());
1778 for &s in &self.batch_static_bodies {
1779 w.bool(s);
1780 }
1781
1782 w_contact_cache(&mut w, &self.contact_cache);
1783 w_material_table(&mut w, &self.material_table);
1784 w.usize(self.body_materials.len());
1785 for &m in &self.body_materials {
1786 w.u16(m);
1787 }
1788
1789 #[cfg(feature = "std")]
1791 {
1792 w.usize(self.pre_solve_hooks.len());
1793 w.usize(self.contact_modifiers.len());
1794 }
1795 #[cfg(not(feature = "std"))]
1796 {
1797 w.usize(0);
1798 w.usize(0);
1799 }
1800 #[cfg(feature = "gpu-solver-bridge")]
1801 w.usize(usize::from(self.gpu_solver_bridge.is_some()));
1802 #[cfg(not(feature = "gpu-solver-bridge"))]
1803 w.usize(0);
1804
1805 w.usize(self.joints.len());
1806 for j in &self.joints {
1807 w_joint(&mut w, j);
1808 }
1809
1810 w.usize(self.force_fields.len());
1811 for f in &self.force_fields {
1812 w_force_field(&mut w, f);
1813 }
1814
1815 w_events(&mut w, &self.events);
1816
1817 w.fix(self.islands.config.linear_threshold);
1818 w.fix(self.islands.config.angular_threshold);
1819 w.u32(self.islands.config.frames_to_sleep);
1820 w.usize(self.islands.sleep_data.len());
1821 for sd in &self.islands.sleep_data {
1822 w.u8(match sd.state {
1823 SleepState::Awake => 0,
1824 SleepState::Sleeping => 1,
1825 });
1826 w.u32(sd.idle_frames);
1827 }
1828
1829 w.usize(self.body_collision_radii.len());
1830 for &r in &self.body_collision_radii {
1831 w.opt_fix(r);
1832 }
1833
1834 w.u8(match self.broadphase {
1835 Broadphase::Bvh => 0,
1836 Broadphase::DynamicTree => 1,
1837 Broadphase::Hybrid => 2,
1838 });
1839 w_tree(&mut w, &self.broadphase_tree);
1840 w.usize(self.broadphase_proxies.len());
1841 for &p in &self.broadphase_proxies {
1842 match p {
1843 Some(id) => {
1844 w.u8(1);
1845 w.u32(id);
1846 }
1847 None => w.u8(0),
1848 }
1849 }
1850
1851 w.usize(self.body_colliders.len());
1852 for c in &self.body_colliders {
1853 w_body_collider(&mut w, c.as_ref());
1854 }
1855
1856 w.usize(self.body_filters.len());
1857 for f in &self.body_filters {
1858 w.u32(f.layer);
1859 w.u32(f.mask);
1860 w.u32(f.group);
1861 }
1862
1863 w.bool(self.overflow_detected);
1864
1865 #[cfg(feature = "std")]
1866 {
1867 let cache = &self.tgs_impulse_cache;
1868 let mut entries: Vec<_> = cache.entries.iter().map(|(&k, &v)| (k, v)).collect();
1869 entries.sort_unstable_by_key(|e| e.0);
1870 w.usize(entries.len());
1871 for (id, imp) in entries {
1872 w.u64(id);
1873 w.fix(imp.normal);
1874 w.fix(imp.tangent1);
1875 w.fix(imp.tangent2);
1876 }
1877 w.u64(cache.hits);
1878 w.u64(cache.misses);
1879 }
1880 #[cfg(not(feature = "std"))]
1881 {
1882 w.usize(0);
1883 w.u64(0);
1884 w.u64(0);
1885 }
1886
1887 #[cfg(feature = "std")]
1888 let participants = {
1889 let mut out = Vec::new();
1890 self.write_participants(&mut out);
1891 out
1892 };
1893 #[cfg(not(feature = "std"))]
1894 let participants: Vec<(u32, Vec<u8>)> = Vec::new();
1895 w.usize(participants.len());
1896 for (kind, payload) in &participants {
1897 w.u32(*kind);
1898 w.usize(payload.len());
1899 w.0.extend_from_slice(payload);
1900 }
1901 w_fault(&mut w, self.fault());
1902 self.fields().write_values(&mut w.0);
1903 w.bool(self.ccd.is_enabled());
1904 w.fix(self.ccd.motion_threshold());
1905
1906 let mut data = w.0;
1907 let payload_len = (data.len() - HEADER_LEN_V4) as u64;
1908 data[8..16].copy_from_slice(&payload_len.to_le_bytes());
1909 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
1910 fnv1a_fold(&mut hash, &data);
1911 data.extend_from_slice(&hash.to_le_bytes());
1912 data
1913 }
1914
1915 pub fn from_world_snapshot(data: &[u8]) -> Result<Self, WorldSnapshotError> {
1929 let mut world = Self::new(SolverConfig::default());
1930 world.restore_world(data)?;
1931 Ok(world)
1932 }
1933
1934 pub fn restore_world(&mut self, data: &[u8]) -> Result<(), WorldSnapshotError> {
1953 self.restore_world_checked(data, None).map(|_| ())
1954 }
1955
1956 pub fn restore_world_checked(
2031 &mut self,
2032 data: &[u8],
2033 expected_law_id: Option<&[u8; 32]>,
2034 ) -> Result<LawCheck, WorldSnapshotError> {
2035 let d = decode(data)?;
2036 let check = law_check(d.ids, expected_law_id)?;
2037 self.check_attachable(&d)?;
2038 self.check_participants(&d.participants)?;
2039 self.fields()
2040 .check_values(&d.fields)
2041 .map_err(WorldSnapshotError::FieldState)?;
2042 self.install(d);
2043 Ok(check)
2044 }
2045
2046 fn check_attachable(&self, d: &Decoded) -> Res<()> {
2047 if d.sdf_poses.len() != self.sdf_colliders.len() {
2048 return Err(WorldSnapshotError::SdfFieldCountMismatch {
2049 snapshot: d.sdf_poses.len(),
2050 world: self.sdf_colliders.len(),
2051 });
2052 }
2053 #[cfg(feature = "std")]
2054 let (hooks, modifiers) = (self.pre_solve_hooks.len(), self.contact_modifiers.len());
2055 #[cfg(not(feature = "std"))]
2056 let (hooks, modifiers) = (0usize, 0usize);
2057 #[cfg(feature = "gpu-solver-bridge")]
2058 let bridge = usize::from(self.gpu_solver_bridge.is_some());
2059 #[cfg(not(feature = "gpu-solver-bridge"))]
2060 let bridge = 0usize;
2061 for (section, snapshot, world) in [
2062 ("pre_solve_hooks", d.pre_solve_hooks, hooks),
2063 ("contact_modifiers", d.contact_modifiers, modifiers),
2064 ("gpu_solver_bridge", d.gpu_solver_bridge, bridge),
2065 ] {
2066 if snapshot != world {
2067 return Err(WorldSnapshotError::CallbackCountMismatch {
2068 section,
2069 snapshot,
2070 world,
2071 });
2072 }
2073 }
2074 Ok(())
2075 }
2076
2077 fn install(&mut self, d: Decoded) {
2078 self.config = d.config;
2079 self.bodies = d.bodies;
2080 self.distance_constraints = d.distance_constraints;
2081 self.contact_constraints = d.contact_constraints;
2082 for (s, p) in self.sdf_colliders.iter_mut().zip(d.sdf_poses) {
2083 s.position = p.0;
2084 s.rotation = p.1;
2085 s.scale = p.2;
2086 s.body_index = p.3;
2087 s.inv_rotation = p.4;
2088 s.scale_f32 = p.5;
2089 s.inv_scale_f32 = p.6;
2090 }
2091 self.sdf_collision_radius = d.sdf_collision_radius;
2092 self.static_colliders = d.static_colliders;
2093 self.constraint_batches = d.constraint_batches;
2094 self.batches_dirty = d.batches_dirty;
2095 self.batch_static_bodies = d.batch_static_bodies;
2096 self.contact_cache = d.contact_cache;
2097 self.material_table = d.material_table;
2098 self.body_materials = d.body_materials;
2099 self.joints = d.joints;
2100 self.force_fields = d.force_fields;
2101 self.events = d.events;
2102
2103 let mut islands = IslandManager::new(d.sleep_data.len(), d.sleep_config);
2105 for j in &self.joints {
2106 let (a, b) = j.bodies();
2107 if a < d.sleep_data.len() && b < d.sleep_data.len() {
2108 islands.union(a, b);
2109 }
2110 }
2111 islands.sleep_data = d.sleep_data;
2112 self.islands = islands;
2113
2114 self.body_collision_radii = d.body_collision_radii;
2115 self.broadphase = d.broadphase;
2116 self.broadphase_tree = d.broadphase_tree;
2117 self.broadphase_proxies = d.broadphase_proxies;
2118 self.broadphase_hybrid = crate::bvh::BroadphaseHybrid::new();
2119 self.body_colliders = d.body_colliders;
2120 self.body_filters = d.body_filters;
2121 self.overflow_detected = d.overflow_detected;
2122 self.kinematic_substeps_left = 0;
2123
2124 #[cfg(feature = "std")]
2125 {
2126 let (entries, hits, misses) = d.tgs_cache;
2127 let mut cache = crate::solver_tgs::ImpulseCache::new();
2128 for (id, [n, t1, t2]) in entries {
2129 cache.entries.insert(
2130 id,
2131 crate::solver_tgs::CachedImpulse {
2132 normal: n,
2133 tangent1: t1,
2134 tangent2: t2,
2135 },
2136 );
2137 }
2138 cache.hits = hits;
2140 cache.misses = misses;
2141 self.tgs_impulse_cache = cache;
2142 }
2143 #[cfg(not(feature = "std"))]
2144 let _ = d.tgs_cache; self.read_participants(&d.participants);
2148 self.set_fault(d.fault);
2149 self.fields_mut().read_values(&d.fields);
2150 self.ccd = d.ccd;
2151 }
2152}
2153
2154fn law_check(ids: Option<([u8; 32], [u8; 32])>, expected: Option<&[u8; 32]>) -> Res<LawCheck> {
2156 let Some((_, stored)) = ids else {
2157 return Ok(LawCheck::Unpinned);
2158 };
2159 match expected {
2160 None => Ok(LawCheck::SemanticsVerified),
2161 Some(e) if *e == stored => Ok(LawCheck::Verified),
2162 Some(e) => Err(WorldSnapshotError::LawIdMismatch {
2163 stored,
2164 expected: *e,
2165 }),
2166 }
2167}
2168
2169fn decode(data: &[u8]) -> Res<Decoded> {
2170 if data.len() < HEADER_LEN + CHECKSUM_LEN {
2171 return Err(WorldSnapshotError::Truncated);
2172 }
2173 if data[0..4] != PhysicsWorld::WORLD_SNAPSHOT_MAGIC {
2174 return Err(WorldSnapshotError::BadMagic);
2175 }
2176 let version = u16::from_le_bytes([data[4], data[5]]);
2177 if !(1..=PhysicsWorld::WORLD_SNAPSHOT_VERSION).contains(&version) {
2178 return Err(WorldSnapshotError::UnsupportedVersion {
2179 found: version,
2180 supported: PhysicsWorld::WORLD_SNAPSHOT_VERSION,
2181 });
2182 }
2183 if data[6] != 0 || data[7] != 0 {
2184 return Err(WorldSnapshotError::ReservedNotZero);
2185 }
2186 let header = header_len(version);
2187 if data.len() < header + CHECKSUM_LEN {
2188 return Err(WorldSnapshotError::Truncated);
2189 }
2190 let mut len_bytes = [0u8; 8];
2191 len_bytes.copy_from_slice(&data[8..16]);
2192 let payload_len = u64::from_le_bytes(len_bytes);
2193 let available = (data.len() - header - CHECKSUM_LEN) as u64;
2194 if payload_len > available {
2195 return Err(WorldSnapshotError::Truncated);
2196 }
2197 if payload_len < available {
2198 return Err(WorldSnapshotError::TrailingBytes {
2199 extra: (available - payload_len) as usize,
2200 });
2201 }
2202 let body_end = data.len() - CHECKSUM_LEN;
2203 let mut stored = [0u8; 8];
2204 stored.copy_from_slice(&data[body_end..]);
2205 let stored = u64::from_le_bytes(stored);
2206 let mut computed: u64 = 0xcbf2_9ce4_8422_2325;
2207 fnv1a_fold(&mut computed, &data[..body_end]);
2208 if stored != computed {
2209 return Err(WorldSnapshotError::ChecksumMismatch { stored, computed });
2210 }
2211 let ids = if version >= 4 {
2212 let mut semantics_id = [0u8; 32];
2213 semantics_id.copy_from_slice(&data[SEMANTICS_ID_AT..SEMANTICS_ID_AT + 32]);
2214 let mut law_id = [0u8; 32];
2215 law_id.copy_from_slice(&data[LAW_ID_AT..LAW_ID_AT + 32]);
2216 if semantics_id != PHYSICS_SEMANTICS_ID {
2217 return Err(WorldSnapshotError::SemanticsMismatch {
2218 stored: semantics_id,
2219 expected: PHYSICS_SEMANTICS_ID,
2220 });
2221 }
2222 Some((semantics_id, law_id))
2223 } else {
2224 None
2225 };
2226
2227 let mut r = R {
2228 data: &data[..body_end],
2229 pos: header,
2230 };
2231 let mut d = decode_payload(&mut r, version)?;
2232 d.ids = ids;
2233 if r.pos != body_end {
2234 return Err(WorldSnapshotError::TrailingBytes {
2235 extra: body_end - r.pos,
2236 });
2237 }
2238 validate(&d)?;
2239 Ok(d)
2240}
2241
2242fn decode_payload(r: &mut R<'_>, version: u16) -> Res<Decoded> {
2243 let config = r_config(r)?;
2244
2245 let n = r.len("bodies", 1)?;
2246 let mut bodies = Vec::with_capacity(n);
2247 for _ in 0..n {
2248 bodies.push(r_body(r)?);
2249 }
2250
2251 let n = r.len("distance_constraints", 160)?;
2252 let mut distance_constraints = Vec::with_capacity(n);
2253 for _ in 0..n {
2254 const S: &str = "distance_constraints";
2255 distance_constraints.push(DistanceConstraint {
2256 body_a: r.usize(S)?,
2257 body_b: r.usize(S)?,
2258 local_anchor_a: r.vec3()?,
2259 local_anchor_b: r.vec3()?,
2260 target_distance: r.fix()?,
2261 compliance: r.fix()?,
2262 cached_lambda: r.fix()?,
2263 });
2264 }
2265
2266 let n = r.len("contact_constraints", 208)?;
2267 let mut contact_constraints = Vec::with_capacity(n);
2268 for _ in 0..n {
2269 const S: &str = "contact_constraints";
2270 contact_constraints.push(ContactConstraint {
2271 body_a: r.usize(S)?,
2272 body_b: r.usize(S)?,
2273 contact: r_contact(r)?,
2274 friction: r.fix()?,
2275 restitution: r.fix()?,
2276 cached_lambda: r.fix()?,
2277 });
2278 }
2279
2280 let n = r.len("sdf_colliders", 193)?;
2281 let mut sdf_poses = Vec::with_capacity(n);
2282 for _ in 0..n {
2283 const S: &str = "sdf_colliders";
2284 let position = r.vec3()?;
2285 let rotation = r.quat()?;
2286 let scale = r.fix()?;
2287 let body_index = match r.u8()? {
2288 SDF_STATIC_TAG => crate::sdf_collider::SDF_STATIC,
2289 1 => r.usize(S)?,
2290 _ => return Err(invalid(S)),
2291 };
2292 let inv_rotation = r.quat()?;
2293 let scale_f32 = r.f32()?;
2294 let inv_scale_f32 = r.f32()?;
2295 sdf_poses.push((
2296 position,
2297 rotation,
2298 scale,
2299 body_index,
2300 inv_rotation,
2301 scale_f32,
2302 inv_scale_f32,
2303 ));
2304 }
2305 let sdf_collision_radius = r.fix()?;
2306
2307 let n = r.len("static_colliders", 1)?;
2308 let mut static_colliders = Vec::with_capacity(n);
2309 for _ in 0..n {
2310 static_colliders.push(r_static_collider(r)?);
2311 }
2312
2313 let n = r.len("constraint_batches", 16)?;
2314 let mut constraint_batches = Vec::with_capacity(n);
2315 for _ in 0..n {
2316 const S: &str = "constraint_batches";
2317 let k = r.len(S, 8)?;
2318 let mut distance_indices = Vec::with_capacity(k);
2319 for _ in 0..k {
2320 distance_indices.push(r.usize(S)?);
2321 }
2322 let k = r.len(S, 8)?;
2323 let mut contact_indices = Vec::with_capacity(k);
2324 for _ in 0..k {
2325 contact_indices.push(r.usize(S)?);
2326 }
2327 constraint_batches.push(ConstraintBatch {
2328 distance_indices,
2329 contact_indices,
2330 });
2331 }
2332 let batches_dirty = r.bool("constraint_batches")?;
2333 let n = r.len("batch_static_bodies", 1)?;
2334 let mut batch_static_bodies = Vec::with_capacity(n);
2335 for _ in 0..n {
2336 batch_static_bodies.push(r.bool("batch_static_bodies")?);
2337 }
2338
2339 let contact_cache = r_contact_cache(r)?;
2340 let material_table = r_material_table(r)?;
2341 let n = r.len("body_materials", 2)?;
2342 let mut body_materials = Vec::with_capacity(n);
2343 for _ in 0..n {
2344 body_materials.push(r.u16()?);
2345 }
2346
2347 let pre_solve_hooks = r.usize("pre_solve_hooks")?;
2348 let contact_modifiers = r.usize("contact_modifiers")?;
2349 let gpu_solver_bridge = r.usize("gpu_solver_bridge")?;
2350
2351 let n = r.len("joints", 1)?;
2352 let mut joints = Vec::with_capacity(n);
2353 for _ in 0..n {
2354 joints.push(r_joint(r)?);
2355 }
2356
2357 let n = r.len("force_fields", 1)?;
2358 let mut force_fields = Vec::with_capacity(n);
2359 for _ in 0..n {
2360 force_fields.push(r_force_field(r)?);
2361 }
2362
2363 let events = r_events(r)?;
2364
2365 let sleep_config = SleepConfig {
2366 linear_threshold: r.fix()?,
2367 angular_threshold: r.fix()?,
2368 frames_to_sleep: r.u32()?,
2369 };
2370 let n = r.len("islands", 5)?;
2371 let mut sleep_data = Vec::with_capacity(n);
2372 for _ in 0..n {
2373 let state = match r.u8()? {
2374 0 => SleepState::Awake,
2375 1 => SleepState::Sleeping,
2376 _ => return Err(invalid("islands")),
2377 };
2378 sleep_data.push(SleepData {
2379 state,
2380 idle_frames: r.u32()?,
2381 });
2382 }
2383
2384 let n = r.len("body_collision_radii", 1)?;
2385 let mut body_collision_radii = Vec::with_capacity(n);
2386 for _ in 0..n {
2387 body_collision_radii.push(r.opt_fix("body_collision_radii")?);
2388 }
2389
2390 let broadphase = match r.u8()? {
2391 0 => Broadphase::Bvh,
2392 1 => Broadphase::DynamicTree,
2393 2 => Broadphase::Hybrid,
2394 _ => return Err(invalid("broadphase")),
2395 };
2396 let broadphase_tree = r_tree(r)?;
2397 let n = r.len("broadphase_proxies", 1)?;
2398 let mut broadphase_proxies = Vec::with_capacity(n);
2399 for _ in 0..n {
2400 broadphase_proxies.push(if r.bool("broadphase_proxies")? {
2401 Some(r.u32()?)
2402 } else {
2403 None
2404 });
2405 }
2406
2407 let n = r.len("body_colliders", 1)?;
2408 let mut body_colliders = Vec::with_capacity(n);
2409 for _ in 0..n {
2410 body_colliders.push(r_body_collider(r)?);
2411 }
2412
2413 let n = r.len("body_filters", 12)?;
2414 let mut body_filters = Vec::with_capacity(n);
2415 for _ in 0..n {
2416 body_filters.push(CollisionFilter {
2417 layer: r.u32()?,
2418 mask: r.u32()?,
2419 group: r.u32()?,
2420 });
2421 }
2422
2423 let overflow_detected = r.bool("overflow_detected")?;
2424
2425 let n = r.len("tgs_impulse_cache", 56)?;
2426 let mut entries = Vec::with_capacity(n);
2427 for _ in 0..n {
2428 entries.push((r.u64()?, [r.fix()?, r.fix()?, r.fix()?]));
2429 }
2430 let tgs_cache = (entries, r.u64()?, r.u64()?);
2431
2432 let (participants, fault, fields) = if version >= 2 {
2433 let n = r.len("participants", 12)?;
2434 let mut participants = Vec::with_capacity(n);
2435 for _ in 0..n {
2436 let kind = r.u32()?;
2437 let len = r.len("participants", 1)?;
2438 participants.push((kind, r.take(len)?.to_vec()));
2439 }
2440 (participants, r_fault(r)?, r_fields(r)?)
2441 } else {
2442 let mut empty = Vec::new();
2443 crate::world_participant::FieldBoard::new().write_values(&mut empty);
2444 (Vec::new(), None, empty)
2445 };
2446 let ccd = if version >= 3 {
2447 super::WorldCcdConfig::new()
2448 .with_enabled(r.bool("continuous_collision")?)
2449 .with_motion_threshold(r.fix()?)
2450 } else {
2451 super::WorldCcdConfig::new()
2452 };
2453
2454 Ok(Decoded {
2455 config,
2456 bodies,
2457 distance_constraints,
2458 contact_constraints,
2459 sdf_poses,
2460 sdf_collision_radius,
2461 static_colliders,
2462 constraint_batches,
2463 batches_dirty,
2464 batch_static_bodies,
2465 contact_cache,
2466 material_table,
2467 body_materials,
2468 pre_solve_hooks,
2469 contact_modifiers,
2470 gpu_solver_bridge,
2471 joints,
2472 force_fields,
2473 events,
2474 sleep_config,
2475 sleep_data,
2476 body_collision_radii,
2477 broadphase,
2478 broadphase_tree,
2479 broadphase_proxies,
2480 body_colliders,
2481 body_filters,
2482 overflow_detected,
2483 tgs_cache,
2484 participants,
2485 fault,
2486 fields,
2487 ccd,
2488 ids: None,
2489 })
2490}
2491
2492fn w_fault(w: &mut W, fault: Option<WorldFault>) {
2493 match fault {
2494 None => w.u8(0),
2495 Some(WorldFault::Participant { index, kind, fault }) => {
2496 w.u8(1);
2497 w.usize(index);
2498 w.u32(kind.get());
2499 w.u8(fault.tag());
2500 }
2501 Some(WorldFault::RigidOverflow) => w.u8(2),
2502 Some(WorldFault::ForceOutOfRange { body }) => {
2503 w.u8(3);
2504 w.usize(body);
2505 }
2506 Some(WorldFault::FieldOutOfRange { field, index }) => {
2507 w.u8(4);
2508 w.u32(field.get());
2509 w.usize(index);
2510 }
2511 }
2512}
2513
2514fn r_fault(r: &mut R<'_>) -> Res<Option<WorldFault>> {
2515 const SECTION: &str = "fault";
2516 Ok(match r.u8()? {
2517 0 => None,
2518 1 => {
2519 let index = r.usize(SECTION)?;
2520 let kind = crate::world_participant::ParticipantKind::new(r.u32()?);
2521 let fault = crate::world_participant::ParticipantFault::from_tag(r.u8()?)
2522 .ok_or(WorldSnapshotError::InvalidValue { section: SECTION })?;
2523 Some(WorldFault::Participant { index, kind, fault })
2524 }
2525 2 => Some(WorldFault::RigidOverflow),
2526 3 => Some(WorldFault::ForceOutOfRange {
2527 body: r.usize(SECTION)?,
2528 }),
2529 4 => Some(WorldFault::FieldOutOfRange {
2530 field: crate::world_participant::PortId::new(r.u32()?),
2531 index: r.usize(SECTION)?,
2532 }),
2533 _ => return Err(WorldSnapshotError::InvalidValue { section: SECTION }),
2534 })
2535}
2536
2537fn r_fields(r: &mut R<'_>) -> Res<Vec<u8>> {
2541 const SECTION: &str = "fields";
2542 let start = r.pos;
2543 let n = r.len(SECTION, 4 + 1 + 1 + 8)?;
2544 for _ in 0..n {
2545 r.u32()?;
2546 if r.u8()? > 1 {
2547 return Err(WorldSnapshotError::InvalidValue { section: SECTION });
2548 }
2549 let samples = match r.u8()? {
2550 0 => r.usize(SECTION)?,
2551 1 => {
2552 let origin = r.vec3()?;
2553 let cell = r.fix()?;
2554 let dims = [r.usize(SECTION)?, r.usize(SECTION)?, r.usize(SECTION)?];
2555 crate::world_participant::FieldLayout::Grid { origin, cell, dims }
2556 .samples()
2557 .ok_or(WorldSnapshotError::InvalidValue { section: SECTION })?
2558 }
2559 _ => return Err(WorldSnapshotError::InvalidValue { section: SECTION }),
2560 };
2561 let bytes = samples
2562 .checked_mul(16)
2563 .ok_or(WorldSnapshotError::Truncated)?;
2564 r.take(bytes)?;
2565 }
2566 Ok(r.data[start..r.pos].to_vec())
2567}
2568
2569fn validate(d: &Decoded) -> Res<()> {
2571 let n = d.bodies.len();
2572 let check = |section: &'static str, index: usize, len: usize| -> Res<()> {
2573 if index < len {
2574 Ok(())
2575 } else {
2576 Err(WorldSnapshotError::DanglingIndex {
2577 section,
2578 index,
2579 len,
2580 })
2581 }
2582 };
2583 for j in &d.joints {
2584 let (a, b) = j.bodies();
2585 check("joints", a, n)?;
2586 check("joints", b, n)?;
2587 }
2588 for c in &d.distance_constraints {
2589 check("distance_constraints", c.body_a, n)?;
2590 check("distance_constraints", c.body_b, n)?;
2591 }
2592 for c in &d.contact_constraints {
2593 check("contact_constraints", c.body_a, n)?;
2594 check("contact_constraints", c.body_b, n)?;
2595 }
2596 for p in &d.sdf_poses {
2597 if p.3 != crate::sdf_collider::SDF_STATIC {
2598 check("sdf_colliders", p.3, n)?;
2599 }
2600 }
2601 for b in &d.constraint_batches {
2602 for &i in &b.distance_indices {
2603 check("constraint_batches", i, d.distance_constraints.len())?;
2604 }
2605 for &i in &b.contact_indices {
2606 check("constraint_batches", i, d.contact_constraints.len())?;
2607 }
2608 }
2609 for &p in d.broadphase_proxies.iter().flatten() {
2610 check(
2611 "broadphase_proxies",
2612 p as usize,
2613 d.broadphase_tree.nodes.len(),
2614 )?;
2615 }
2616 Ok(())
2617}
2618
2619#[cfg(all(test, feature = "std"))]
2620mod tests {
2621 use super::super::*;
2629 use super::WorldSnapshotError;
2630 use crate::character::CharacterController;
2631 use crate::collider::Contact;
2632 use crate::compound::CompoundShape;
2633 use crate::force::{ForceField, ForceFieldInstance};
2634 use crate::heightfield::HeightField;
2635 use crate::joint::{
2636 BallJoint, ConeTwistJoint, D6Joint, FixedJoint, HingeJoint, Joint, SliderJoint, SpringJoint,
2637 };
2638 use crate::motor::{apply_motors, JointMotor, PdController};
2639 use crate::plane_collider::PlaneCollider;
2640 use crate::sdf_collider::{ClosureSdf, SdfCollider};
2641 use crate::shape::Shape;
2642 use crate::static_collider::StaticCollider;
2643 use crate::trimesh::TriMesh;
2644
2645 fn fx(n: i64, d: i64) -> Fix128 {
2646 Fix128::from_ratio(n, d)
2647 }
2648
2649 fn v(x: i64, y: i64, z: i64) -> Vec3Fix {
2650 Vec3Fix::from_int(x, y, z)
2651 }
2652
2653 fn digest(w: &mut PhysicsWorld) -> Vec<(&'static str, String)> {
2655 let mut d = Vec::new();
2656 d.push(("config", format!("{:?}", w.config)));
2657 d.push(("bodies", format!("{:?}", w.bodies)));
2658 d.push((
2659 "distance_constraints",
2660 format!("{:?}", w.distance_constraints),
2661 ));
2662 d.push((
2663 "contact_constraints",
2664 format!("{:?}", w.contact_constraints),
2665 ));
2666 let sdf: Vec<String> = w
2667 .sdf_colliders
2668 .iter()
2669 .map(|s| {
2670 format!(
2671 "{:?} {:?} {:?} {} {:?} {} {} field@probe={}",
2672 s.position,
2673 s.rotation,
2674 s.scale,
2675 s.body_index,
2676 s.inv_rotation,
2677 s.scale_f32.to_bits(),
2678 s.inv_scale_f32.to_bits(),
2679 s.field.distance(0.3, 0.2, 0.1).to_bits()
2680 )
2681 })
2682 .collect();
2683 d.push(("sdf_colliders", format!("{sdf:?}")));
2684 d.push((
2685 "sdf_collision_radius",
2686 format!("{:?}", w.sdf_collision_radius),
2687 ));
2688 let statics: Vec<String> = w
2689 .static_colliders
2690 .iter()
2691 .map(|c| match c {
2692 StaticCollider::Plane(p) => format!("plane {p:?}"),
2693 StaticCollider::HeightField(h) => format!("hf {h:?}"),
2694 StaticCollider::TriMesh(m) => format!(
2695 "mesh {:?} {:?} {:?} {:?} {:?}",
2696 m.triangles, m.bvh.nodes, m.bvh.primitives, m.bvh.bounds, m.bounds
2697 ),
2698 })
2699 .collect();
2700 d.push(("static_colliders", format!("{statics:?}")));
2701 let batches: Vec<_> = w
2702 .constraint_batches
2703 .iter()
2704 .map(|b| (b.distance_indices.clone(), b.contact_indices.clone()))
2705 .collect();
2706 d.push(("constraint_batches", format!("{batches:?}")));
2707 d.push(("batches_dirty", format!("{}", w.batches_dirty)));
2708 d.push((
2709 "batch_static_bodies",
2710 format!("{:?}", w.batch_static_bodies),
2711 ));
2712 let index_probe: Vec<_> = w
2713 .contact_cache
2714 .manifolds
2715 .iter()
2716 .map(|m| w.contact_cache.find(&m.pair).map(|f| f.stale_frames))
2717 .collect();
2718 d.push((
2719 "contact_cache",
2720 format!(
2721 "{:?} {} {:?}",
2722 w.contact_cache.manifolds,
2723 w.contact_cache.max_stale_frames,
2724 w.contact_cache.warm_start_factor
2725 ),
2726 ));
2727 d.push(("contact_cache.pair_index", format!("{index_probe:?}")));
2728 d.push((
2729 "material_table",
2730 format!(
2731 "{:?} {:?} {:?} {:?}",
2732 w.material_table.materials,
2733 w.material_table.pair_overrides,
2734 w.material_table.default_friction_combine,
2735 w.material_table.default_restitution_combine
2736 ),
2737 ));
2738 d.push(("body_materials", format!("{:?}", w.body_materials)));
2739 d.push((
2740 "callbacks",
2741 format!("{} {}", w.pre_solve_hooks.len(), w.contact_modifiers.len()),
2742 ));
2743 d.push(("joints", format!("{:?}", w.joints)));
2744 d.push(("force_fields", format!("{:?}", w.force_fields)));
2745 let e = &w.events;
2746 d.push((
2747 "events",
2748 format!(
2749 "{:?} {:?} {:?} {:?} {:?} {:?}",
2750 e.contact_events,
2751 e.trigger_events,
2752 e.prev_pairs,
2753 e.curr_pairs,
2754 e.prev_triggers,
2755 e.curr_triggers
2756 ),
2757 ));
2758 d.push(("islands.config", format!("{:?}", w.islands.config)));
2759 d.push(("islands.sleep_data", format!("{:?}", w.islands.sleep_data)));
2760 let n = w.islands.sleep_data.len();
2761 let mut partition = Vec::new();
2762 for i in 0..n {
2763 for j in 0..n {
2764 partition.push(w.islands.find(i) == w.islands.find(j));
2765 }
2766 }
2767 d.push(("islands.partition", format!("{partition:?}")));
2768 d.push((
2769 "body_collision_radii",
2770 format!("{:?}", w.body_collision_radii),
2771 ));
2772 d.push(("broadphase", format!("{:?}", w.broadphase)));
2773 let t = &w.broadphase_tree;
2774 d.push((
2775 "broadphase_tree",
2776 format!(
2777 "{:?} {:?} {} {:?} {:?}",
2778 t.nodes, t.free_list, t.root, t.margin, t.metric
2779 ),
2780 ));
2781 d.push(("broadphase_proxies", format!("{:?}", w.broadphase_proxies)));
2782 d.push(("body_colliders", format!("{:?}", w.body_colliders)));
2783 d.push(("body_filters", format!("{:?}", w.body_filters)));
2784 d.push(("overflow_detected", format!("{}", w.overflow_detected)));
2785 d.push((
2786 "kinematic_substeps_left",
2787 format!("{}", w.kinematic_substeps_left),
2788 ));
2789 let c = &w.tgs_impulse_cache;
2790 let mut entries: Vec<_> = c.entries.iter().map(|(k, v)| (*k, *v)).collect();
2791 entries.sort_unstable_by_key(|e| e.0);
2792 let mut live: Vec<_> = c.live.keys().copied().collect();
2793 live.sort_unstable();
2794 d.push((
2795 "tgs_impulse_cache",
2796 format!("{entries:?} {live:?} {} {}", c.hits, c.misses),
2797 ));
2798 d
2799 }
2800
2801 fn differing(a: &mut PhysicsWorld, b: &mut PhysicsWorld) -> Vec<&'static str> {
2803 digest(a)
2804 .into_iter()
2805 .zip(digest(b))
2806 .filter(|(x, y)| x.1 != y.1)
2807 .map(|(x, _)| x.0)
2808 .collect()
2809 }
2810
2811 fn ball_sdf() -> Box<dyn crate::sdf_collider::SdfField> {
2812 Box::new(ClosureSdf::new(
2813 |x, y, z| (x * x + y * y + z * z).sqrt() - 1.0,
2814 |x, y, z| {
2815 let l = (x * x + y * y + z * z).sqrt().max(1e-6);
2816 (x / l, y / l, z / l)
2817 },
2818 ))
2819 }
2820
2821 fn target_world() -> PhysicsWorld {
2823 let mut t = PhysicsWorld::new(SolverConfig::default());
2824 t.add_sdf_collider(SdfCollider::new_dynamic(ball_sdf(), 0));
2827 t
2828 }
2829
2830 #[derive(Clone)]
2832 struct Driver {
2833 motors: Vec<JointMotor>,
2834 character: (Vec3Fix, Vec3Fix, bool, Option<usize>, Vec3Fix),
2835 }
2836
2837 impl Driver {
2838 fn new() -> Self {
2839 let mut pd = PdController::new(fx(20, 1), fx(2, 1), fx(50, 1));
2840 pd.set_position_target(fx(3, 2));
2841 Self {
2842 motors: vec![JointMotor::new(1, pd)],
2843 character: (v(1, 1, 3), Vec3Fix::ZERO, false, None, Vec3Fix::ZERO),
2844 }
2845 }
2846
2847 fn frame(&mut self, w: &mut PhysicsWorld, dt: Fix128) {
2849 apply_motors(&self.motors, &w.joints, &mut w.bodies, dt);
2850 let (p, vel, g, gb, pv) = self.character;
2851 let mut ch = CharacterController::new_default(p);
2852 ch.velocity = vel;
2853 ch.grounded = g;
2854 ch.ground_body_index = gb;
2855 ch.platform_velocity = pv;
2856 ch.move_and_slide(
2857 Vec3Fix::new(fx(1, 20), Fix128::ZERO, Fix128::ZERO),
2858 &w.bodies,
2859 &w.sdf_colliders,
2860 );
2861 for push in ch.compute_push_impulses(&w.bodies, fx(1, 2)) {
2862 let b = &mut w.bodies[push.body_index];
2863 if !b.is_static() {
2864 b.velocity = b.velocity + push.impulse * b.inv_mass;
2865 }
2866 }
2867 self.character = (
2868 ch.position,
2869 ch.velocity,
2870 ch.grounded,
2871 ch.ground_body_index,
2872 ch.platform_velocity,
2873 );
2874 w.step(dt);
2875 }
2876 }
2877
2878 fn scene(backend: SolverBackend, broadphase: Broadphase) -> PhysicsWorld {
2885 let config = SolverConfig {
2886 solver_backend: backend,
2887 ..SolverConfig::default()
2888 };
2889 let mut w = PhysicsWorld::new(config);
2890 w.set_broadphase(broadphase);
2891 w.set_sdf_collision_radius(fx(3, 5));
2892 w.broadphase_tree.margin = fx(3, 8);
2894 w.set_sleep_config(SleepConfig {
2895 linear_threshold: fx(1, 2),
2896 angular_threshold: fx(1, 2),
2897 frames_to_sleep: 3,
2898 });
2899
2900 w.add_static_collider(StaticCollider::Plane(PlaneCollider::new(
2901 v(0, 1, 0),
2902 Fix128::ZERO,
2903 )));
2904 let heights: Vec<Fix128> = (0..16).map(|i| fx(i % 4, 8)).collect();
2905 w.add_static_collider(StaticCollider::HeightField(HeightField::new(
2906 heights,
2907 4,
2908 4,
2909 Fix128::ONE,
2910 v(6, 0, -2),
2911 )));
2912 w.add_static_collider(StaticCollider::TriMesh(TriMesh::from_indexed(
2913 &[v(-10, 0, -10), v(-6, 1, -10), v(-10, 0, -6), v(-6, 1, -6)],
2914 &[0, 1, 2, 1, 3, 2],
2915 )));
2916 w.add_sdf_collider(
2917 SdfCollider::new_static(
2918 ball_sdf(),
2919 v(-4, 0, 0),
2920 QuatFix::from_axis_angle(v(0, 1, 0), fx(1, 3)),
2921 )
2922 .with_scale(fx(3, 2)),
2923 );
2924
2925 w.add_body_with_radius(
2927 RigidBody::new_dynamic(
2928 Vec3Fix::new(Fix128::ZERO, fx(1, 2), Fix128::ZERO),
2929 Fix128::ONE,
2930 ),
2931 fx(1, 2),
2932 );
2933 let anchor = w.add_body(RigidBody::new_static(v(3, 6, 0)));
2935 let mut ids = Vec::new();
2936 for k in 0..7 {
2937 let id = w.add_body_with_radius(
2938 RigidBody::new_dynamic(v(3 + k, 5, 2 * k), Fix128::ONE),
2939 fx(1, 4),
2940 );
2941 ids.push(id);
2942 }
2943 let up = v(0, 1, 0);
2944 let z = Vec3Fix::ZERO;
2945 let mut d6 = D6Joint::new(anchor, ids[5], z, z);
2946 d6.linear_x = crate::joint::D6Motion::Limited;
2947 w.add_joint(Joint::Ball(BallJoint::new(anchor, ids[0], z, v(-1, 1, 0))));
2948 let mut hinge = HingeJoint::new(anchor, ids[1], z, v(-1, 0, 0), up, up);
2949 hinge.angle_min = Some(fx(-1, 2));
2950 hinge.angle_max = Some(fx(1, 2));
2951 w.add_joint(Joint::Hinge(hinge));
2952 w.add_joint(Joint::Fixed(FixedJoint::new(
2953 anchor,
2954 ids[2],
2955 z,
2956 v(0, 1, 0),
2957 QuatFix::IDENTITY,
2958 )));
2959 let mut slider = SliderJoint::new(anchor, ids[3], v(1, 0, 0), z, z);
2960 slider.limit_min = Some(fx(-1, 1));
2961 slider.break_force = Some(fx(1000, 1));
2962 w.add_joint(Joint::Slider(slider));
2963 w.add_joint(Joint::Spring(SpringJoint::new(
2964 anchor,
2965 ids[4],
2966 z,
2967 z,
2968 fx(3, 2),
2969 fx(40, 1),
2970 fx(1, 2),
2971 )));
2972 w.add_joint(Joint::D6(d6));
2973 w.add_joint(Joint::ConeTwist(ConeTwistJoint::new(
2974 anchor, ids[6], z, z, up, up,
2975 )));
2976 w.add_distance_constraint(DistanceConstraint {
2977 body_a: ids[0],
2978 body_b: ids[1],
2979 local_anchor_a: z,
2980 local_anchor_b: z,
2981 target_distance: fx(3, 2),
2982 compliance: fx(1, 1000),
2983 cached_lambda: Fix128::ZERO,
2984 });
2985
2986 let boxed = w
2988 .add_shaped_body(
2989 &Shape::Box {
2990 half_extents: Vec3Fix::new(fx(1, 2), fx(1, 4), fx(1, 2)),
2991 },
2992 Fix128::ONE,
2993 v(7, 2, 0),
2994 )
2995 .unwrap();
2996 let mut compound = CompoundShape::new();
2997 compound.add_sphere(
2998 crate::collider::Sphere::new(Vec3Fix::ZERO, fx(1, 2)),
2999 v(-1, 0, 0) * fx(1, 2),
3000 QuatFix::IDENTITY,
3001 );
3002 compound.add_sphere(
3003 crate::collider::Sphere::new(Vec3Fix::ZERO, fx(1, 2)),
3004 v(1, 0, 0) * fx(1, 2),
3005 QuatFix::IDENTITY,
3006 );
3007 let comp = w
3008 .add_compound_body(&compound, Fix128::ONE, v(8, 3, 1))
3009 .unwrap();
3010 if let Some(crate::body_collider::BodyCollider::Compound(c)) = &mut w.body_colliders[comp] {
3012 let _ = c.compute_aabb();
3013 assert!(!c.dirty);
3014 }
3015 let over_sdf =
3016 w.add_body_with_radius(RigidBody::new_dynamic(v(-4, 3, 0), Fix128::ONE), fx(1, 2));
3017 let sensor = w.add_body_with_radius(RigidBody::new_sensor(v(3, 5, 0)), fx(2, 1));
3019 let mut kin = RigidBody::new_kinematic(v(0, 2, -3));
3020 kin.kinematic_target = Some((v(2, 2, -3), QuatFix::IDENTITY));
3021 let kin = w.add_body_with_radius(kin, fx(1, 2));
3022
3023 let rubber = w.material_table.register_rubber();
3024 let ice = w.material_table.register_ice();
3025 w.material_table
3026 .set_pair_override(rubber, ice, fx(1, 7), fx(2, 7));
3027 w.set_body_material(boxed, rubber);
3028 w.set_body_material(comp, ice);
3029 w.set_body_filter(over_sdf, CollisionFilter::new(2, 0xFFFF_FFFF));
3030
3031 w.add_force_field(
3032 ForceFieldInstance::new(ForceField::Directional {
3033 direction: v(1, 0, 0),
3034 strength: fx(3, 1),
3035 })
3036 .with_affected_bodies(vec![boxed, comp]),
3037 );
3038 w.add_force_field(ForceFieldInstance::new(ForceField::Drag {
3039 coefficient: fx(1, 10),
3040 }));
3041 w.add_force_field(ForceFieldInstance::new(ForceField::Vortex {
3042 center: v(3, 5, 0),
3043 axis: up,
3044 strength: fx(1, 2),
3045 falloff_radius: fx(20, 1),
3046 }));
3047 let mut blast = ForceFieldInstance::new(ForceField::Explosion {
3048 center: v(0, 0, 0),
3049 strength: fx(500, 1),
3050 radius: fx(50, 1),
3051 falloff_power: fx(2, 1),
3052 });
3053 blast.enabled = false;
3054 w.add_force_field(blast);
3055 let _ = (sensor, kin);
3056 w
3057 }
3058
3059 fn dt() -> Fix128 {
3060 fx(1, 60)
3061 }
3062
3063 fn round_trip(
3068 backend: SolverBackend,
3069 broadphase: Broadphase,
3070 k: usize,
3071 m: usize,
3072 overflow: bool,
3073 ) -> Vec<&'static str> {
3074 let mut a = scene(backend, broadphase);
3075 let mut drv = Driver::new();
3076 for _ in 0..k {
3077 drv.frame(&mut a, dt());
3078 }
3079 a.add_contact(ContactConstraint::new(
3080 2,
3081 3,
3082 Contact {
3083 depth: fx(1, 100),
3084 normal: v(0, 1, 0),
3085 point_a: v(3, 5, 0),
3086 point_b: v(4, 5, 2),
3087 },
3088 ));
3089 a.overflow_detected = overflow;
3090 a.rebuild_batches();
3093 assert!(!a.constraint_batches.is_empty() && !a.batches_dirty);
3094 assert!(!a.contact_constraints.is_empty() && !a.contact_cache.manifolds.is_empty());
3095 let blob = a.snapshot_world();
3096 let mut b = target_world();
3097 b.restore_world(&blob).expect("restore");
3098 let mut drv_b = drv.clone();
3099
3100 let mut diff = differing(&mut a, &mut b);
3101 for _ in 0..m {
3102 drv.frame(&mut a, dt());
3103 drv_b.frame(&mut b, dt());
3104 for f in differing(&mut a, &mut b) {
3105 if !diff.contains(&f) {
3106 diff.push(f);
3107 }
3108 }
3109 }
3110 if a.snapshot_world() != b.snapshot_world() && !diff.contains(&"blob") {
3111 diff.push("blob");
3112 }
3113 diff
3114 }
3115
3116 #[test]
3117 fn round_trip_is_bit_identical_for_every_field_and_every_later_step() {
3118 for backend in [SolverBackend::Xpbd, SolverBackend::Tgs] {
3119 for bp in [Broadphase::Bvh, Broadphase::DynamicTree] {
3120 for (k, m) in [(0, 0), (0, 5), (1, 1), (7, 3), (20, 15)] {
3121 let diff = round_trip(backend, bp, k, m, k % 2 == 1);
3122 assert!(diff.is_empty(), "{backend:?} {bp:?} k={k} m={m}: {diff:?}");
3123 }
3124 }
3125 }
3126 }
3127
3128 #[test]
3132 fn round_trip_scene_exercises_the_covered_state() {
3133 let mut a = scene(SolverBackend::Xpbd, Broadphase::DynamicTree);
3134 let mut drv = Driver::new();
3135 for _ in 0..20 {
3136 drv.frame(&mut a, dt());
3137 }
3138 assert!(a
3139 .islands
3140 .sleep_data
3141 .iter()
3142 .any(|s| s.state == SleepState::Sleeping && a.bodies.len() > 1));
3143 assert!(a.is_sleeping(0), "body 0 rests on the plane and sleeps");
3144 assert!(!a.events.curr_pairs.is_empty() || !a.events.prev_pairs.is_empty());
3145 assert!(
3146 !a.events.curr_triggers.is_empty(),
3147 "sensor overlaps the swinging bodies"
3148 );
3149 assert!(!a.broadphase_tree.nodes.is_empty());
3150 assert!(a.broadphase_proxies.iter().any(Option::is_some));
3151 assert_ne!(a.islands.find(1), a.islands.find(0));
3152 assert_eq!(a.islands.find(1), a.islands.find(2));
3153 assert!(a
3154 .body_colliders
3155 .iter()
3156 .any(|c| matches!(c, Some(crate::body_collider::BodyCollider::Compound(_)))));
3157
3158 let mut t = scene(SolverBackend::Tgs, Broadphase::Bvh);
3159 let mut drv = Driver::new();
3160 for _ in 0..7 {
3161 drv.frame(&mut t, dt());
3162 }
3163 assert!(!t.tgs_impulse_cache.entries.is_empty());
3164 assert!(t.tgs_impulse_cache.hits + t.tgs_impulse_cache.misses > 0);
3165 assert!(t.tgs_impulse_cache.live.is_empty());
3167 }
3168
3169 #[test]
3170 fn step_n_equals_step_called_n_times() {
3171 for n in [0usize, 1, 2, 9] {
3172 let mut a = scene(SolverBackend::Xpbd, Broadphase::Bvh);
3173 let mut b = scene(SolverBackend::Xpbd, Broadphase::Bvh);
3174 a.step_n(n, dt());
3175 for _ in 0..n {
3176 b.step(dt());
3177 }
3178 assert!(differing(&mut a, &mut b).is_empty(), "n={n}");
3179 assert_eq!(a.snapshot_world(), b.snapshot_world());
3180 }
3181 let mut a = scene(SolverBackend::Xpbd, Broadphase::Bvh);
3183 let before = a.snapshot_world();
3184 a.step_n(0, dt());
3185 assert_eq!(a.snapshot_world(), before);
3186 a.step_n(5, Fix128::ZERO);
3188 assert_eq!(a.snapshot_world(), before);
3189 }
3190
3191 #[test]
3193 fn restore_requires_the_same_number_of_non_data_parts() {
3194 let mut a = scene(SolverBackend::Xpbd, Broadphase::Bvh);
3195 a.config.substeps = 3;
3197 let blob = a.snapshot_world();
3198
3199 assert!(matches!(
3201 PhysicsWorld::from_world_snapshot(&blob),
3202 Err(WorldSnapshotError::SdfFieldCountMismatch {
3203 snapshot: 1,
3204 world: 0
3205 })
3206 ));
3207
3208 let mut t = target_world();
3210 t.add_pre_solve_hook(Box::new(|_, _, _| true));
3211 let before = digest(&mut t);
3212 assert_eq!(
3213 t.restore_world(&blob),
3214 Err(WorldSnapshotError::CallbackCountMismatch {
3215 section: "pre_solve_hooks",
3216 snapshot: 0,
3217 world: 1,
3218 })
3219 );
3220 assert_eq!(
3221 digest(&mut t),
3222 before,
3223 "rejected restore leaves the target untouched"
3224 );
3225 }
3226
3227 fn variant_world() -> PhysicsWorld {
3233 use crate::body_collider::BodyCollider;
3234 use crate::collider::{Capsule, ConvexHull, Sphere};
3235 use crate::material::{CombineRule, PhysicsMaterial};
3236 let mut w = PhysicsWorld::new(SolverConfig::default());
3237 w.set_broadphase(Broadphase::Hybrid);
3238 let shapes = [
3239 Shape::Box {
3240 half_extents: Vec3Fix::new(fx(1, 2), fx(1, 4), fx(1, 2)),
3241 },
3242 Shape::Cylinder {
3243 radius: fx(1, 2),
3244 half_height: fx(3, 4),
3245 },
3246 Shape::Cone {
3247 radius: fx(1, 2),
3248 half_height: fx(1, 1),
3249 },
3250 Shape::Ellipsoid {
3251 radii: Vec3Fix::new(fx(1, 2), fx(3, 4), fx(1, 4)),
3252 },
3253 Shape::Wedge {
3254 width: fx(1, 1),
3255 height: fx(1, 2),
3256 depth: fx(3, 2),
3257 },
3258 Shape::Torus {
3259 major_radius: fx(1, 1),
3260 minor_radius: fx(1, 4),
3261 },
3262 ];
3263 for (k, shape) in shapes.iter().enumerate() {
3264 let i = w.add_body_with_radius(
3265 RigidBody::new_dynamic(v(3 * k as i64, 4, 0), Fix128::ONE),
3266 fx(3, 2),
3267 );
3268 w.body_colliders[i] = Some(BodyCollider::Shape(*shape));
3269 }
3270 let mut c = CompoundShape::new();
3271 c.add_sphere(
3272 Sphere::new(Vec3Fix::ZERO, fx(1, 4)),
3273 v(1, 0, 0),
3274 QuatFix::IDENTITY,
3275 );
3276 c.add_capsule(
3277 Capsule::new(v(0, -1, 0), v(0, 1, 0), fx(1, 4)),
3278 v(-1, 0, 0),
3279 QuatFix::IDENTITY,
3280 );
3281 c.add_box(
3282 crate::box_collider::OrientedBox {
3283 center: Vec3Fix::ZERO,
3284 half_extents: Vec3Fix::new(fx(1, 4), fx(1, 4), fx(1, 4)),
3285 rotation: QuatFix::IDENTITY,
3286 },
3287 v(0, 0, 1),
3288 QuatFix::from_axis_angle(v(0, 1, 0), fx(1, 3)),
3289 );
3290 c.add_convex_hull(
3291 ConvexHull::new(vec![v(0, 0, 0), v(1, 0, 0), v(0, 1, 0), v(0, 0, 1)]),
3292 v(0, 0, -1),
3293 QuatFix::IDENTITY,
3294 );
3295 let comp =
3296 w.add_body_with_radius(RigidBody::new_dynamic(v(0, 8, 4), Fix128::ONE), fx(2, 1));
3297 w.body_colliders[comp] = Some(BodyCollider::Compound(c));
3298
3299 let mut mat = PhysicsMaterial::new(0, fx(1, 3), fx(1, 5));
3300 mat.friction_combine = CombineRule::Multiply;
3301 mat.restitution_combine = CombineRule::Multiply;
3302 let id = w.material_table.register(mat);
3303 w.material_table.default_friction_combine = CombineRule::Multiply;
3304 w.set_body_material(comp, id);
3305
3306 w.add_force_field(
3307 ForceFieldInstance::new(ForceField::Point {
3308 center: v(0, 10, 0),
3309 strength: fx(5, 1),
3310 repulsive: true,
3311 max_force: fx(50, 1),
3312 })
3313 .with_affected_bodies(vec![0, comp]),
3314 );
3315 w.add_force_field(ForceFieldInstance::new(ForceField::Buoyancy {
3316 surface_y: fx(2, 1),
3317 density: fx(1000, 1),
3318 drag: fx(1, 2),
3319 }));
3320 w.add_force_field(ForceFieldInstance::new(ForceField::Magnetic {
3321 position: v(0, 0, 0),
3322 moment: v(0, 1, 0),
3323 strength: fx(1, 10),
3324 }));
3325 w
3326 }
3327
3328 #[test]
3331 fn round_trip_covers_every_shape_child_and_force_field_kind() {
3332 let mut a = variant_world();
3333 let blob = a.snapshot_world();
3334 let mut b = PhysicsWorld::from_world_snapshot(&blob).expect("restore");
3335 assert!(differing(&mut a, &mut b).is_empty());
3336 for _ in 0..5 {
3337 a.step(dt());
3338 b.step(dt());
3339 }
3340 assert_eq!(differing(&mut a, &mut b), Vec::<&str>::new());
3341 assert_eq!(a.snapshot_world(), b.snapshot_world());
3342 }
3343
3344 #[test]
3347 fn round_trip_keeps_a_body_attached_sdf_collider() {
3348 let mut a = target_world();
3349 a.add_body_with_radius(RigidBody::new_dynamic(v(0, 3, 0), Fix128::ONE), fx(1, 2));
3350 let blob = a.snapshot_world();
3351 let mut b = target_world();
3352 b.restore_world(&blob).expect("restore");
3353 assert!(differing(&mut a, &mut b).is_empty());
3354 }
3355
3356 #[test]
3358 fn error_display_names_the_values() {
3359 use crate::world_participant::{ParticipantMismatch, StateError};
3360 let cases: [(WorldSnapshotError, &[&str]); 15] = [
3361 (WorldSnapshotError::Truncated, &["truncated"]),
3362 (WorldSnapshotError::BadMagic, &["magic"]),
3363 (
3364 WorldSnapshotError::UnsupportedVersion {
3365 found: 77,
3366 supported: 88,
3367 },
3368 &["77", "88"],
3369 ),
3370 (WorldSnapshotError::ReservedNotZero, &["reserved"]),
3371 (WorldSnapshotError::TrailingBytes { extra: 5 }, &["5"]),
3372 (
3373 WorldSnapshotError::ChecksumMismatch {
3374 stored: 0x10,
3375 computed: 0x20,
3376 },
3377 &["0x0000000000000010", "0x0000000000000020"],
3378 ),
3379 (
3380 WorldSnapshotError::InvalidValue { section: "joints" },
3381 &["joints"],
3382 ),
3383 (
3384 WorldSnapshotError::DanglingIndex {
3385 section: "contacts",
3386 index: 9,
3387 len: 4,
3388 },
3389 &["contacts", "9", "4"],
3390 ),
3391 (
3392 WorldSnapshotError::SdfFieldCountMismatch {
3393 snapshot: 2,
3394 world: 3,
3395 },
3396 &["2", "3"],
3397 ),
3398 (
3399 WorldSnapshotError::CallbackCountMismatch {
3400 section: "contact_modifiers",
3401 snapshot: 6,
3402 world: 7,
3403 },
3404 &["contact_modifiers", "6", "7"],
3405 ),
3406 (
3407 WorldSnapshotError::ParticipantMismatch(ParticipantMismatch::Count {
3408 snapshot: 11,
3409 world: 12,
3410 }),
3411 &["11", "12"],
3412 ),
3413 (
3414 WorldSnapshotError::ParticipantState {
3415 index: 13,
3416 error: StateError::InvalidValue,
3417 },
3418 &["13", "InvalidValue"],
3419 ),
3420 (
3421 WorldSnapshotError::FieldState(StateError::Length {
3422 expected: 14,
3423 found: 15,
3424 }),
3425 &["14", "15"],
3426 ),
3427 (
3428 WorldSnapshotError::SemanticsMismatch {
3429 stored: [0xAB; 32],
3430 expected: [0x01; 32],
3431 },
3432 &["semantics", &"ab".repeat(32), &"01".repeat(32)],
3433 ),
3434 (
3435 WorldSnapshotError::LawIdMismatch {
3436 stored: [0x0F; 32],
3437 expected: [0xF0; 32],
3438 },
3439 &["law id", &"0f".repeat(32), &"f0".repeat(32)],
3440 ),
3441 ];
3442 for (e, parts) in cases {
3443 let text = e.to_string();
3444 for p in parts {
3445 assert!(text.contains(p), "{e:?} -> {text:?} lacks {p:?}");
3446 }
3447 }
3448 }
3449
3450 #[test]
3456 fn restore_rejects_malformed_headers_and_corruption() {
3457 let src = variant_world();
3458 let blob = src.snapshot_world();
3459 let mut t = PhysicsWorld::new(SolverConfig::default());
3460 let before = digest(&mut t);
3461
3462 assert_eq!(
3463 t.restore_world(&blob[..10]),
3464 Err(WorldSnapshotError::Truncated)
3465 );
3466
3467 let mut bad = blob.clone();
3468 bad[0] ^= 0xFF;
3469 assert_eq!(t.restore_world(&bad), Err(WorldSnapshotError::BadMagic));
3470
3471 let mut bad = blob.clone();
3472 bad[6] = 1;
3473 assert_eq!(
3474 t.restore_world(&bad),
3475 Err(WorldSnapshotError::ReservedNotZero)
3476 );
3477
3478 let mut bad = blob.clone();
3479 bad[8..16].copy_from_slice(&u64::MAX.to_le_bytes());
3480 assert_eq!(t.restore_world(&bad), Err(WorldSnapshotError::Truncated));
3481
3482 let mut bad = blob.clone();
3483 bad.extend_from_slice(&[0, 0, 0]);
3484 assert_eq!(
3485 t.restore_world(&bad),
3486 Err(WorldSnapshotError::TrailingBytes { extra: 3 })
3487 );
3488
3489 let mut bad = blob.clone();
3490 bad[20] ^= 0x01;
3491 assert!(matches!(
3492 t.restore_world(&bad),
3493 Err(WorldSnapshotError::ChecksumMismatch { stored, computed }) if stored != computed
3494 ));
3495
3496 assert_eq!(digest(&mut t), before);
3497 assert!(t.restore_world(&blob).is_ok());
3499 }
3500
3501 fn reader(data: &[u8]) -> super::R<'_> {
3502 super::R { data, pos: 0 }
3503 }
3504
3505 #[test]
3507 fn leaf_readers_reject_unknown_tags() {
3508 let inv = |section| Some(WorldSnapshotError::InvalidValue { section });
3509 assert_eq!(
3510 super::r_shape(&mut reader(&[6])).err(),
3511 inv("body_colliders")
3512 );
3513 assert_eq!(
3514 super::r_body_collider(&mut reader(&[3])).err(),
3515 inv("body_colliders")
3516 );
3517 let mut w = super::W(Vec::new());
3519 w.u8(2);
3520 w.usize(1);
3521 w.u8(4);
3522 assert_eq!(
3523 super::r_body_collider(&mut reader(&w.0)).err(),
3524 inv("body_colliders")
3525 );
3526 assert_eq!(
3527 super::r_force_field(&mut reader(&[7])).err(),
3528 inv("force_fields")
3529 );
3530 assert_eq!(super::r_d6_motion(&mut reader(&[3])).err(), inv("joints"));
3531 assert_eq!(
3532 super::r_combine(&mut reader(&[4])).err(),
3533 inv("material_table")
3534 );
3535 assert_eq!(super::r_fault(&mut reader(&[5])).err(), inv("fault"));
3536 let mut w = super::W(Vec::new());
3538 super::w_config(&mut w, &SolverConfig::default());
3539 *w.0.last_mut().expect("backend tag") = 2;
3540 assert_eq!(super::r_config(&mut reader(&w.0)).err(), inv("config"));
3541 assert_eq!(
3543 super::r_shape(&mut reader(&[])).err(),
3544 Some(WorldSnapshotError::Truncated)
3545 );
3546 }
3547
3548 #[test]
3550 fn fault_codec_round_trips_every_variant() {
3551 use crate::world_participant::{ParticipantFault, ParticipantKind, PortId, WorldFault};
3552 let faults = [
3553 None,
3554 Some(WorldFault::Participant {
3555 index: 3,
3556 kind: ParticipantKind::new(0xABCD),
3557 fault: ParticipantFault::InvalidState,
3558 }),
3559 Some(WorldFault::RigidOverflow),
3560 Some(WorldFault::ForceOutOfRange { body: 17 }),
3561 Some(WorldFault::FieldOutOfRange {
3562 field: PortId::new(9),
3563 index: 21,
3564 }),
3565 ];
3566 for f in faults {
3567 let mut w = super::W(Vec::new());
3568 super::w_fault(&mut w, f);
3569 let mut r = reader(&w.0);
3570 assert_eq!(super::r_fault(&mut r), Ok(f));
3571 assert_eq!(r.pos, w.0.len(), "{f:?} reads exactly what it wrote");
3572 }
3573 let mut w = super::W(Vec::new());
3575 w.u8(1);
3576 w.usize(0);
3577 w.u32(1);
3578 w.u8(0xEE);
3579 assert_eq!(
3580 super::r_fault(&mut reader(&w.0)),
3581 Err(WorldSnapshotError::InvalidValue { section: "fault" })
3582 );
3583 }
3584
3585 fn reseal(mut b: Vec<u8>) -> Vec<u8> {
3591 let n = b.len() - super::CHECKSUM_LEN;
3592 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
3593 super::super::fnv1a_fold(&mut h, &b[..n]);
3594 b[n..].copy_from_slice(&h.to_le_bytes());
3595 b
3596 }
3597
3598 fn header_id(b: &[u8], at: usize) -> [u8; 32] {
3599 b[at..at + 32].try_into().expect("32 bytes")
3600 }
3601
3602 #[test]
3603 fn version_4_header_layout() {
3604 use crate::semantics::PHYSICS_SEMANTICS_ID;
3605 assert_eq!(super::HEADER_LEN_V4, 80);
3606 assert_eq!((super::SEMANTICS_ID_AT, super::LAW_ID_AT), (16, 48));
3607 assert_eq!(super::header_len(1), 16);
3608 assert_eq!(super::header_len(3), 16);
3609 assert_eq!(super::header_len(4), 80);
3610 let w = variant_world();
3611 let b = w.snapshot_world();
3612 assert_eq!(&b[4..6], &4u16.to_le_bytes());
3613 assert_eq!(header_id(&b, 16), PHYSICS_SEMANTICS_ID);
3614 assert_eq!(header_id(&b, 48), w.law_id(&PHYSICS_SEMANTICS_ID));
3615 let len = u64::from_le_bytes(b[8..16].try_into().expect("8 bytes")) as usize;
3616 assert_eq!(len, b.len() - 80 - 8);
3617 }
3618
3619 #[test]
3620 fn law_check_covers_every_case() {
3621 use super::{law_check, LawCheck};
3622 let (s, l) = ([1u8; 32], [2u8; 32]);
3623 let other = [3u8; 32];
3624 assert_eq!(law_check(None, None), Ok(LawCheck::Unpinned));
3625 assert_eq!(law_check(None, Some(&other)), Ok(LawCheck::Unpinned));
3626 assert_eq!(
3627 law_check(Some((s, l)), None),
3628 Ok(LawCheck::SemanticsVerified)
3629 );
3630 assert_eq!(law_check(Some((s, l)), Some(&l)), Ok(LawCheck::Verified));
3631 assert_eq!(
3632 law_check(Some((s, l)), Some(&other)),
3633 Err(WorldSnapshotError::LawIdMismatch {
3634 stored: l,
3635 expected: other
3636 })
3637 );
3638 }
3639
3640 #[test]
3645 fn version_4_identifier_checks() {
3646 use super::LawCheck;
3647 use crate::semantics::PHYSICS_SEMANTICS_ID;
3648 let src = variant_world();
3649 let blob = src.snapshot_world();
3650 let law = src.law_id(&PHYSICS_SEMANTICS_ID);
3651 let mut t = PhysicsWorld::new(SolverConfig::default());
3652 let before = digest(&mut t);
3653
3654 let mut x = blob.clone();
3655 x[16 + 7] ^= 0x40;
3656 let x = reseal(x);
3657 let foreign = WorldSnapshotError::SemanticsMismatch {
3658 stored: header_id(&x, 16),
3659 expected: PHYSICS_SEMANTICS_ID,
3660 };
3661 assert_eq!(t.restore_world(&x), Err(foreign));
3662 assert_eq!(t.restore_world_checked(&x, Some(&law)), Err(foreign));
3663
3664 let mut x = blob.clone();
3665 x[48 + 3] ^= 0x02;
3666 let x = reseal(x);
3667 assert_eq!(
3668 t.restore_world_checked(&x, Some(&law)),
3669 Err(WorldSnapshotError::LawIdMismatch {
3670 stored: header_id(&x, 48),
3671 expected: law
3672 })
3673 );
3674
3675 let mut x = blob.clone();
3676 x[4..6].copy_from_slice(&5u16.to_le_bytes());
3677 assert_eq!(
3678 t.restore_world(&reseal(x)),
3679 Err(WorldSnapshotError::UnsupportedVersion {
3680 found: 5,
3681 supported: 4
3682 })
3683 );
3684 assert_eq!(
3686 t.restore_world(&blob[..60]),
3687 Err(WorldSnapshotError::Truncated)
3688 );
3689 assert_eq!(digest(&mut t), before);
3690
3691 assert_eq!(
3692 t.restore_world_checked(&blob, None),
3693 Ok(LawCheck::SemanticsVerified)
3694 );
3695 let mut t = PhysicsWorld::new(SolverConfig::default());
3696 assert_eq!(
3697 t.restore_world_checked(&blob, Some(&law)),
3698 Ok(LawCheck::Verified)
3699 );
3700 }
3701
3702 #[test]
3705 fn version_3_form_is_unpinned() {
3706 use super::LawCheck;
3707 let src = variant_world();
3708 let blob = src.snapshot_world();
3709 let mut v3 = blob[..16].to_vec();
3710 v3[4..6].copy_from_slice(&3u16.to_le_bytes());
3711 v3.extend_from_slice(&blob[80..]);
3712 let v3 = reseal(v3);
3713 let mut t = PhysicsWorld::new(SolverConfig::default());
3714 assert_eq!(
3715 t.restore_world_checked(&v3, Some(&[9; 32])),
3716 Ok(LawCheck::Unpinned)
3717 );
3718 assert_eq!(t.snapshot_world(), blob);
3719 }
3720}