1use std::any::Any;
2use std::ops::Sub;
3
4use loam_math::Bivector;
5use loam_physics::collision::VectorOps;
6use loam_physics::{
7 BodyDef, BodyId, Collider, EditError, Narrowphase, PhysicsSpace, RigidBody, World, WorldState,
8};
9
10use crate::command::{Outcome, Rejection};
11use crate::domain::{
12 ChartCommand, ChartPose, DomainBuilder, DomainError, Facility, Homogeneous, Pose, TypedDomain,
13};
14use crate::entity::{Entity, EntityKey, SceneId};
15use crate::phase::Step;
16use crate::session::RestoreError;
17use crate::store::{Owner, SchemaId, Store};
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20pub enum GrabHold {
21 #[default]
23 Center,
24 Anchor,
26}
27
28#[derive(Clone, Copy)]
29pub struct GrabConfig<S: PhysicsSpace> {
30 stiffness: f32,
31 max_carry_speed: f32,
32 max_acceleration: f32,
33 hold: GrabHold,
34 anchor_point: fn(S::Point, S::Point) -> S::Point,
35 constrain_target: fn(S::Point, S::Point) -> S::Point,
36}
37
38impl<S: PhysicsSpace> GrabConfig<S> {
39 pub fn new(stiffness: f32, max_carry_speed: f32, max_acceleration: f32) -> Self {
40 Self {
41 stiffness,
42 max_carry_speed,
43 max_acceleration,
44 hold: GrabHold::Center,
45 anchor_point: |_, point| point,
46 constrain_target: |_, point| point,
47 }
48 }
49
50 pub fn hold(mut self, hold: GrabHold) -> Self {
51 self.hold = hold;
52 self
53 }
54
55 pub fn anchor_point(mut self, anchor_point: fn(S::Point, S::Point) -> S::Point) -> Self {
56 self.anchor_point = anchor_point;
57 self
58 }
59
60 pub fn constrain_target(
61 mut self,
62 constrain_target: fn(S::Point, S::Point) -> S::Point,
63 ) -> Self {
64 self.constrain_target = constrain_target;
65 self
66 }
67}
68
69pub struct PhysicsConfig<S: PhysicsSpace> {
70 gravity: Option<S::Vector>,
71 register: fn(&mut Narrowphase<S>),
72 min_substeps: u32,
73 max_substeps: u32,
74 grab: Option<GrabConfig<S>>,
75 max_step_travel: Option<f32>,
76 motion_speed: fn(&RigidBody<S>) -> f32,
77}
78
79fn linear_speed<S>(body: &RigidBody<S>) -> f32
80where
81 S: PhysicsSpace,
82 S::Vector: VectorOps,
83{
84 body.velocity.length()
85}
86
87impl<S> PhysicsConfig<S>
88where
89 S: PhysicsSpace,
90 S::Vector: VectorOps,
91{
92 pub fn new(register: fn(&mut Narrowphase<S>)) -> Self {
93 Self {
94 gravity: None,
95 register,
96 min_substeps: 1,
97 max_substeps: 1,
98 grab: None,
99 max_step_travel: None,
100 motion_speed: linear_speed::<S>,
101 }
102 }
103
104 pub fn gravity(mut self, gravity: S::Vector) -> Self {
105 self.gravity = Some(gravity);
106 self
107 }
108
109 pub fn substeps(mut self, substeps: u32) -> Self {
110 self.min_substeps = substeps.max(1);
111 self.max_substeps = self.min_substeps;
112 self
113 }
114
115 pub fn adaptive_substeps(
116 mut self,
117 minimum: u32,
118 maximum: u32,
119 max_step_travel: f32,
120 motion_speed: fn(&RigidBody<S>) -> f32,
121 ) -> Self {
122 self.min_substeps = minimum.max(1);
123 self.max_substeps = maximum.max(self.min_substeps);
124 self.max_step_travel = Some(max_step_travel);
125 self.motion_speed = motion_speed;
126 self
127 }
128
129 pub fn grab(mut self, grab: GrabConfig<S>) -> Self {
130 self.grab = Some(grab);
131 self
132 }
133
134 fn validate(&self, space: &S) -> Result<(), EditError> {
135 if self
136 .gravity
137 .is_some_and(|gravity| !space.valid_vector(gravity))
138 {
139 return Err(EditError::InvalidGravity);
140 }
141 if let Some(grab) = &self.grab {
142 if !grab.stiffness.is_finite()
143 || grab.stiffness < 0.0
144 || grab.max_carry_speed.is_nan()
145 || grab.max_carry_speed < 0.0
146 || grab.max_acceleration.is_nan()
147 || grab.max_acceleration < 0.0
148 {
149 return Err(EditError::InvalidGrabConfig);
150 }
151 }
152 if self
153 .max_step_travel
154 .is_some_and(|travel| travel.is_nan() || travel <= 0.0)
155 {
156 return Err(EditError::InvalidStepTravel);
157 }
158 Ok(())
159 }
160}
161
162struct Held<S: PhysicsSpace> {
163 body: BodyId,
164 target: S::Point,
165 anchor: S::Vector,
166 offset: S::Vector,
167}
168
169struct Released<S: PhysicsSpace> {
170 body: BodyId,
171 anchor: S::Vector,
172}
173
174impl<S: PhysicsSpace> Clone for Held<S> {
175 fn clone(&self) -> Self {
176 *self
177 }
178}
179
180impl<S: PhysicsSpace> Copy for Held<S> {}
181
182impl<S: PhysicsSpace> Clone for Released<S> {
183 fn clone(&self) -> Self {
184 *self
185 }
186}
187
188impl<S: PhysicsSpace> Copy for Released<S> {}
189
190pub struct Physics<S: PhysicsSpace> {
191 scene: SceneId,
192 world: World<S>,
193 to_body: Vec<Option<(u32, BodyId)>>,
194 to_entity: Vec<Option<(u32, EntityKey)>>,
195 held: Option<Held<S>>,
196 released: Option<Released<S>>,
197 min_substeps: u32,
198 max_substeps: u32,
199 grab: Option<GrabConfig<S>>,
200 max_step_travel: Option<f32>,
201 motion_speed: fn(&RigidBody<S>) -> f32,
202}
203
204struct PhysicsSnapshot<S: PhysicsSpace> {
205 world: WorldState<S>,
206 to_entity: Vec<Option<(u32, EntityKey)>>,
207}
208
209fn body_at(to_body: &[Option<(u32, BodyId)>], key: EntityKey) -> Option<BodyId> {
210 match to_body.get(key.slot() as usize)? {
211 Some((generation, body)) if *generation == key.generation() => Some(*body),
212 _ => None,
213 }
214}
215
216fn entity_at(to_entity: &[Option<(u32, EntityKey)>], body: BodyId) -> Option<EntityKey> {
217 match to_entity.get(body.slot() as usize)? {
218 Some((generation, key)) if *generation == body.generation() => Some(*key),
219 _ => None,
220 }
221}
222
223fn put<T: Copy>(slots: &mut Vec<Option<T>>, index: usize, value: Option<T>) {
224 if index >= slots.len() {
225 if value.is_none() {
226 return;
227 }
228 slots.resize(index + 1, None);
229 }
230 slots[index] = value;
231}
232
233fn clamp_length<V: VectorOps>(vector: V, maximum: f32) -> V {
234 let length = vector.length();
235 if length > maximum {
236 vector * (maximum / length)
237 } else {
238 vector
239 }
240}
241
242fn domain_edit(entity: Entity, error: EditError) -> DomainError {
243 match error {
244 EditError::StaleHandle => DomainError::Stale(entity),
245 error => error.into(),
246 }
247}
248
249fn domain_rejection(entity: Entity, rejection: Rejection) -> DomainError {
250 match rejection {
251 Rejection::Domain(error) => error,
252 Rejection::Edit(error) => domain_edit(entity, error),
253 _ => DomainError::Unsupported("physics pose edit"),
254 }
255}
256
257impl<S> Physics<S>
258where
259 S: Homogeneous + PhysicsSpace + Copy,
260 S::Vector: VectorOps + Default,
261 S::Point: Sub<Output = S::Vector>,
262{
263 pub fn world(&self) -> &World<S> {
264 &self.world
265 }
266
267 pub fn narrowphase_mut(&mut self) -> &mut Narrowphase<S> {
268 &mut self.world.narrowphase
269 }
270
271 pub fn set_solver_iterations(&mut self, iterations: usize) {
272 self.world.set_solver_iterations(iterations);
273 }
274
275 pub fn set_grab_hold(&mut self, hold: GrabHold) {
277 if let Some(grab) = &mut self.grab {
278 grab.hold = hold;
279 }
280 }
281
282 pub fn set_gravity(&mut self, gravity: Option<S::Vector>) -> Result<(), EditError> {
283 self.world.set_gravity(gravity)
284 }
285
286 pub fn set_solver_tolerance(&mut self, tolerance: f32) -> Result<(), EditError> {
287 self.world.set_solver_tolerance(tolerance)
288 }
289
290 pub fn body(&self, entity: Entity) -> Option<BodyId> {
291 if entity.scene() != self.scene {
292 return None;
293 }
294 body_at(&self.to_body, entity.key())
295 }
296
297 pub fn entity(&self, body: BodyId) -> Option<Entity> {
298 entity_at(&self.to_entity, body).map(|key| Entity::new(self.scene, key))
299 }
300
301 pub fn set_velocity(
302 &mut self,
303 entity: Entity,
304 velocity: S::Vector,
305 angular_velocity: S::AngVel,
306 ) -> Result<(), EditError> {
307 let id = self.body(entity).ok_or(EditError::StaleHandle)?;
308 self.world.set_velocity(id, velocity, angular_velocity)
309 }
310
311 pub fn set_restitution(&mut self, entity: Entity, restitution: f32) -> Result<(), EditError> {
312 let id = self.body(entity).ok_or(EditError::StaleHandle)?;
313 self.world.set_restitution(id, restitution)
314 }
315
316 pub fn set_collision_filter(
317 &mut self,
318 entity: Entity,
319 group: u32,
320 mask: u32,
321 ) -> Result<(), EditError> {
322 let id = self.body(entity).ok_or(EditError::StaleHandle)?;
323 self.world.set_collision_filter(id, group, mask)
324 }
325
326 pub fn set_mass_properties(
327 &mut self,
328 entity: Entity,
329 mass: f32,
330 inertia: S::Inertia,
331 ) -> Result<(), EditError> {
332 let id = self.body(entity).ok_or(EditError::StaleHandle)?;
333 self.world.set_mass_properties(id, mass, inertia)
334 }
335
336 pub fn set_collider(
337 &mut self,
338 entity: Entity,
339 collider: Collider,
340 inertia: S::Inertia,
341 ) -> Result<(), EditError> {
342 let id = self.body(entity).ok_or(EditError::StaleHandle)?;
343 self.world.set_collider(id, collider, inertia)
344 }
345
346 pub fn sleep_body(&mut self, entity: Entity) -> Result<(), EditError> {
347 let id = self.body(entity).ok_or(EditError::StaleHandle)?;
348 self.world.sleep_body(id)
349 }
350
351 pub fn wake_body(&mut self, entity: Entity) -> Result<(), EditError> {
352 let id = self.body(entity).ok_or(EditError::StaleHandle)?;
353 self.world.wake_body(id)
354 }
355
356 pub fn is_held(&self, entity: Entity) -> bool {
357 self.body(entity)
358 .is_some_and(|body| self.held.is_some_and(|held| held.body == body))
359 }
360
361 pub fn held_anchor(&self, entity: Entity) -> Option<S::Point> {
362 let body = self.body(entity)?;
363 let held = self.held.filter(|held| held.body == body)?;
364 let row = self.world.body(body)?;
365 let lever = self
366 .world
367 .space()
368 .iso_transport(row.orientation, row.position, held.anchor);
369 let point = self.world.space().exp(row.position, lever);
370 self.world.space().valid_point(point).then_some(point)
371 }
372
373 pub fn throw(&mut self, entity: Entity, velocity: S::Vector) -> Result<(), EditError> {
374 let id = self.body(entity).ok_or(EditError::StaleHandle)?;
375 let angular = self
376 .world
377 .body(id)
378 .ok_or(EditError::StaleHandle)?
379 .angular_velocity;
380 self.world.set_velocity(id, velocity, angular)?;
381 if self.released.is_some_and(|released| released.body == id) {
382 self.released = None;
383 }
384 Ok(())
385 }
386
387 pub fn released_anchor(&self, entity: Entity) -> Option<S::Point> {
389 let body = self.body(entity)?;
390 let released = self.released.filter(|released| released.body == body)?;
391 let row = self.world.body(body)?;
392 let lever =
393 self.world
394 .space()
395 .iso_transport(row.orientation, row.position, released.anchor);
396 let point = self.world.space().exp(row.position, lever);
397 self.world.space().valid_point(point).then_some(point)
398 }
399
400 pub(crate) fn spawn(&mut self, entity: Entity, body: BodyDef<S>) -> BodyId {
401 let _ = self.despawn(entity);
402 let id = self.world.push_body(body);
403 let key = entity.key();
404 put(
405 &mut self.to_body,
406 key.slot() as usize,
407 Some((key.generation(), id)),
408 );
409 put(
410 &mut self.to_entity,
411 id.slot() as usize,
412 Some((id.generation(), key)),
413 );
414 id
415 }
416
417 pub fn despawn(&mut self, entity: Entity) -> Option<BodyId> {
418 let id = self.body(entity)?;
419 if self.held.is_some_and(|held| held.body == id) {
420 self.held = None;
421 }
422 if self.released.is_some_and(|released| released.body == id) {
423 self.released = None;
424 }
425 let _ = self.world.despawn_body(id);
426 put(&mut self.to_body, entity.key().slot() as usize, None);
427 put(&mut self.to_entity, id.slot() as usize, None);
428 Some(id)
429 }
430
431 fn mirror_entity(&self, id: BodyId, poses: &Store<Pose<S>>) -> Result<Entity, EditError> {
432 let entity = self.entity(id).ok_or(EditError::StaleHandle)?;
433 if entity.scene() != poses.scene() {
434 return Err(EditError::StaleHandle);
435 }
436 poses.get(entity).ok_or(EditError::StaleHandle)?;
437 Ok(entity)
438 }
439
440 fn mirror_pose(&self, id: BodyId, poses: &mut Store<Pose<S>>) -> Result<(), EditError> {
441 let entity = self.mirror_entity(id, poses)?;
442 let row = self.world.body(id).ok_or(EditError::StaleHandle)?;
443 let pose = poses.get_mut(entity).ok_or(EditError::StaleHandle)?;
444 *pose = self.world.space().pose_of(self.world.space().iso_compose(
445 self.world.space().transvection(row.position),
446 row.orientation,
447 ));
448 Ok(())
449 }
450
451 pub(crate) fn check_pose(&self, pose: Pose<S>) -> Result<(), EditError> {
452 let (position, orientation) = self.physics_pose(pose);
453 if !self.world.space().valid_point(position)
454 || !self.world.space().valid_orientation(orientation)
455 {
456 return Err(EditError::NotFinite);
457 }
458 Ok(())
459 }
460
461 fn physics_pose(&self, pose: Pose<S>) -> (S::Point, S::Iso) {
462 let space = *self.world.space();
463 let orientation = space.iso_compose(
464 space.iso_inverse(space.transvection(pose.point)),
465 space.iso_of(&pose),
466 );
467 (pose.point, orientation)
468 }
469
470 pub(crate) fn replace_pose(
471 &mut self,
472 id: BodyId,
473 pose: Pose<S>,
474 poses: &mut Store<Pose<S>>,
475 ) -> Result<(), EditError> {
476 self.mirror_entity(id, poses)?;
477 let (position, orientation) = self.physics_pose(pose);
478 self.world.set_pose(id, position, orientation)?;
479 self.mirror_pose(id, poses)
480 }
481
482 fn place(
483 &mut self,
484 id: BodyId,
485 pose: &ChartPose,
486 poses: &mut Store<Pose<S>>,
487 ) -> Result<Outcome, Rejection> {
488 let pose = self.world.space().pose_from_chart(pose)?;
489 self.replace_pose(id, pose, poses)
490 .map_err(Rejection::Edit)?;
491 Ok(Outcome::Done)
492 }
493
494 fn move_to(&mut self, id: BodyId, position: S::Point) -> Result<bool, Rejection> {
495 let orientation = self
496 .world
497 .body(id)
498 .ok_or(Rejection::Edit(EditError::StaleHandle))?
499 .orientation;
500 if !self.world.space().valid_point(position) {
501 return Err(Rejection::Edit(EditError::NotFinite));
502 }
503 if let Some(held) = &mut self.held {
504 if held.body == id {
505 let target = self.grab.map_or(position, |grab| {
506 (grab.constrain_target)(held.target, position)
507 });
508 if !self.world.space().valid_point(target) {
509 return Err(Rejection::Edit(EditError::NotFinite));
510 }
511 held.target = target;
512 return Ok(false);
513 }
514 }
515 self.world
516 .set_pose(id, position, orientation)
517 .map_err(Rejection::Edit)?;
518 Ok(true)
519 }
520
521 fn move_to_and_mirror(
522 &mut self,
523 id: BodyId,
524 point: S::Point,
525 poses: &mut Store<Pose<S>>,
526 ) -> Result<(), Rejection> {
527 self.mirror_entity(id, poses).map_err(Rejection::Edit)?;
528 if self.move_to(id, point)? {
529 self.mirror_pose(id, poses).map_err(Rejection::Edit)?;
530 }
531 Ok(())
532 }
533
534 fn grab(&mut self, id: BodyId, point: S::Point) -> Result<Outcome, Rejection> {
535 let Some(grab) = self.grab else {
536 return Ok(Outcome::Done);
537 };
538 let row = self
539 .world
540 .body(id)
541 .ok_or(Rejection::Edit(EditError::StaleHandle))?;
542 let target = row.position;
543 let anchor = (grab.anchor_point)(target, point);
544 if !self.world.space().valid_point(anchor) {
545 return Err(Rejection::Edit(EditError::NotFinite));
546 }
547 let lever = self.world.space().log(target, anchor);
548 let local_anchor = self.world.space().iso_transport(
549 self.world.space().iso_inverse(row.orientation),
550 target,
551 lever,
552 );
553 if !self.world.space().valid_vector(local_anchor) {
554 return Err(Rejection::Edit(EditError::NotFinite));
555 }
556 self.world
557 .set_velocity(id, S::Vector::default(), S::AngVel::zero())
558 .map_err(Rejection::Edit)?;
559 self.released = None;
560 self.held = Some(Held {
561 body: id,
562 target,
563 anchor: local_anchor,
564 offset: lever,
565 });
566 Ok(Outcome::Done)
567 }
568
569 fn release_grab(&mut self, id: BodyId) -> Result<Outcome, Rejection> {
570 if let Some(held) = self.held.filter(|held| held.body == id) {
571 self.held = None;
572 self.released = Some(Released {
573 body: held.body,
574 anchor: held.anchor,
575 });
576 }
577 Ok(Outcome::Done)
578 }
579
580 fn drive_held(&mut self, dt: f32) -> Result<(), DomainError> {
581 let Some(held) = self.held else {
582 return Ok(());
583 };
584 let Some(grab) = self.grab else {
585 return Ok(());
586 };
587 let space = *self.world.space();
588 let row = *self.world.body(held.body).ok_or(EditError::StaleHandle)?;
589 if grab.hold == GrabHold::Center {
590 let desired = clamp_length(
591 space.log(row.position, held.target) * grab.stiffness,
592 grab.max_carry_speed,
593 );
594 let acceleration = clamp_length(desired - row.velocity, grab.max_acceleration * dt);
595 return self
596 .world
597 .set_velocity(held.body, row.velocity + acceleration, row.angular_velocity)
598 .map_err(DomainError::from);
599 }
600 let lever = space.iso_transport(row.orientation, row.position, held.anchor);
601 let point = space.exp(row.position, lever);
602 let goal = space.exp(held.target, held.offset);
603 let moving = space.velocity_at_point(&row, point);
604 let desired = clamp_length(
605 space.log(point, goal) * grab.stiffness,
606 grab.max_carry_speed,
607 );
608 let change = clamp_length(desired - moving, grab.max_acceleration * dt);
609 let size = change.length();
610 if size <= 0.0 {
611 return Ok(());
612 }
613 let direction = change * (1.0 / size);
614 let mut probe = row;
615 probe.apply_impulse_at_point(&space, direction, point);
616 let response = (space.velocity_at_point(&probe, point) - moving).dot(direction);
617 if response <= 0.0 {
618 return Ok(());
619 }
620 self.world
621 .apply_impulse_at_point(held.body, direction * (size / response), point)
622 .map_err(DomainError::from)
623 }
624
625 fn step_count(&self, dt: f32) -> u32 {
626 if self.min_substeps == self.max_substeps {
627 return self.min_substeps;
628 }
629 let Some(max_step_travel) = self.max_step_travel else {
630 return self.min_substeps;
631 };
632 let fastest = self
633 .world
634 .bodies()
635 .iter()
636 .map(|body| (self.motion_speed)(body))
637 .fold(0.0f32, f32::max);
638 let needed = (fastest * dt / max_step_travel).ceil() as u32;
639 needed.clamp(self.min_substeps, self.max_substeps)
640 }
641
642 fn sync_poses(&mut self, poses: &mut Store<Pose<S>>, rewrite: bool) {
643 let space = *self.world.space();
644 let scene = poses.scene();
645 let to_entity = &self.to_entity;
646 for (id, row) in self.world.drain_dirty() {
647 let Some(key) = entity_at(to_entity, id) else {
648 continue;
649 };
650 let entity = Entity::new(scene, key);
651 let next =
652 space.pose_of(space.iso_compose(space.transvection(row.position), row.orientation));
653 if !rewrite
654 && poses
655 .get(entity)
656 .is_some_and(|pose| space.chart_pose(pose) == space.chart_pose(&next))
657 {
658 continue;
659 }
660 let Some(pose) = poses.get_mut(entity) else {
661 continue;
662 };
663 *pose = next;
664 }
665 }
666}
667
668impl<S> Facility<S> for Physics<S>
669where
670 S: Homogeneous + PhysicsSpace + Copy,
671 S::Vector: VectorOps + Default,
672 S::AngVel: PartialEq,
673 S::Point: Sub<Output = S::Vector>,
674{
675 fn name(&self) -> &'static str {
676 "physics"
677 }
678
679 fn bind(&mut self, scene: SceneId, _owner: Owner) {
680 self.scene = scene;
681 }
682
683 fn step(
684 &mut self,
685 poses: &mut Store<Pose<S>>,
686 step: Step,
687 _owner: Owner,
688 ) -> Result<(), DomainError> {
689 if !step.dt.is_finite() || step.dt < 0.0 {
690 return Err(EditError::InvalidTimeStep.into());
691 }
692 self.drive_held(step.dt)?;
693 let substeps = self.step_count(step.dt);
694 let dt = step.dt / substeps as f32;
695 for _ in 0..substeps {
696 self.world.step(dt)?;
697 }
698 self.sync_poses(poses, true);
699 self.released = None;
700 Ok(())
701 }
702
703 fn synchronize(&mut self, poses: &mut Store<Pose<S>>, _owner: Owner) {
704 self.sync_poses(poses, false);
705 }
706
707 fn set_pose(
708 &mut self,
709 entity: Entity,
710 pose: Pose<S>,
711 poses: &mut Store<Pose<S>>,
712 _owner: Owner,
713 ) -> Option<Result<(), DomainError>> {
714 let id = self.body(entity)?;
715 Some(
716 self.replace_pose(id, pose, poses)
717 .map_err(|error| domain_edit(entity, error)),
718 )
719 }
720
721 fn move_to(
722 &mut self,
723 entity: Entity,
724 point: S::Point,
725 poses: &mut Store<Pose<S>>,
726 _owner: Owner,
727 ) -> Option<Result<(), DomainError>> {
728 let id = self.body(entity)?;
729 Some(
730 self.move_to_and_mirror(id, point, poses)
731 .map_err(|error| domain_rejection(entity, error)),
732 )
733 }
734
735 fn snapshot(&self, _owner: Owner) -> Box<dyn Any + Send> {
736 Box::new(PhysicsSnapshot::<S> {
737 world: self.world.snapshot(),
738 to_entity: self.to_entity.clone(),
739 })
740 }
741
742 fn check_restore(&self, from: &(dyn Any + Send), _owner: Owner) -> Result<(), RestoreError> {
743 let from = from
744 .downcast_ref::<PhysicsSnapshot<S>>()
745 .ok_or_else(|| RestoreError::Schema(SchemaId::of::<PhysicsSnapshot<S>>()))?;
746 self.world
747 .check_restore(&from.world)
748 .map_err(RestoreError::Edit)
749 }
750
751 fn restore(&mut self, from: &(dyn Any + Send), _owner: Owner) -> Result<(), RestoreError> {
752 let from = from
753 .downcast_ref::<PhysicsSnapshot<S>>()
754 .ok_or_else(|| RestoreError::Schema(SchemaId::of::<PhysicsSnapshot<S>>()))?;
755 self.world
756 .restore(&from.world)
757 .map_err(RestoreError::Edit)?;
758 self.to_entity.clear();
759 self.to_entity.extend_from_slice(&from.to_entity);
760 self.held = None;
761 self.released = None;
762 for slot in &mut self.to_body {
763 *slot = None;
764 }
765 for dense in 0..self.world.bodies().len() {
766 let id = self.world.bodies().id_at(dense);
767 let Some(key) = entity_at(&self.to_entity, id) else {
768 continue;
769 };
770 put(
771 &mut self.to_body,
772 key.slot() as usize,
773 Some((key.generation(), id)),
774 );
775 }
776 Ok(())
777 }
778
779 fn release(&mut self, entity: Entity, _owner: Owner) {
780 let _ = self.despawn(entity);
781 }
782
783 fn apply(
784 &mut self,
785 command: &ChartCommand,
786 poses: &mut Store<Pose<S>>,
787 _owner: Owner,
788 ) -> Option<Result<Outcome, Rejection>> {
789 match command {
790 ChartCommand::Place { entity, pose } => {
791 let id = self.body(*entity)?;
792 Some(self.place(id, pose, poses))
793 }
794 ChartCommand::Grab { entity, point } => {
795 let id = self.body(*entity)?;
796 let point = match self.world.space().point_from_chart(point) {
797 Ok(point) => point,
798 Err(error) => return Some(Err(error.into())),
799 };
800 Some(self.grab(id, point))
801 }
802 ChartCommand::Release { entity } => {
803 let id = self.body(*entity)?;
804 Some(self.release_grab(id))
805 }
806 _ => None,
807 }
808 }
809}
810
811impl<S> DomainBuilder<S>
812where
813 S: Homogeneous + PhysicsSpace + Copy,
814 S::Vector: VectorOps + Default,
815 S::AngVel: PartialEq,
816 S::Point: Sub<Output = S::Vector>,
817{
818 pub fn physics(self, config: PhysicsConfig<S>) -> Result<Self, EditError> {
819 config.validate(&self.space)?;
820 let mut world = World::new(self.space);
821 world.set_gravity(config.gravity)?;
822 (config.register)(&mut world.narrowphase);
823 Ok(self.facility(Physics {
824 scene: SceneId::UNBOUND,
825 world,
826 to_body: Vec::new(),
827 to_entity: Vec::new(),
828 held: None,
829 released: None,
830 min_substeps: config.min_substeps,
831 max_substeps: config.max_substeps,
832 grab: config.grab,
833 max_step_travel: config.max_step_travel,
834 motion_speed: config.motion_speed,
835 }))
836 }
837}
838
839impl<S> TypedDomain<S>
840where
841 S: Homogeneous + PhysicsSpace + Copy,
842 S::Vector: VectorOps + Default,
843 S::Point: Sub<Output = S::Vector>,
844{
845 pub fn physics(&self) -> Option<&Physics<S>> {
846 self.facilities
847 .iter()
848 .find_map(|facility| (facility.as_ref() as &dyn Any).downcast_ref::<Physics<S>>())
849 }
850
851 pub fn physics_mut(&mut self) -> Option<&mut Physics<S>> {
852 self.facilities
853 .iter_mut()
854 .find_map(|facility| (facility.as_mut() as &mut dyn Any).downcast_mut::<Physics<S>>())
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use loam_math::{EuclideanR4, Rotor4};
861 use loam_physics::euclidean_r4::{register_default_narrowphase, sphere_body_r4};
862 use loam_time::alloc::bytes_allocated_by;
863
864 use super::*;
865 use crate::command::{Command, SpawnBundle};
866 use crate::domain::{DomainId, DomainOwner, DomainSpace, Instance};
867 use crate::entity::{Entities, Epoch, RuntimeId, SceneId};
868 use crate::input::Input;
869 use crate::phase::Tick;
870 use crate::session::{Material, PreparedGeometry, Publication, Session, SimConfig};
871 use crate::store::DEFAULT_LOG_CAPACITY;
872 use crate::view::{Section4, Vec4, ViewSpec};
873
874 crate::stores! {
875 #[derive(Default)]
876 pub struct Bare {}
877 }
878
879 const MOVING: usize = 64;
880 const WARM_STEPS: u64 = 8;
881
882 fn step_at(tick: u64) -> Step {
883 Step {
884 tick: Tick(tick),
885 dt: 1.0 / 60.0,
886 }
887 }
888
889 #[test]
890 fn a_constrained_grab_does_not_drop_its_release_lever() {
891 let scene = SceneId {
892 runtime: RuntimeId::allocate(),
893 epoch: Epoch::default(),
894 };
895 let mut entities = Entities::new(scene);
896 let mut domain = DomainBuilder::new("r4", EuclideanR4)
897 .tracked(DEFAULT_LOG_CAPACITY)
898 .physics(
899 PhysicsConfig::new(register_default_narrowphase).grab(
900 GrabConfig::new(20.0, 20.0, 400.0)
901 .anchor_point(|body: Vec4, picked: Vec4| {
902 Vec4::new(picked.x, picked.y, picked.z, body.w)
903 })
904 .constrain_target(|current: Vec4, target: Vec4| {
905 Vec4::new(
906 target.x.clamp(-1.0, 1.0),
907 target.y.max(0.5),
908 target.z.clamp(-1.0, 1.0),
909 current.w,
910 )
911 }),
912 ),
913 )
914 .unwrap()
915 .build(DomainId::new(0), scene);
916 let entity = entities.spawn();
917 let start = Vec4::new(0.0, 2.0, 0.0, 0.75);
918 domain.attach_pose(entity, Pose::at(start)).expect("pose");
919 let physics = domain.physics_mut().expect("physics");
920 let id = physics.spawn(
921 entity,
922 sphere_body_r4(start, Vec4::ZERO, 0.5, 1.0).expect("body"),
923 );
924 let mut orientation = physics.world.body(id).expect("body").orientation;
925 orientation.rotation = Rotor4::from_rotation_arc(Vec4::Y, Vec4::W);
926 physics
927 .world
928 .set_pose(id, start, orientation)
929 .expect("pose");
930 physics
931 .grab(id, Vec4::new(0.0, 2.3, 0.0, -4.0))
932 .expect("grab");
933 physics
934 .move_to(id, Vec4::new(9.0, -9.0, 0.0, 7.0))
935 .expect("move");
936 assert_eq!(
937 physics.held.expect("held").target,
938 Vec4::new(1.0, 0.5, 0.0, 0.75)
939 );
940 physics.release_grab(id).expect("release");
941 let handle = physics.released_anchor(entity).expect("released");
942 assert!(
943 (handle - Vec4::new(0.0, 2.3, 0.0, 0.75)).length() < 1e-5,
944 "the release lost the grabbed handle: {handle}"
945 );
946 }
947
948 #[test]
949 fn the_grab_hold_decides_whether_an_off_center_body_swings() {
950 let lowest = |hold: GrabHold| {
951 let scene = SceneId {
952 runtime: RuntimeId::allocate(),
953 epoch: Epoch::default(),
954 };
955 let mut entities = Entities::new(scene);
956 let mut domain = DomainBuilder::new("r4", EuclideanR4)
957 .tracked(DEFAULT_LOG_CAPACITY)
958 .physics(
959 PhysicsConfig::new(register_default_narrowphase)
960 .gravity(Vec4::NEG_Y * 9.8)
961 .substeps(4)
962 .grab(GrabConfig::new(20.0, 20.0, 400.0).hold(hold)),
963 )
964 .unwrap()
965 .build(DomainId::new(0), scene);
966 let entity = entities.spawn();
967 domain
968 .attach_pose(entity, Pose::at(Vec4::ZERO))
969 .expect("pose");
970 let physics = domain.physics_mut().expect("physics");
971 let id = physics.spawn(
972 entity,
973 sphere_body_r4(Vec4::ZERO, Vec4::ZERO, 0.5, 1.0).expect("body"),
974 );
975 physics.grab(id, Vec4::X * 0.4).expect("grab");
976 let mut lowest = f32::INFINITY;
977 for tick in 0..120 {
978 domain.step(step_at(tick)).expect("step");
979 let physics = domain.physics().expect("physics");
980 lowest = lowest.min(physics.world.body(id).expect("body").position.y);
981 }
982 let grip = domain
983 .physics()
984 .expect("physics")
985 .held_anchor(entity)
986 .expect("held");
987 assert!(
988 (grip - Vec4::X * 0.4).length() < 0.1,
989 "the {hold:?} hold let the grab point drift to {grip}"
990 );
991 lowest
992 };
993 let swung = lowest(GrabHold::Anchor);
994 assert!(
995 swung < -0.3,
996 "the anchor hold never swung the body under its grab point; its center bottomed out at y {swung}"
997 );
998 let carried = lowest(GrabHold::Center);
999 assert!(
1000 carried > -0.05,
1001 "the center hold let the body swing down to y {carried}"
1002 );
1003 }
1004
1005 #[test]
1006 fn a_held_body_transfers_motion_through_sleeping_contacts() {
1007 let scene = SceneId {
1008 runtime: RuntimeId::allocate(),
1009 epoch: Epoch::default(),
1010 };
1011 let mut entities = Entities::new(scene);
1012 let mut domain = DomainBuilder::new("r4", EuclideanR4)
1013 .tracked(DEFAULT_LOG_CAPACITY)
1014 .physics(
1015 PhysicsConfig::new(register_default_narrowphase)
1016 .substeps(4)
1017 .grab(GrabConfig::new(20.0, 20.0, 400.0)),
1018 )
1019 .unwrap()
1020 .build(DomainId::new(0), scene);
1021 let held_at = Vec4::ZERO;
1022 let far_neighbor_at = Vec4::X * 2.0;
1023 let [held_body, neighbor_body, far_neighbor_body] = [0.0, 1.0, 2.0].map(|x| {
1024 let entity = entities.spawn();
1025 let at = Vec4::X * x;
1026 domain.attach_pose(entity, Pose::at(at)).expect("pose");
1027 domain.physics_mut().expect("physics").spawn(
1028 entity,
1029 sphere_body_r4(at, Vec4::ZERO, 0.5, 1.0).expect("body"),
1030 )
1031 });
1032 let physics = domain.physics_mut().expect("physics");
1033 physics.world.sleep_body(neighbor_body).expect("sleep");
1034 physics.world.sleep_body(far_neighbor_body).expect("sleep");
1035 physics.grab(held_body, held_at).expect("grab");
1036 physics.move_to(held_body, Vec4::X * 2.0).expect("move");
1037
1038 for tick in 0..4 {
1039 domain.step(step_at(tick)).expect("step");
1040 }
1041
1042 let physics = domain.physics().expect("physics");
1043 let neighbor = physics.world.body(neighbor_body).expect("neighbor");
1044 let far_neighbor = physics.world.body(far_neighbor_body).expect("far neighbor");
1045 assert!(
1046 !neighbor.is_sleeping() && !far_neighbor.is_sleeping(),
1047 "the struck chain stayed asleep"
1048 );
1049 assert!(
1050 far_neighbor.position.x > far_neighbor_at.x || far_neighbor.velocity.x > 0.1,
1051 "the held body transferred no motion through the chain: position {} velocity {}",
1052 far_neighbor.position.x,
1053 far_neighbor.velocity.x
1054 );
1055 }
1056
1057 #[test]
1058 fn a_held_body_dragged_and_thrown_between_ticks_keeps_its_blend() {
1059 let mut session = Session::new(Bare::default(), SimConfig::default());
1060 let r4 = session.register_domain(
1061 DomainBuilder::new("r4", EuclideanR4)
1062 .tracked(DEFAULT_LOG_CAPACITY)
1063 .physics(
1064 PhysicsConfig::new(register_default_narrowphase)
1065 .grab(GrabConfig::new(20.0, 20.0, 400.0)),
1066 )
1067 .unwrap(),
1068 );
1069 let geometry = session.prepare(PreparedGeometry::Lines4 {
1070 segments: vec![[[0.1, 0.0, 0.0, 0.0], [-0.1, 0.0, 0.0, 0.0]]],
1071 });
1072 let material = session.add_material(Material::flat([1.0; 4]));
1073 let root = session.views().root();
1074 let body = session.dispatch(|dispatch| {
1075 let eye = dispatch
1076 .spawn(SpawnBundle::new().at(r4, Pose::at(Vec4::Z * 4.0)))
1077 .unwrap();
1078 let body = dispatch
1079 .spawn(
1080 SpawnBundle::new()
1081 .at(r4, Pose::at(Vec4::ZERO))
1082 .instance(Instance::new(geometry, material)),
1083 )
1084 .unwrap();
1085 let domain = dispatch.domains.typed(r4).unwrap();
1086 domain
1087 .add_view(ViewSpec::new(root, eye, Section4 { w: 0.0 }))
1088 .unwrap();
1089 domain
1090 .spawn_body(
1091 body,
1092 sphere_body_r4(Vec4::ZERO, Vec4::ZERO, 0.5, 1.0).unwrap(),
1093 )
1094 .unwrap();
1095 body
1096 });
1097 let chart = |x: f32| EuclideanR4.chart_point(Vec4::X * x);
1098 session.submit(Command::Chart(
1099 r4.id(),
1100 ChartCommand::Grab {
1101 entity: body,
1102 point: chart(0.0),
1103 },
1104 ));
1105 session.submit(Command::Chart(
1106 r4.id(),
1107 ChartCommand::Move {
1108 entity: body,
1109 point: chart(5.0),
1110 },
1111 ));
1112 session.boundary(Input::default()).unwrap();
1113 session.tick().unwrap();
1114 let mut publication = Publication::default();
1115 let mut drawn_x = |session: &mut Session<Bare>| {
1116 session.publish_at(&mut publication, 0.5).unwrap();
1117 publication.views[0].records.instances.rows()[0]
1118 .pose
1119 .coordinates[0]
1120 };
1121 let blended = drawn_x(&mut session);
1122 assert!(blended > 0.0);
1123 session.submit(Command::Chart(
1124 r4.id(),
1125 ChartCommand::Move {
1126 entity: body,
1127 point: chart(6.0),
1128 },
1129 ));
1130 session.boundary(Input::default()).unwrap();
1131 session
1132 .domains_mut()
1133 .typed(r4)
1134 .unwrap()
1135 .physics_mut()
1136 .unwrap()
1137 .throw(body, Vec4::X)
1138 .unwrap();
1139 assert_eq!(drawn_x(&mut session), blended);
1140 }
1141
1142 #[test]
1143 fn a_warmed_step_with_sixty_four_moving_bodies_allocates_nothing() {
1144 let mut entities = Entities::new(SceneId {
1145 runtime: RuntimeId::allocate(),
1146 epoch: Epoch::default(),
1147 });
1148 let mut domain = DomainBuilder::new("r4", EuclideanR4)
1149 .tracked(DEFAULT_LOG_CAPACITY)
1150 .physics(PhysicsConfig::new(register_default_narrowphase).gravity(Vec4::NEG_Y))
1151 .unwrap()
1152 .build(DomainId::new(0), entities.scene());
1153 for index in 0..MOVING {
1154 let at = Vec4::new(index as f32 * 4.0, 10.0, 0.0, 0.0);
1155 let entity = entities.spawn();
1156 domain.attach_pose(entity, Pose::at(at)).expect("pose row");
1157 domain.physics_mut().expect("physics").spawn(
1158 entity,
1159 sphere_body_r4(at, Vec4::ZERO, 0.5, 1.0).expect("body"),
1160 );
1161 }
1162 for tick in 0..WARM_STEPS {
1163 domain.step(step_at(tick)).expect("warm step");
1164 }
1165
1166 let bytes = bytes_allocated_by(|| {
1167 domain.step(step_at(WARM_STEPS)).expect("step");
1168 })
1169 .expect("the counting allocator is installed");
1170 assert_eq!(bytes, 0, "a warmed physics step allocated {bytes} bytes");
1171 }
1172
1173 #[test]
1174 fn invalid_physics_configuration_is_refused_before_installation() {
1175 let invalid_gravity =
1176 PhysicsConfig::new(register_default_narrowphase).gravity(Vec4::splat(f32::NAN));
1177 assert_eq!(
1178 DomainBuilder::new("r4", EuclideanR4)
1179 .physics(invalid_gravity)
1180 .err(),
1181 Some(EditError::InvalidGravity)
1182 );
1183
1184 let invalid_grab = PhysicsConfig::new(register_default_narrowphase).grab(GrabConfig::new(
1185 f32::NAN,
1186 1.0,
1187 1.0,
1188 ));
1189 assert_eq!(
1190 DomainBuilder::new("r4", EuclideanR4)
1191 .physics(invalid_grab)
1192 .err(),
1193 Some(EditError::InvalidGrabConfig)
1194 );
1195
1196 let invalid_travel = PhysicsConfig::new(register_default_narrowphase).adaptive_substeps(
1197 1,
1198 4,
1199 0.0,
1200 linear_speed::<EuclideanR4>,
1201 );
1202 assert_eq!(
1203 DomainBuilder::new("r4", EuclideanR4)
1204 .physics(invalid_travel)
1205 .err(),
1206 Some(EditError::InvalidStepTravel)
1207 );
1208 }
1209}