1use crate::math::{Rot, Vect};
2use bevy::ecs::{query, system::SystemParam};
3use bevy::prelude::*;
4use rapier::prelude::Real;
5
6pub(crate) const RAPIER_CONTEXT_EXPECT_ERROR: &str =
7 "RapierContextEntityLink.0 refers to an entity missing components from RapierContextSimulation.";
8
9use crate::plugin::context::{
10 DefaultRapierContext, RapierContextColliders, RapierContextJoints, RapierContextSimulation,
11 RapierQueryPipeline, RapierRigidBodySet,
12};
13
14#[derive(SystemParam)]
18pub struct ReadRapierContext<'w, 's, T: query::QueryFilter + 'static = With<DefaultRapierContext>> {
19 pub rapier_context: Query<
21 'w,
22 's,
23 (
24 &'static RapierContextSimulation,
25 &'static RapierContextColliders,
26 &'static RapierContextJoints,
27 &'static RapierRigidBodySet,
28 ),
29 T,
30 >,
31}
32
33impl<'w, 's, T: query::QueryFilter + 'static> ReadRapierContext<'w, 's, T> {
34 pub fn single(&self) -> Result<RapierContext<'_>> {
40 let (simulation, colliders, joints, rigidbody_set) = self.rapier_context.single()?;
41 Ok(RapierContext {
42 simulation,
43 colliders,
44 joints,
45 rigidbody_set,
46 })
47 }
48}
49
50#[cfg_attr(feature = "serde-serialize", derive(Serialize))]
55#[derive(query::QueryData)]
56pub struct RapierContext<'a> {
57 pub simulation: &'a RapierContextSimulation,
59 pub colliders: &'a RapierContextColliders,
61 pub joints: &'a RapierContextJoints,
63 pub rigidbody_set: &'a RapierRigidBodySet,
65}
66
67#[derive(SystemParam)]
71pub struct WriteRapierContext<'w, 's, T: query::QueryFilter + 'static = With<DefaultRapierContext>>
72{
73 pub rapier_context: Query<
75 'w,
76 's,
77 (
78 &'static mut RapierContextSimulation,
79 &'static mut RapierContextColliders,
80 &'static mut RapierContextJoints,
81 &'static mut RapierRigidBodySet,
82 ),
83 T,
84 >,
85}
86
87impl<'w, 's, T: query::QueryFilter + 'static> WriteRapierContext<'w, 's, T> {
88 pub fn single(&self) -> Result<RapierContext<'_>> {
94 let (simulation, colliders, joints, rigidbody_set) = self.rapier_context.single()?;
95 Ok(RapierContext {
96 simulation,
97 colliders,
98 joints,
99 rigidbody_set,
100 })
101 }
102
103 pub fn single_mut(&mut self) -> Result<RapierContextMut<'_>> {
109 let (simulation, colliders, joints, rigidbody_set) = self.rapier_context.single_mut()?;
110 Ok(RapierContextMut {
111 simulation,
112 colliders,
113 joints,
114 rigidbody_set,
115 })
116 }
117}
118
119pub struct RapierContextMut<'a> {
124 pub simulation: Mut<'a, RapierContextSimulation>,
126 pub colliders: Mut<'a, RapierContextColliders>,
128 pub joints: Mut<'a, RapierContextJoints>,
130 pub rigidbody_set: Mut<'a, RapierRigidBodySet>,
132}
133
134mod simulation {
136 use crate::control::CharacterCollision;
137 use crate::control::MoveShapeOptions;
138 use crate::control::MoveShapeOutput;
139 use crate::geometry::AsShape;
140 use crate::plugin::context::SimulationToRenderTime;
141 use crate::plugin::ContactPairView;
142 use crate::plugin::{SimulationMode, TimestepMode};
143 use crate::prelude::CollisionEvent;
144 use crate::prelude::ContactForceEvent;
145 use crate::prelude::QueryFilter;
146 use crate::prelude::RapierRigidBodyHandle;
147 use crate::prelude::TransformInterpolation;
148 use rapier::prelude::PhysicsHooks;
149
150 use super::*;
151
152 impl RapierContext<'_> {
154 pub fn contact_pair(
156 &self,
157 collider1: Entity,
158 collider2: Entity,
159 ) -> Option<ContactPairView<'_>> {
160 self.simulation
161 .contact_pair(self.colliders, self.rigidbody_set, collider1, collider2)
162 }
163
164 pub fn contact_pairs_with(
166 &self,
167 collider: Entity,
168 ) -> impl Iterator<Item = ContactPairView<'_>> {
169 self.simulation
170 .contact_pairs_with(self.colliders, self.rigidbody_set, collider)
171 }
172
173 pub fn intersection_pair(&self, collider1: Entity, collider2: Entity) -> Option<bool> {
175 self.simulation
176 .intersection_pair(self.colliders, collider1, collider2)
177 }
178
179 pub fn intersection_pairs_with(
181 &self,
182 collider: Entity,
183 ) -> impl Iterator<Item = (Entity, Entity, bool)> + '_ {
184 self.simulation
185 .intersection_pairs_with(self.colliders, collider)
186 }
187 }
188
189 impl RapierContextMut<'_> {
191 #[expect(clippy::too_many_arguments)]
193 pub fn step_simulation(
194 &mut self,
195 gravity: Vect,
196 simulation_mode: SimulationMode,
197 timestep_mode: TimestepMode,
198 events: Option<(
199 &MessageWriter<CollisionEvent>,
200 &MessageWriter<ContactForceEvent>,
201 )>,
202 hooks: &dyn PhysicsHooks,
203 time: &Time,
204 sim_to_render_time: &mut SimulationToRenderTime,
205 interpolation_query: Option<
206 &mut Query<(&RapierRigidBodyHandle, &mut TransformInterpolation)>,
207 >,
208 ) {
209 self.simulation.step_simulation(
210 &mut self.colliders,
211 &mut self.joints,
212 &mut self.rigidbody_set,
213 gravity,
214 simulation_mode,
215 timestep_mode,
216 events,
217 hooks,
218 time,
219 sim_to_render_time,
220 interpolation_query,
221 )
222 }
223
224 #[expect(clippy::too_many_arguments)]
229 pub fn move_shape(
230 &mut self,
231 movement: Vect,
232 shape: &(impl AsShape + ?Sized),
233 shape_translation: Vect,
234 shape_rotation: Rot,
235 shape_mass: Real,
236 options: &MoveShapeOptions,
237 filter: QueryFilter,
238 events: impl FnMut(CharacterCollision),
239 ) -> MoveShapeOutput {
240 self.simulation.move_shape(
241 &mut self.colliders,
242 &mut self.rigidbody_set,
243 movement,
244 shape,
245 shape_translation,
246 shape_rotation,
247 shape_mass,
248 options,
249 filter,
250 events,
251 )
252 }
253
254 pub fn contact_pair(
256 &self,
257 collider1: Entity,
258 collider2: Entity,
259 ) -> Option<ContactPairView<'_>> {
260 self.simulation
261 .contact_pair(&self.colliders, &self.rigidbody_set, collider1, collider2)
262 }
263
264 pub fn contact_pairs_with(
266 &self,
267 collider: Entity,
268 ) -> impl Iterator<Item = ContactPairView<'_>> {
269 self.simulation
270 .contact_pairs_with(&self.colliders, &self.rigidbody_set, collider)
271 }
272
273 pub fn intersection_pair(&self, collider1: Entity, collider2: Entity) -> Option<bool> {
275 self.simulation
276 .intersection_pair(&self.colliders, collider1, collider2)
277 }
278
279 pub fn intersection_pairs_with(
281 &self,
282 collider: Entity,
283 ) -> impl Iterator<Item = (Entity, Entity, bool)> + '_ {
284 self.simulation
285 .intersection_pairs_with(&self.colliders, collider)
286 }
287 }
288}
289
290mod query_pipeline {
291 use rapier::{
292 geometry::Collider as RapierCollider,
293 parry::{partitioning::Bvh, query::ShapeCastOptions},
294 prelude::FeatureId,
295 };
296
297 use crate::geometry::AsShape;
298
299 use crate::prelude::{
300 NonlinearMotion, PointProjection, QueryFilter, RayIntersection, ShapeCastHit,
301 ShapeClosestPoints, ShapeContact,
302 };
303
304 use super::*;
305
306 impl RapierContext<'_> {
307 pub fn with_query_pipeline<'a, T>(
309 &'a self,
310 filter: QueryFilter<'a>,
311 scoped_fn: impl FnOnce(RapierQueryPipeline<'_>) -> T,
312 ) -> T {
313 crate::prelude::RapierQueryPipeline::new_scoped(
314 &self.simulation.broad_phase,
315 self.colliders,
316 self.rigidbody_set,
317 &filter,
318 self.simulation.query_dispatcher(),
319 scoped_fn,
320 )
321 }
322 pub fn cast_ray(
324 &self,
325 ray_origin: Vect,
326 ray_dir: Vect,
327 max_toi: Real,
328 solid: bool,
329 filter: QueryFilter,
330 ) -> Option<(Entity, Real)> {
331 self.with_query_pipeline(filter, |query_pipeline| {
332 query_pipeline.cast_ray(ray_origin, ray_dir, max_toi, solid)
333 })
334 }
335
336 pub fn cast_ray_and_get_normal(
338 &self,
339 ray_origin: Vect,
340 ray_dir: Vect,
341 max_toi: Real,
342 solid: bool,
343 filter: QueryFilter,
344 ) -> Option<(Entity, RayIntersection)> {
345 self.with_query_pipeline(filter, |query_pipeline| {
346 query_pipeline.cast_ray_and_get_normal(ray_origin, ray_dir, max_toi, solid)
347 })
348 }
349
350 pub fn intersect_point(
354 &self,
355 point: Vect,
356 filter: QueryFilter,
357 mut callback: impl FnMut(Entity, &RapierCollider) -> bool,
358 ) {
359 self.with_query_pipeline(filter, |query_pipeline| {
360 for (e, co) in query_pipeline.intersect_point(point) {
361 if !callback(e, co) {
362 break;
363 }
364 }
365 });
366 }
367
368 pub fn intersect_ray(
372 &self,
373 ray_origin: Vect,
374 ray_dir: Vect,
375 max_toi: Real,
376 solid: bool,
377 filter: QueryFilter,
378 mut callback: impl FnMut(Entity, &RapierCollider, RayIntersection) -> bool,
379 ) {
380 self.with_query_pipeline(filter, |query_pipeline| {
381 for (e, co, intersection) in
382 query_pipeline.intersect_ray(ray_origin, ray_dir, max_toi, solid)
383 {
384 if !callback(e, co, intersection) {
385 break;
386 }
387 }
388 });
389 }
390
391 pub fn intersect_shape(
393 &self,
394 shape_pos: Vect,
395 shape_rot: Rot,
396 shape: &(impl AsShape + ?Sized),
397 filter: QueryFilter,
398 mut callback: impl FnMut(Entity, &RapierCollider) -> bool,
399 ) {
400 self.with_query_pipeline(filter, |query_pipeline| {
401 for (e, co) in query_pipeline.intersect_shape(shape_pos, shape_rot, shape) {
402 if !callback(e, co) {
403 break;
404 }
405 }
406 });
407 }
408
409 pub fn intersect_aabb_conservative(
411 &self,
412 #[cfg(feature = "dim2")] aabb: bevy::shape::Aabb2d,
413 #[cfg(feature = "dim3")] aabb: bevy::shape::Aabb3d,
414 filter: QueryFilter,
415 mut callback: impl FnMut(Entity, &RapierCollider) -> bool,
416 ) {
417 self.with_query_pipeline(filter, |query_pipeline| {
418 for (e, co) in query_pipeline.intersect_aabb_conservative(aabb) {
419 if !callback(e, co) {
420 break;
421 }
422 }
423 });
424 }
425
426 pub fn cast_shape(
428 &self,
429 shape_pos: Vect,
430 shape_rot: Rot,
431 shape_vel: Vect,
432 shape: &(impl AsShape + ?Sized),
433 options: ShapeCastOptions,
434 filter: QueryFilter,
435 ) -> Option<(Entity, ShapeCastHit)> {
436 self.with_query_pipeline(filter, |query_pipeline| {
437 query_pipeline.cast_shape(shape_pos, shape_rot, shape_vel, shape, options)
438 })
439 }
440
441 pub fn project_point(
443 &self,
444 point: Vect,
445 max_dist: f32,
446 solid: bool,
447 filter: QueryFilter,
448 ) -> Option<(Entity, PointProjection)> {
449 self.with_query_pipeline(filter, |query_pipeline| {
450 query_pipeline.project_point(point, max_dist, solid)
451 })
452 }
453 }
454
455 impl RapierContext<'_> {
456 pub fn project_point_and_get_feature(
458 &self,
459 point: Vect,
460 max_dist: Real,
461 filter: QueryFilter,
462 ) -> Option<(Entity, PointProjection, FeatureId)> {
463 self.with_query_pipeline(filter, |query_pipeline| {
464 query_pipeline.project_point_and_get_feature(point, max_dist)
465 })
466 }
467
468 pub fn cast_shape_nonlinear(
470 &self,
471 shape_motion: &NonlinearMotion,
472 shape: &(impl AsShape + ?Sized),
473 start_time: Real,
474 end_time: Real,
475 stop_at_penetration: bool,
476 filter: QueryFilter,
477 ) -> Option<(Entity, ShapeCastHit)> {
478 self.with_query_pipeline(filter, |query_pipeline| {
479 query_pipeline.cast_shape_nonlinear(
480 shape_motion,
481 shape,
482 start_time,
483 end_time,
484 stop_at_penetration,
485 )
486 })
487 }
488
489 pub fn distance_to_shape(
491 &self,
492 shape_pos: Vect,
493 shape_rot: Rot,
494 shape: &(impl AsShape + ?Sized),
495 filter: QueryFilter,
496 ) -> Option<(Entity, Real)> {
497 self.with_query_pipeline(filter, |query_pipeline| {
498 query_pipeline.distance_to_shape(shape_pos, shape_rot, shape)
499 })
500 }
501
502 pub fn closest_points_to_shape(
504 &self,
505 shape_pos: Vect,
506 shape_rot: Rot,
507 shape: &(impl AsShape + ?Sized),
508 max_dist: Real,
509 filter: QueryFilter,
510 ) -> Option<(Entity, ShapeClosestPoints)> {
511 self.with_query_pipeline(filter, |query_pipeline| {
512 query_pipeline.closest_points_to_shape(shape_pos, shape_rot, shape, max_dist)
513 })
514 }
515
516 pub fn contact_with_shape(
518 &self,
519 shape_pos: Vect,
520 shape_rot: Rot,
521 shape: &(impl AsShape + ?Sized),
522 prediction: Real,
523 filter: QueryFilter,
524 ) -> Option<(Entity, ShapeContact)> {
525 self.with_query_pipeline(filter, |query_pipeline| {
526 query_pipeline.contact_with_shape(shape_pos, shape_rot, shape, prediction)
527 })
528 }
529
530 pub fn bvh(&self) -> &Bvh {
532 self.simulation
533 .broad_phase
534 .as_query_pipeline(
535 self.simulation.query_dispatcher(),
536 &self.rigidbody_set.bodies,
537 &self.colliders.colliders,
538 Default::default(),
539 )
540 .bvh
541 }
542 }
543
544 impl RapierContextMut<'_> {
545 fn as_read(&self) -> RapierContext<'_> {
547 RapierContext {
548 simulation: &self.simulation,
549 colliders: &self.colliders,
550 joints: &self.joints,
551 rigidbody_set: &self.rigidbody_set,
552 }
553 }
554
555 pub fn project_point_and_get_feature(
557 &self,
558 point: Vect,
559 max_dist: Real,
560 filter: QueryFilter,
561 ) -> Option<(Entity, PointProjection, FeatureId)> {
562 self.as_read()
563 .project_point_and_get_feature(point, max_dist, filter)
564 }
565
566 pub fn cast_shape_nonlinear(
568 &self,
569 shape_motion: &NonlinearMotion,
570 shape: &(impl AsShape + ?Sized),
571 start_time: Real,
572 end_time: Real,
573 stop_at_penetration: bool,
574 filter: QueryFilter,
575 ) -> Option<(Entity, ShapeCastHit)> {
576 self.as_read().cast_shape_nonlinear(
577 shape_motion,
578 shape,
579 start_time,
580 end_time,
581 stop_at_penetration,
582 filter,
583 )
584 }
585
586 pub fn distance_to_shape(
588 &self,
589 shape_pos: Vect,
590 shape_rot: Rot,
591 shape: &(impl AsShape + ?Sized),
592 filter: QueryFilter,
593 ) -> Option<(Entity, Real)> {
594 self.as_read()
595 .distance_to_shape(shape_pos, shape_rot, shape, filter)
596 }
597
598 pub fn closest_points_to_shape(
600 &self,
601 shape_pos: Vect,
602 shape_rot: Rot,
603 shape: &(impl AsShape + ?Sized),
604 max_dist: Real,
605 filter: QueryFilter,
606 ) -> Option<(Entity, ShapeClosestPoints)> {
607 self.as_read()
608 .closest_points_to_shape(shape_pos, shape_rot, shape, max_dist, filter)
609 }
610
611 pub fn contact_with_shape(
613 &self,
614 shape_pos: Vect,
615 shape_rot: Rot,
616 shape: &(impl AsShape + ?Sized),
617 prediction: Real,
618 filter: QueryFilter,
619 ) -> Option<(Entity, ShapeContact)> {
620 self.as_read()
621 .contact_with_shape(shape_pos, shape_rot, shape, prediction, filter)
622 }
623
624 pub fn bvh(&self) -> &Bvh {
626 self.simulation
627 .broad_phase
628 .as_query_pipeline(
629 self.simulation.query_dispatcher(),
630 &self.rigidbody_set.bodies,
631 &self.colliders.colliders,
632 Default::default(),
633 )
634 .bvh
635 }
636 }
637
638 impl RapierContextMut<'_> {
640 pub fn cast_ray(
642 &self,
643 ray_origin: Vect,
644 ray_dir: Vect,
645 max_toi: Real,
646 solid: bool,
647 filter: QueryFilter,
648 ) -> Option<(Entity, Real)> {
649 self.with_query_pipeline(filter, |query_pipeline| {
650 query_pipeline.cast_ray(ray_origin, ray_dir, max_toi, solid)
651 })
652 }
653
654 pub fn cast_ray_and_get_normal(
656 &self,
657 ray_origin: Vect,
658 ray_dir: Vect,
659 max_toi: Real,
660 solid: bool,
661 filter: QueryFilter,
662 ) -> Option<(Entity, RayIntersection)> {
663 self.with_query_pipeline(filter, |query_pipeline| {
664 query_pipeline.cast_ray_and_get_normal(ray_origin, ray_dir, max_toi, solid)
665 })
666 }
667
668 pub fn intersect_point(
672 &self,
673 point: Vect,
674 filter: QueryFilter,
675 mut callback: impl FnMut(Entity, &RapierCollider) -> bool,
676 ) {
677 self.with_query_pipeline(filter, |query_pipeline| {
678 for (e, co) in query_pipeline.intersect_point(point) {
679 if !callback(e, co) {
680 break;
681 }
682 }
683 });
684 }
685
686 pub fn intersect_ray(
690 &self,
691 ray_origin: Vect,
692 ray_dir: Vect,
693 max_toi: Real,
694 solid: bool,
695 filter: QueryFilter,
696 mut callback: impl FnMut(Entity, &RapierCollider, RayIntersection) -> bool,
697 ) {
698 self.with_query_pipeline(filter, |query_pipeline| {
699 for (e, co, intersection) in
700 query_pipeline.intersect_ray(ray_origin, ray_dir, max_toi, solid)
701 {
702 if !callback(e, co, intersection) {
703 break;
704 }
705 }
706 });
707 }
708
709 pub fn intersect_shape(
711 &self,
712 shape_pos: Vect,
713 shape_rot: Rot,
714 shape: &(impl AsShape + ?Sized),
715 filter: QueryFilter,
716 mut callback: impl FnMut(Entity, &RapierCollider) -> bool,
717 ) {
718 self.with_query_pipeline(filter, |query_pipeline| {
719 for (e, co) in query_pipeline.intersect_shape(shape_pos, shape_rot, shape) {
720 if !callback(e, co) {
721 break;
722 }
723 }
724 });
725 }
726
727 pub fn intersect_aabb_conservative(
729 &self,
730 #[cfg(feature = "dim2")] aabb: bevy::shape::Aabb2d,
731 #[cfg(feature = "dim3")] aabb: bevy::shape::Aabb3d,
732 filter: QueryFilter,
733 mut callback: impl FnMut(Entity, &RapierCollider) -> bool,
734 ) {
735 self.with_query_pipeline(filter, |query_pipeline| {
736 for (e, co) in query_pipeline.intersect_aabb_conservative(aabb) {
737 if !callback(e, co) {
738 break;
739 }
740 }
741 });
742 }
743
744 pub fn cast_shape(
746 &self,
747 shape_pos: Vect,
748 shape_rot: Rot,
749 shape_vel: Vect,
750 shape: &(impl AsShape + ?Sized),
751 options: ShapeCastOptions,
752 filter: QueryFilter,
753 ) -> Option<(Entity, ShapeCastHit)> {
754 self.with_query_pipeline(filter, |query_pipeline| {
755 query_pipeline.cast_shape(shape_pos, shape_rot, shape_vel, shape, options)
756 })
757 }
758
759 pub fn project_point(
761 &self,
762 point: Vect,
763 max_dist: f32,
764 solid: bool,
765 filter: QueryFilter,
766 ) -> Option<(Entity, PointProjection)> {
767 self.with_query_pipeline(filter, |query_pipeline| {
768 query_pipeline.project_point(point, max_dist, solid)
769 })
770 }
771 }
772
773 impl RapierContextMut<'_> {
774 pub fn with_query_pipeline<'a, T>(
776 &'a self,
777 filter: QueryFilter<'a>,
778 scoped_fn: impl FnOnce(RapierQueryPipeline<'_>) -> T,
779 ) -> T {
780 crate::prelude::RapierQueryPipeline::new_scoped(
781 &self.simulation.broad_phase,
782 &self.colliders,
783 &self.rigidbody_set,
784 &filter,
785 self.simulation.query_dispatcher(),
786 scoped_fn,
787 )
788 }
789 }
790}
791
792mod rigidbody_set {
793 use std::collections::HashMap;
794
795 use super::*;
796 pub use rapier::prelude::RigidBodyHandle;
797
798 impl RapierContext<'_> {
799 pub fn entity2body(&self) -> &HashMap<Entity, RigidBodyHandle> {
801 self.rigidbody_set.entity2body()
802 }
803
804 pub fn rigid_body_entity(&self, handle: RigidBodyHandle) -> Option<Entity> {
806 self.rigidbody_set.rigid_body_entity(handle)
807 }
808
809 pub fn impulse_revolute_joint_angle(&self, entity: Entity) -> Option<f32> {
811 self.rigidbody_set
812 .impulse_revolute_joint_angle(self.joints, entity)
813 }
814 }
815
816 impl RapierContextMut<'_> {
817 pub fn propagate_modified_body_positions_to_colliders(&mut self) {
819 self.rigidbody_set
820 .propagate_modified_body_positions_to_colliders(&mut self.colliders)
821 }
822
823 pub fn entity2body(&self) -> &HashMap<Entity, RigidBodyHandle> {
825 self.rigidbody_set.entity2body()
826 }
827
828 pub fn rigid_body_entity(&self, handle: RigidBodyHandle) -> Option<Entity> {
830 self.rigidbody_set.rigid_body_entity(handle)
831 }
832
833 pub fn impulse_revolute_joint_angle(&self, entity: Entity) -> Option<f32> {
835 self.rigidbody_set
836 .impulse_revolute_joint_angle(&self.joints, entity)
837 }
838 }
839}
840
841mod soft_bodies {
842 use super::*;
843 use crate::dynamics::RapierSoftBody;
844 use crate::pipeline::SoftBodyTearResult;
845 use rapier::dynamics::{RigidBodyHandle, SoftBodyHandle};
846 use std::collections::HashMap;
847
848 impl RapierContext<'_> {
849 pub fn entity2soft_body(&self) -> &HashMap<Entity, SoftBodyHandle> {
851 self.rigidbody_set.entity2soft_body()
852 }
853
854 pub fn soft_body_entity(&self, handle: SoftBodyHandle) -> Option<Entity> {
856 self.rigidbody_set.soft_body_entity(handle)
857 }
858
859 pub fn soft_body(&self, entity: Entity) -> Option<&RapierSoftBody> {
861 self.rigidbody_set.soft_body(entity)
862 }
863
864 pub fn soft_body_particle_positions(
866 &self,
867 entity: Entity,
868 ) -> Option<impl ExactSizeIterator<Item = Vect> + '_> {
869 self.rigidbody_set.soft_body_particle_positions(entity)
870 }
871
872 pub fn soft_body_particle_velocities(
874 &self,
875 entity: Entity,
876 ) -> Option<impl ExactSizeIterator<Item = Vect> + '_> {
877 self.rigidbody_set.soft_body_particle_velocities(entity)
878 }
879
880 pub fn soft_body_volume(&self, entity: Entity) -> Option<Real> {
882 self.rigidbody_set.soft_body_volume(entity)
883 }
884
885 pub fn soft_body_mass(&self, entity: Entity) -> Option<Real> {
887 self.rigidbody_set.soft_body_mass(entity)
888 }
889
890 pub fn soft_body_center_of_mass(&self, entity: Entity) -> Option<Vect> {
892 self.rigidbody_set.soft_body_center_of_mass(entity)
893 }
894
895 pub fn is_soft_body_sleeping(&self, entity: Entity) -> Option<bool> {
897 self.rigidbody_set.is_soft_body_sleeping(entity)
898 }
899
900 pub fn soft_body_whole_proxy(&self, entity: Entity) -> Option<RigidBodyHandle> {
902 self.rigidbody_set.soft_body_whole_proxy(entity)
903 }
904
905 pub fn soft_body_cluster_index(&self, entity: Entity) -> Option<(SoftBodyHandle, u32)> {
907 self.rigidbody_set.soft_body_cluster_index(entity)
908 }
909 }
910
911 impl RapierContextMut<'_> {
912 pub fn entity2soft_body(&self) -> &HashMap<Entity, SoftBodyHandle> {
914 self.rigidbody_set.entity2soft_body()
915 }
916
917 pub fn soft_body_entity(&self, handle: SoftBodyHandle) -> Option<Entity> {
919 self.rigidbody_set.soft_body_entity(handle)
920 }
921
922 pub fn soft_body(&self, entity: Entity) -> Option<&RapierSoftBody> {
924 self.rigidbody_set.soft_body(entity)
925 }
926
927 pub fn soft_body_particle_positions(
929 &self,
930 entity: Entity,
931 ) -> Option<impl ExactSizeIterator<Item = Vect> + '_> {
932 self.rigidbody_set.soft_body_particle_positions(entity)
933 }
934
935 pub fn soft_body_particle_velocities(
937 &self,
938 entity: Entity,
939 ) -> Option<impl ExactSizeIterator<Item = Vect> + '_> {
940 self.rigidbody_set.soft_body_particle_velocities(entity)
941 }
942
943 pub fn soft_body_volume(&self, entity: Entity) -> Option<Real> {
945 self.rigidbody_set.soft_body_volume(entity)
946 }
947
948 pub fn soft_body_mass(&self, entity: Entity) -> Option<Real> {
950 self.rigidbody_set.soft_body_mass(entity)
951 }
952
953 pub fn soft_body_center_of_mass(&self, entity: Entity) -> Option<Vect> {
955 self.rigidbody_set.soft_body_center_of_mass(entity)
956 }
957
958 pub fn is_soft_body_sleeping(&self, entity: Entity) -> Option<bool> {
960 self.rigidbody_set.is_soft_body_sleeping(entity)
961 }
962
963 pub fn soft_body_whole_proxy(&self, entity: Entity) -> Option<RigidBodyHandle> {
965 self.rigidbody_set.soft_body_whole_proxy(entity)
966 }
967
968 pub fn soft_body_cluster_index(&self, entity: Entity) -> Option<(SoftBodyHandle, u32)> {
970 self.rigidbody_set.soft_body_cluster_index(entity)
971 }
972
973 pub fn soft_body_mut(&mut self, entity: Entity) -> Option<&mut RapierSoftBody> {
975 self.rigidbody_set.soft_body_mut(entity)
976 }
977
978 pub fn wake_up_soft_body(&mut self, entity: Entity, strong: bool) -> bool {
980 self.rigidbody_set.wake_up_soft_body(entity, strong)
981 }
982
983 pub fn tear_soft_body(
985 &mut self,
986 commands: &mut Commands,
987 entity: Entity,
988 edges: &[u32],
989 cells: &[u32],
990 ) -> Option<SoftBodyTearResult> {
991 self.simulation.tear_soft_body(
992 &mut self.colliders,
993 &mut self.joints,
994 &mut self.rigidbody_set,
995 commands,
996 entity,
997 edges,
998 cells,
999 )
1000 }
1001
1002 pub fn cut_soft_body(
1004 &mut self,
1005 commands: &mut Commands,
1006 entity: Entity,
1007 blade: &[Vect; rapier::math::DIM],
1008 ) -> Option<SoftBodyTearResult> {
1009 self.simulation.cut_soft_body(
1010 &mut self.colliders,
1011 &mut self.joints,
1012 &mut self.rigidbody_set,
1013 commands,
1014 entity,
1015 blade,
1016 )
1017 }
1018 }
1019}
1020
1021mod joints {
1022 use super::*;
1023 use crate::dynamics::{ImpulseJointImpulses, InverseKinematicsOption, MultibodyJointState};
1024 use rapier::dynamics::Multibody;
1025 use rapier::math::Jacobian;
1026 use std::ops::Range;
1027
1028 impl RapierContext<'_> {
1029 pub fn impulse_joint_impulses(&self, entity: Entity) -> Option<ImpulseJointImpulses> {
1031 self.joints.impulse_joint_impulses(entity)
1032 }
1033
1034 pub fn impulse_joints_between(
1036 &self,
1037 body1: Entity,
1038 body2: Entity,
1039 ) -> impl Iterator<Item = Entity> + '_ {
1040 self.joints
1041 .impulse_joints_between(self.rigidbody_set, body1, body2)
1042 }
1043
1044 pub fn attached_impulse_joints(&self, body: Entity) -> impl Iterator<Item = Entity> + '_ {
1046 self.joints
1047 .attached_impulse_joints(self.rigidbody_set, body)
1048 }
1049
1050 pub fn attached_enabled_impulse_joints(
1052 &self,
1053 body: Entity,
1054 ) -> impl Iterator<Item = Entity> + '_ {
1055 self.joints
1056 .attached_enabled_impulse_joints(self.rigidbody_set, body)
1057 }
1058
1059 pub fn multibody(&self, entity: Entity) -> Option<(&Multibody, usize)> {
1061 self.joints.multibody(self.rigidbody_set, entity)
1062 }
1063
1064 pub fn multibody_root(&self, entity: Entity) -> Option<Entity> {
1066 self.joints.multibody_root(self.rigidbody_set, entity)
1067 }
1068
1069 pub fn multibody_links(&self, entity: Entity) -> impl Iterator<Item = Entity> + '_ {
1071 self.joints.multibody_links(self.rigidbody_set, entity)
1072 }
1073
1074 pub fn multibody_parent_link(&self, entity: Entity) -> Option<Entity> {
1076 self.joints
1077 .multibody_parent_link(self.rigidbody_set, entity)
1078 }
1079
1080 pub fn multibody_joint_between(&self, body1: Entity, body2: Entity) -> Option<Entity> {
1082 self.joints
1083 .multibody_joint_between(self.rigidbody_set, body1, body2)
1084 }
1085
1086 pub fn attached_multibody_joints(&self, body: Entity) -> impl Iterator<Item = Entity> + '_ {
1088 self.joints
1089 .attached_multibody_joints(self.rigidbody_set, body)
1090 }
1091
1092 pub fn multibody_link_dofs(&self, entity: Entity) -> Option<Range<usize>> {
1094 self.joints.multibody_link_dofs(self.rigidbody_set, entity)
1095 }
1096
1097 pub fn multibody_generalized_velocity(&self, entity: Entity) -> Option<&[Real]> {
1099 self.joints
1100 .multibody_generalized_velocity(self.rigidbody_set, entity)
1101 }
1102
1103 pub fn multibody_joint_state(&self, entity: Entity) -> Option<MultibodyJointState> {
1105 self.joints.multibody_joint_state(entity)
1106 }
1107
1108 pub fn multibody_body_jacobian(&self, entity: Entity) -> Option<&Jacobian<Real>> {
1110 self.joints
1111 .multibody_body_jacobian(self.rigidbody_set, entity)
1112 }
1113
1114 pub fn multibody_inverse_kinematics(
1116 &self,
1117 link: Entity,
1118 target: Transform,
1119 options: &InverseKinematicsOption,
1120 joint_can_move: impl Fn(Entity) -> bool,
1121 displacements: &mut Vec<Real>,
1122 ) -> bool {
1123 self.joints.multibody_inverse_kinematics(
1124 self.rigidbody_set,
1125 link,
1126 target,
1127 options,
1128 joint_can_move,
1129 displacements,
1130 )
1131 }
1132 }
1133
1134 impl RapierContextMut<'_> {
1135 pub fn impulse_joint_impulses(&self, entity: Entity) -> Option<ImpulseJointImpulses> {
1137 self.joints.impulse_joint_impulses(entity)
1138 }
1139
1140 pub fn impulse_joints_between(
1142 &self,
1143 body1: Entity,
1144 body2: Entity,
1145 ) -> impl Iterator<Item = Entity> + '_ {
1146 self.joints
1147 .impulse_joints_between(&self.rigidbody_set, body1, body2)
1148 }
1149
1150 pub fn attached_impulse_joints(&self, body: Entity) -> impl Iterator<Item = Entity> + '_ {
1152 self.joints
1153 .attached_impulse_joints(&self.rigidbody_set, body)
1154 }
1155
1156 pub fn attached_enabled_impulse_joints(
1158 &self,
1159 body: Entity,
1160 ) -> impl Iterator<Item = Entity> + '_ {
1161 self.joints
1162 .attached_enabled_impulse_joints(&self.rigidbody_set, body)
1163 }
1164
1165 pub fn multibody(&self, entity: Entity) -> Option<(&Multibody, usize)> {
1167 self.joints.multibody(&self.rigidbody_set, entity)
1168 }
1169
1170 pub fn multibody_root(&self, entity: Entity) -> Option<Entity> {
1172 self.joints.multibody_root(&self.rigidbody_set, entity)
1173 }
1174
1175 pub fn multibody_links(&self, entity: Entity) -> impl Iterator<Item = Entity> + '_ {
1177 self.joints.multibody_links(&self.rigidbody_set, entity)
1178 }
1179
1180 pub fn multibody_parent_link(&self, entity: Entity) -> Option<Entity> {
1182 self.joints
1183 .multibody_parent_link(&self.rigidbody_set, entity)
1184 }
1185
1186 pub fn multibody_joint_between(&self, body1: Entity, body2: Entity) -> Option<Entity> {
1188 self.joints
1189 .multibody_joint_between(&self.rigidbody_set, body1, body2)
1190 }
1191
1192 pub fn attached_multibody_joints(&self, body: Entity) -> impl Iterator<Item = Entity> + '_ {
1194 self.joints
1195 .attached_multibody_joints(&self.rigidbody_set, body)
1196 }
1197
1198 pub fn multibody_link_dofs(&self, entity: Entity) -> Option<Range<usize>> {
1200 self.joints.multibody_link_dofs(&self.rigidbody_set, entity)
1201 }
1202
1203 pub fn multibody_generalized_velocity(&self, entity: Entity) -> Option<&[Real]> {
1205 self.joints
1206 .multibody_generalized_velocity(&self.rigidbody_set, entity)
1207 }
1208
1209 pub fn multibody_joint_state(&self, entity: Entity) -> Option<MultibodyJointState> {
1211 self.joints.multibody_joint_state(entity)
1212 }
1213
1214 pub fn multibody_body_jacobian(&self, entity: Entity) -> Option<&Jacobian<Real>> {
1216 self.joints
1217 .multibody_body_jacobian(&self.rigidbody_set, entity)
1218 }
1219
1220 pub fn multibody_inverse_kinematics(
1222 &self,
1223 link: Entity,
1224 target: Transform,
1225 options: &InverseKinematicsOption,
1226 joint_can_move: impl Fn(Entity) -> bool,
1227 displacements: &mut Vec<Real>,
1228 ) -> bool {
1229 self.joints.multibody_inverse_kinematics(
1230 &self.rigidbody_set,
1231 link,
1232 target,
1233 options,
1234 joint_can_move,
1235 displacements,
1236 )
1237 }
1238
1239 pub fn multibody_mut(&mut self, entity: Entity) -> Option<(&mut Multibody, usize)> {
1241 self.joints.multibody_mut(&self.rigidbody_set, entity)
1242 }
1243
1244 pub fn multibody_generalized_velocity_mut(
1246 &mut self,
1247 entity: Entity,
1248 ) -> Option<&mut [Real]> {
1249 self.joints
1250 .multibody_generalized_velocity_mut(&mut self.rigidbody_set, entity)
1251 }
1252
1253 pub fn multibody_joint_velocity_mut(&mut self, entity: Entity) -> Option<&mut [Real]> {
1255 self.joints
1256 .multibody_joint_velocity_mut(&mut self.rigidbody_set, entity)
1257 }
1258
1259 pub fn multibody_apply_displacements(
1261 &mut self,
1262 entity: Entity,
1263 displacements: &[Real],
1264 ) -> bool {
1265 self.joints
1266 .multibody_apply_displacements(&self.rigidbody_set, entity, displacements)
1267 }
1268
1269 pub fn multibody_forward_kinematics(
1271 &mut self,
1272 entity: Entity,
1273 read_root_pose_from_rigid_body: bool,
1274 ) -> bool {
1275 self.joints.multibody_forward_kinematics(
1276 &mut self.rigidbody_set,
1277 entity,
1278 read_root_pose_from_rigid_body,
1279 )
1280 }
1281 }
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286 use super::*;
1287 use crate::geometry::{Collider, NonlinearMotion, ShapeCastOptions, ShapeClosestPoints};
1288 use crate::plugin::{NoUserData, RapierPhysicsPlugin};
1289 use crate::prelude::QueryFilter;
1290 use approx::assert_relative_eq;
1291 use bevy::ecs::system::RunSystemOnce;
1292 use bevy::time::{TimePlugin, TimeUpdateStrategy};
1293
1294 #[cfg(feature = "dim2")]
1295 fn v(x: Real, y: Real) -> Vect {
1296 Vect::new(x, y)
1297 }
1298 #[cfg(feature = "dim3")]
1299 fn v(x: Real, y: Real) -> Vect {
1300 Vect::new(x, y, 0.0)
1301 }
1302
1303 #[cfg(feature = "dim2")]
1304 fn quarter_turn() -> Rot {
1305 std::f32::consts::FRAC_PI_2
1306 }
1307 #[cfg(feature = "dim3")]
1308 fn quarter_turn() -> Rot {
1309 Rot::from_rotation_z(std::f32::consts::FRAC_PI_2)
1310 }
1311
1312 #[cfg(feature = "dim2")]
1313 fn unit_cuboid() -> Collider {
1314 Collider::cuboid(0.5, 0.5)
1315 }
1316 #[cfg(feature = "dim3")]
1317 fn unit_cuboid() -> Collider {
1318 Collider::cuboid(0.5, 0.5, 0.5)
1319 }
1320
1321 fn transform_at(x: Real, y: Real) -> Transform {
1322 Transform::from_xyz(x, y, 0.0)
1323 }
1324
1325 fn test_app() -> App {
1326 let mut app = App::new();
1327 app.add_plugins((
1328 TransformPlugin,
1329 TimePlugin,
1330 RapierPhysicsPlugin::<NoUserData>::default(),
1331 ))
1332 .insert_resource(TimeUpdateStrategy::ManualDuration(
1333 std::time::Duration::from_secs_f32(1.0 / 60.0),
1334 ));
1335 app.finish();
1336 app.update();
1337 app
1338 }
1339
1340 fn ball_and_cuboid_app() -> (App, Entity, Entity) {
1342 let mut app = test_app();
1343 let ball = app
1344 .world_mut()
1345 .spawn((transform_at(0.0, 0.0), Collider::ball(0.5)))
1346 .id();
1347 let cuboid = app
1348 .world_mut()
1349 .spawn((transform_at(10.0, 0.0), unit_cuboid()))
1350 .id();
1351 app.update();
1352 app.update();
1353 (app, ball, cuboid)
1354 }
1355
1356 fn run<T: Send + 'static>(
1357 app: &mut App,
1358 f: impl Fn(RapierContext) -> T + Send + Sync + 'static,
1359 ) -> T {
1360 app.world_mut()
1361 .run_system_once(move |context: ReadRapierContext| f(context.single().unwrap()))
1362 .unwrap()
1363 }
1364
1365 #[test]
1366 fn cast_shape_nonlinear_hits_the_first_collider() {
1367 let (mut app, ball, _) = ball_and_cuboid_app();
1368 let (from_context, from_pipeline) = run(&mut app, |context| {
1369 let shape = Collider::ball(0.5);
1370 let mut motion = NonlinearMotion::constant_position(v(-5.0, 0.0), Rot::default());
1371 motion.linear_velocity = v(1.0, 0.0);
1372 let from_context = context.cast_shape_nonlinear(
1373 &motion,
1374 &*shape.raw,
1375 0.0,
1376 10.0,
1377 true,
1378 QueryFilter::default(),
1379 );
1380 let from_pipeline = context.with_query_pipeline(QueryFilter::default(), |pipeline| {
1381 pipeline.cast_shape_nonlinear(&motion, &*shape.raw, 0.0, 2.0, true)
1382 });
1383 (from_context, from_pipeline)
1384 });
1385
1386 let (entity, hit) = from_context.unwrap();
1387 assert_eq!(entity, ball);
1388 assert_relative_eq!(hit.time_of_impact, 4.0, epsilon = 1.0e-2);
1389 assert!(from_pipeline.is_none());
1391 }
1392
1393 #[test]
1394 fn scene_queries_report_subshapes() {
1395 let mut app = test_app();
1396 let compound = app
1397 .world_mut()
1398 .spawn((
1399 transform_at(0.0, 0.0),
1400 Collider::compound(vec![
1401 (v(0.0, 0.0), Rot::default(), Collider::ball(0.5)),
1402 (v(3.0, 0.0), Rot::default(), Collider::ball(0.5)),
1403 ]),
1404 ))
1405 .id();
1406 app.update();
1407 app.update();
1408
1409 run(&mut app, move |context| {
1410 let filter = QueryFilter::default();
1411 let (e, inter) = context
1412 .cast_ray_and_get_normal(v(3.0, 5.0), v(0.0, -1.0), Real::MAX, true, filter)
1413 .unwrap();
1414 assert_eq!(e, compound);
1415 assert_eq!(inter.subshape, 1);
1416
1417 let mut ray_subshapes = vec![];
1418 context.intersect_ray(
1419 v(3.0, 5.0),
1420 v(0.0, -1.0),
1421 Real::MAX,
1422 true,
1423 filter,
1424 |_, _, i| {
1425 ray_subshapes.push(i.subshape);
1426 true
1427 },
1428 );
1429 assert_eq!(ray_subshapes, vec![1]);
1430
1431 let (_, proj) = context
1432 .project_point(v(3.0, 2.0), Real::MAX, true, filter)
1433 .unwrap();
1434 assert_eq!(proj.subshape, 1);
1435 let (_, proj) = context
1436 .project_point(v(0.0, 2.0), Real::MAX, true, filter)
1437 .unwrap();
1438 assert_eq!(proj.subshape, 0);
1439
1440 let shape = Collider::ball(0.25);
1441 let (_, hit) = context
1442 .cast_shape(
1443 v(3.0, 5.0),
1444 Rot::default(),
1445 v(0.0, -1.0),
1446 &*shape.raw,
1447 ShapeCastOptions::default(),
1448 filter,
1449 )
1450 .unwrap();
1451 assert_eq!(hit.subshape1, 1);
1452 assert_eq!(hit.subshape2, 0);
1453
1454 let (_, contact) = context
1455 .contact_with_shape(v(3.0, 1.0), Rot::default(), &*shape.raw, 1.0, filter)
1456 .unwrap();
1457 assert_eq!(contact.subshape1, 1);
1458 });
1459 }
1460
1461 #[test]
1462 fn project_point_and_get_feature_honors_max_dist() {
1463 let (mut app, ball, _) = ball_and_cuboid_app();
1464 run(&mut app, move |context| {
1465 let filter = QueryFilter::default();
1466 assert!(context
1467 .project_point_and_get_feature(v(0.0, 2.5), 1.0, filter)
1468 .is_none());
1469 let (e, proj, _) = context
1470 .project_point_and_get_feature(v(0.0, 2.5), 3.0, filter)
1471 .unwrap();
1472 assert_eq!(e, ball);
1473 assert_relative_eq!(proj.point, v(0.0, 0.5), epsilon = 1.0e-4);
1474
1475 let from_pipeline = context.with_query_pipeline(filter, |pipeline| {
1476 pipeline.project_point_and_get_feature(v(0.0, 2.5), 1.0)
1477 });
1478 assert!(from_pipeline.is_none());
1479 });
1480 }
1481
1482 #[test]
1483 fn predicate_receives_the_collider() {
1484 let (mut app, ball, cuboid) = ball_and_cuboid_app();
1485 run(&mut app, move |context| {
1486 let hits = |filter: QueryFilter| {
1487 let mut hits = vec![];
1488 context.intersect_ray(
1489 v(-5.0, 0.0),
1490 v(1.0, 0.0),
1491 Real::MAX,
1492 true,
1493 filter,
1494 |e, _, _| {
1495 hits.push(e);
1496 true
1497 },
1498 );
1499 hits.sort();
1500 hits
1501 };
1502 let mut all = vec![ball, cuboid];
1503 all.sort();
1504
1505 let is_cuboid =
1506 |_: Entity, co: &rapier::geometry::Collider| co.shape().as_cuboid().is_some();
1507 let not_cuboid = |e: Entity, _: &rapier::geometry::Collider| e != cuboid;
1508
1509 assert_eq!(hits(QueryFilter::default()), all);
1510 assert_eq!(
1511 hits(QueryFilter::default().predicate(&is_cuboid)),
1512 vec![cuboid]
1513 );
1514 assert_eq!(
1515 hits(QueryFilter::default().predicate(¬_cuboid)),
1516 vec![ball]
1517 );
1518 });
1519 }
1520
1521 #[test]
1522 fn intersect_queries_yield_colliders() {
1523 let (mut app, ball, cuboid) = ball_and_cuboid_app();
1524 run(&mut app, move |context| {
1525 let filter = QueryFilter::default();
1526 let is_ball = |co: &rapier::geometry::Collider| co.shape().as_ball().is_some();
1527
1528 let mut point_hits = vec![];
1529 context.intersect_point(v(0.0, 0.0), filter, |e, co| {
1530 point_hits.push((e, is_ball(co)));
1531 true
1532 });
1533 assert_eq!(point_hits, vec![(ball, true)]);
1534
1535 let mut ray_hits = vec![];
1536 context.intersect_ray(
1537 v(10.0, 5.0),
1538 v(0.0, -1.0),
1539 Real::MAX,
1540 true,
1541 filter,
1542 |e, co, inter| {
1543 ray_hits.push((e, is_ball(co), inter.time_of_impact));
1544 true
1545 },
1546 );
1547 assert_eq!(ray_hits.len(), 1);
1548 assert_eq!(ray_hits[0].0, cuboid);
1549 assert!(!ray_hits[0].1);
1550 assert_relative_eq!(ray_hits[0].2, 4.5, epsilon = 1.0e-4);
1551
1552 let shape = Collider::ball(1.0);
1553 let mut shape_hits = vec![];
1554 context.intersect_shape(
1555 v(10.0, 1.0),
1556 Rot::default(),
1557 &*shape.raw,
1558 filter,
1559 |e, co| {
1560 shape_hits.push((e, is_ball(co)));
1561 true
1562 },
1563 );
1564 assert_eq!(shape_hits, vec![(cuboid, false)]);
1565
1566 let aabb = Collider::ball(0.1).aabb(v(0.0, 0.0), Rot::default());
1567 let mut aabb_hits = vec![];
1568 context.intersect_aabb_conservative(aabb, filter, |e, co| {
1569 aabb_hits.push((e, is_ball(co)));
1570 true
1571 });
1572 assert_eq!(aabb_hits, vec![(ball, true)]);
1573
1574 context.with_query_pipeline(filter, |pipeline| {
1576 let hits: Vec<_> = pipeline
1577 .intersect_point(v(10.0, 0.0))
1578 .map(|(e, co)| (e, is_ball(co)))
1579 .collect();
1580 assert_eq!(hits, vec![(cuboid, false)]);
1581 });
1582 });
1583 }
1584
1585 #[test]
1586 fn distance_closest_points_and_contact_with_scene() {
1587 let (mut app, ball, cuboid) = ball_and_cuboid_app();
1588 run(&mut app, move |context| {
1589 let filter = QueryFilter::default();
1590 let shape = Collider::ball(0.5);
1591 let rot = quarter_turn();
1593
1594 let (e, dist) = context
1595 .distance_to_shape(v(2.0, 0.0), rot, &*shape.raw, filter)
1596 .unwrap();
1597 assert_eq!(e, ball);
1598 assert_relative_eq!(dist, 1.0, epsilon = 1.0e-4);
1599
1600 let (e, dist) = context
1601 .distance_to_shape(
1602 v(2.0, 0.0),
1603 rot,
1604 &*shape.raw,
1605 QueryFilter::default().exclude_collider(ball),
1606 )
1607 .unwrap();
1608 assert_eq!(e, cuboid);
1609 assert_relative_eq!(dist, 7.0, epsilon = 1.0e-4);
1610
1611 assert!(context
1612 .closest_points_to_shape(v(2.0, 0.0), rot, &*shape.raw, 0.5, filter)
1613 .is_none());
1614 let (e, pts) = context
1615 .closest_points_to_shape(v(2.0, 0.0), rot, &*shape.raw, 2.0, filter)
1616 .unwrap();
1617 assert_eq!(e, ball);
1618 let ShapeClosestPoints::WithinMargin(p1, p2) = pts else {
1619 panic!("unexpected closest points: {pts:?}");
1620 };
1621 assert_relative_eq!(p1, v(0.5, 0.0), epsilon = 1.0e-4);
1622 assert_relative_eq!(p2, v(1.5, 0.0), epsilon = 1.0e-4);
1623 let (_, pts) = context
1624 .closest_points_to_shape(v(0.5, 0.0), rot, &*shape.raw, 2.0, filter)
1625 .unwrap();
1626 assert_eq!(pts, ShapeClosestPoints::Intersecting);
1627
1628 assert!(context
1629 .contact_with_shape(v(2.0, 0.0), rot, &*shape.raw, 0.5, filter)
1630 .is_none());
1631 let (e, contact) = context
1632 .contact_with_shape(v(2.0, 0.0), rot, &*shape.raw, 2.0, filter)
1633 .unwrap();
1634 assert_eq!(e, ball);
1635 assert_relative_eq!(contact.distance, 1.0, epsilon = 1.0e-4);
1636 assert_relative_eq!(contact.point1, v(0.5, 0.0), epsilon = 1.0e-4);
1637 assert_relative_eq!(contact.point2, v(1.5, 0.0), epsilon = 1.0e-4);
1638 assert_relative_eq!(contact.normal1, v(1.0, 0.0), epsilon = 1.0e-4);
1639 assert_relative_eq!(contact.normal2, v(-1.0, 0.0), epsilon = 1.0e-4);
1640 });
1641 }
1642
1643 #[test]
1644 fn bvh_leaves_resolve_to_filtered_colliders() {
1645 let (mut app, ball, cuboid) = ball_and_cuboid_app();
1646 run(&mut app, move |context| {
1647 let leaves: Vec<u32> = context.bvh().leaves(|_| true).collect();
1648 assert_eq!(leaves.len(), 2);
1649
1650 let filter = QueryFilter::default().exclude_collider(cuboid);
1651 context.with_query_pipeline(filter, |pipeline| {
1652 let mut resolved: Vec<_> = leaves
1653 .iter()
1654 .filter_map(|leaf| pipeline.bvh_leaf_collider(*leaf))
1655 .map(|(e, _)| e)
1656 .collect();
1657 resolved.sort();
1658 assert_eq!(resolved, vec![ball]);
1659 assert_eq!(pipeline.bvh().leaves(|_| true).count(), 2);
1660 });
1661 });
1662 }
1663
1664 #[test]
1665 fn scene_shape_casts_report_world_space_collider_witnesses() {
1666 let (mut app, _, cuboid) = ball_and_cuboid_app();
1667 let (entity, hit) = run(&mut app, |context| {
1668 let shape = Collider::ball(0.5);
1669 context.cast_shape(
1670 v(5.0, 0.0),
1671 quarter_turn(),
1672 v(1.0, 0.0),
1673 &shape,
1674 ShapeCastOptions::default(),
1675 QueryFilter::default(),
1676 )
1677 })
1678 .unwrap();
1679 assert_eq!(entity, cuboid);
1680 let details = hit.details.unwrap();
1681 assert_relative_eq!(details.witness1, v(9.5, 0.0), epsilon = 1.0e-2);
1683 assert_relative_eq!(details.normal1, v(-1.0, 0.0), epsilon = 1.0e-2);
1684 assert_relative_eq!(details.witness2, v(0.0, -0.5), epsilon = 1.0e-2);
1686 assert_relative_eq!(details.normal2, v(0.0, -1.0), epsilon = 1.0e-2);
1687 }
1688
1689 #[test]
1690 fn move_shape_from_a_user_system() {
1691 let (mut app, ball, _) = ball_and_cuboid_app();
1692 let (output, hits) = app
1693 .world_mut()
1694 .run_system_once(|mut context: WriteRapierContext| {
1695 let mut context = context.single_mut().unwrap();
1696 let options = crate::control::MoveShapeOptions {
1697 snap_to_ground: None,
1698 ..Default::default()
1699 };
1700 let mut hits = vec![];
1701 let output = context.move_shape(
1702 v(8.0, 0.0),
1703 &rapier::parry::shape::Ball::new(0.5),
1704 v(-5.0, 0.0),
1705 Rot::default(),
1706 1.0,
1707 &options,
1708 QueryFilter::default(),
1709 |collision| hits.push(collision.entity),
1710 );
1711 (output, hits)
1712 })
1713 .unwrap();
1714
1715 assert!(output.effective_translation.x < 4.0);
1717 assert!(output.effective_translation.x > 3.5);
1718 assert!(hits.contains(&ball));
1719 }
1720
1721 struct CountingDispatcher(std::sync::Arc<std::sync::atomic::AtomicUsize>);
1724
1725 mod counting_dispatcher {
1726 use super::CountingDispatcher;
1727 use rapier::geometry::{ContactData, ContactManifoldData};
1728 use rapier::math::{Pose, Real, Vector};
1729 use rapier::parry::query::{
1730 details::NormalConstraints, ClosestPoints, Contact, ContactManifold,
1731 ContactManifoldsWorkspace, DefaultQueryDispatcher, NonlinearRigidMotion,
1732 PersistentQueryDispatcher, QueryDispatcher, ShapeCastHit, ShapeCastOptions,
1733 ShapeDistance, ShapeIntersection, Unsupported,
1734 };
1735 use rapier::parry::shape::Shape;
1736 use std::sync::atomic::Ordering;
1737
1738 impl QueryDispatcher for CountingDispatcher {
1739 fn intersection_test(
1740 &self,
1741 pos12: &Pose,
1742 g1: &dyn Shape,
1743 g2: &dyn Shape,
1744 ) -> Result<ShapeIntersection, Unsupported> {
1745 DefaultQueryDispatcher.intersection_test(pos12, g1, g2)
1746 }
1747
1748 fn distance(
1749 &self,
1750 pos12: &Pose,
1751 g1: &dyn Shape,
1752 g2: &dyn Shape,
1753 ) -> Result<ShapeDistance, Unsupported> {
1754 DefaultQueryDispatcher.distance(pos12, g1, g2)
1755 }
1756
1757 fn contact(
1758 &self,
1759 pos12: &Pose,
1760 g1: &dyn Shape,
1761 g2: &dyn Shape,
1762 prediction: Real,
1763 ) -> Result<Option<Contact>, Unsupported> {
1764 DefaultQueryDispatcher.contact(pos12, g1, g2, prediction)
1765 }
1766
1767 fn closest_points(
1768 &self,
1769 pos12: &Pose,
1770 g1: &dyn Shape,
1771 g2: &dyn Shape,
1772 max_dist: Real,
1773 ) -> Result<ClosestPoints, Unsupported> {
1774 DefaultQueryDispatcher.closest_points(pos12, g1, g2, max_dist)
1775 }
1776
1777 fn cast_shapes(
1778 &self,
1779 pos12: &Pose,
1780 local_vel12: Vector,
1781 g1: &dyn Shape,
1782 g2: &dyn Shape,
1783 options: ShapeCastOptions,
1784 ) -> Result<Option<ShapeCastHit>, Unsupported> {
1785 self.0.fetch_add(1, Ordering::Relaxed);
1786 DefaultQueryDispatcher.cast_shapes(pos12, local_vel12, g1, g2, options)
1787 }
1788
1789 fn cast_shapes_nonlinear(
1790 &self,
1791 motion1: &NonlinearRigidMotion,
1792 g1: &dyn Shape,
1793 motion2: &NonlinearRigidMotion,
1794 g2: &dyn Shape,
1795 start_time: Real,
1796 end_time: Real,
1797 stop_at_penetration: bool,
1798 ) -> Result<Option<ShapeCastHit>, Unsupported> {
1799 DefaultQueryDispatcher.cast_shapes_nonlinear(
1800 motion1,
1801 g1,
1802 motion2,
1803 g2,
1804 start_time,
1805 end_time,
1806 stop_at_penetration,
1807 )
1808 }
1809 }
1810
1811 impl PersistentQueryDispatcher<ContactManifoldData, ContactData> for CountingDispatcher {
1812 fn contact_manifolds(
1813 &self,
1814 pos12: &Pose,
1815 g1: &dyn Shape,
1816 g2: &dyn Shape,
1817 prediction: Real,
1818 manifolds: &mut Vec<ContactManifold<ContactManifoldData, ContactData>>,
1819 workspace: &mut Option<ContactManifoldsWorkspace>,
1820 ) -> Result<(), Unsupported> {
1821 DefaultQueryDispatcher
1822 .contact_manifolds(pos12, g1, g2, prediction, manifolds, workspace)
1823 }
1824
1825 fn contact_manifold_convex_convex(
1826 &self,
1827 pos12: &Pose,
1828 g1: &dyn Shape,
1829 g2: &dyn Shape,
1830 normal_constraints1: Option<&dyn NormalConstraints>,
1831 normal_constraints2: Option<&dyn NormalConstraints>,
1832 prediction: Real,
1833 manifold: &mut ContactManifold<ContactManifoldData, ContactData>,
1834 ) -> Result<(), Unsupported> {
1835 DefaultQueryDispatcher.contact_manifold_convex_convex(
1836 pos12,
1837 g1,
1838 g2,
1839 normal_constraints1,
1840 normal_constraints2,
1841 prediction,
1842 manifold,
1843 )
1844 }
1845 }
1846 }
1847
1848 #[test]
1849 fn scene_queries_use_the_context_query_dispatcher() {
1850 let mut app = test_app();
1851 let casts = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
1852 let dispatcher = CountingDispatcher(casts.clone());
1853 app.world_mut()
1854 .run_system_once(move |mut context: WriteRapierContext| {
1855 context
1856 .single_mut()
1857 .unwrap()
1858 .simulation
1859 .set_query_dispatcher(CountingDispatcher(dispatcher.0.clone()));
1860 })
1861 .unwrap();
1862 let ball = app
1863 .world_mut()
1864 .spawn((transform_at(0.0, 0.0), Collider::ball(0.5)))
1865 .id();
1866 app.update();
1867 app.update();
1868
1869 let casts_before = casts.load(std::sync::atomic::Ordering::Relaxed);
1870 let hit = run(&mut app, |context| {
1871 let shape = Collider::ball(0.5);
1872 context.cast_shape(
1873 v(-5.0, 0.0),
1874 Rot::default(),
1875 v(1.0, 0.0),
1876 &*shape.raw,
1877 ShapeCastOptions::default(),
1878 QueryFilter::default(),
1879 )
1880 });
1881 assert_eq!(hit.map(|(e, _)| e), Some(ball));
1882 assert!(casts.load(std::sync::atomic::Ordering::Relaxed) > casts_before);
1883
1884 let casts_before = casts.load(std::sync::atomic::Ordering::Relaxed);
1886 app.world_mut()
1887 .run_system_once(|mut context: WriteRapierContext| {
1888 let shape = Collider::ball(0.5);
1889 context.single_mut().unwrap().move_shape(
1890 v(8.0, 0.0),
1891 &*shape.raw,
1892 v(-5.0, 0.0),
1893 Rot::default(),
1894 1.0,
1895 &Default::default(),
1896 QueryFilter::default(),
1897 |_| {},
1898 );
1899 })
1900 .unwrap();
1901 assert!(casts.load(std::sync::atomic::Ordering::Relaxed) > casts_before);
1902 }
1903}