bevy_rapier2d/plugin/context/systemparams/
rapier_context_systemparam.rs

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/// Utility [`SystemParam`] to easily access every required components of a [`RapierContext`] immutably.
15///
16/// This uses the [`DefaultRapierContext`] filter by default, but you can use a custom query filter with the `T` type parameter.
17#[derive(SystemParam)]
18pub struct ReadRapierContext<'w, 's, T: query::QueryFilter + 'static = With<DefaultRapierContext>> {
19    /// The query used to feed components into [`RapierContext`] struct through [`ReadRapierContext::single`].
20    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    /// Returns a single [`RapierContext`] corresponding to the filter (T) of [`ReadRapierContext`].
35    ///
36    /// If the number of query items is not exactly one, a [`bevy::ecs::query::QuerySingleError`] is returned instead.
37    ///
38    /// You can also use the underlying query [`ReadRapierContext::rapier_context`] for finer grained queries.
39    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/// A helper struct to avoid passing too many parameters to most rapier functions.
51/// This helps with reducing boilerplate, at the (small) price of maybe getting too much information from the ECS.
52///
53/// Note: This is not a component, refer to [`ReadRapierContext`], [`WriteRapierContext`], or [`RapierContextSimulation`]
54#[cfg_attr(feature = "serde-serialize", derive(Serialize))]
55#[derive(query::QueryData)]
56pub struct RapierContext<'a> {
57    /// The Rapier context, containing all the state of the physics engine.
58    pub simulation: &'a RapierContextSimulation,
59    /// The set of colliders part of the simulation.
60    pub colliders: &'a RapierContextColliders,
61    /// The sets of joints part of the simulation.
62    pub joints: &'a RapierContextJoints,
63    /// The set of rigid-bodies part of the simulation.
64    pub rigidbody_set: &'a RapierRigidBodySet,
65}
66
67/// Utility [`SystemParam`] to easily access every required components of a [`RapierContext`] mutably.
68///
69/// This uses the [`DefaultRapierContext`] filter by default, but you can use a custom query filter with the `T` type parameter.
70#[derive(SystemParam)]
71pub struct WriteRapierContext<'w, 's, T: query::QueryFilter + 'static = With<DefaultRapierContext>>
72{
73    /// The query used to feed components into [`RapierContext`] struct through [`ReadRapierContext::single`].
74    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    /// Returns a single [`RapierContext`] corresponding to the filter (T) of [`WriteRapierContext`].
89    ///
90    /// If the number of query items is not exactly one, a [`bevy::ecs::query::QuerySingleError`] is returned instead.
91    ///
92    /// You can also use the underlying query [`WriteRapierContext::rapier_context`] for finer grained queries.
93    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    /// Returns a single mutable [`RapierContextMut`] corresponding to the filter (T) of [`WriteRapierContext`].
104    ///
105    /// If the number of query items is not exactly one, a [`bevy::ecs::query::QuerySingleError`] is returned instead.
106    ///
107    /// You can also use the underlying query [`WriteRapierContext::rapier_context`] for finer grained queries.
108    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
119/// A helper struct to avoid passing too many parameters to most rapier functions.
120/// This helps with reducing boilerplate, at the (small) price of maybe getting too much information from the ECS.
121///
122/// If you need more granular control over mutability of each component, use a regular [`Query`]
123pub struct RapierContextMut<'a> {
124    /// The Rapier context, containing all the state of the physics engine.
125    pub simulation: Mut<'a, RapierContextSimulation>,
126    /// The set of colliders part of the simulation.
127    pub colliders: Mut<'a, RapierContextColliders>,
128    /// The sets of joints part of the simulation.
129    pub joints: Mut<'a, RapierContextJoints>,
130    /// The set of rigid-bodies part of the simulation.
131    pub rigidbody_set: Mut<'a, RapierRigidBodySet>,
132}
133
134/// [`RapierRigidBodySet`] functions
135mod simulation {
136    use crate::control::CharacterCollision;
137    use crate::control::MoveShapeOptions;
138    use crate::control::MoveShapeOutput;
139    use crate::plugin::context::SimulationToRenderTime;
140    use crate::plugin::ContactPairView;
141    use crate::plugin::TimestepMode;
142    use crate::prelude::CollisionEvent;
143    use crate::prelude::ContactForceEvent;
144    use crate::prelude::RapierQueryPipelineMut;
145    use crate::prelude::RapierRigidBodyHandle;
146    use crate::prelude::TransformInterpolation;
147    use rapier::prelude::PhysicsHooks;
148    use rapier::prelude::Shape;
149
150    use super::*;
151
152    /// [`RapierContextSimulation`] functions for immutable accesses
153    impl RapierContext<'_> {
154        /// Shortcut to [`RapierContextSimulation::contact_pair`].
155        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        /// Shortcut to [`RapierContextSimulation::contact_pairs_with`].
165        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        /// Shortcut to [`RapierContextSimulation::intersection_pair`].
174        pub fn intersection_pair(&self, collider1: Entity, collider2: Entity) -> Option<bool> {
175            self.simulation
176                .intersection_pair(self.colliders, collider1, collider2)
177        }
178
179        /// Shortcut to [`RapierContextSimulation::intersection_pairs_with`].
180        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    /// [`RapierContextSimulation`] functions for mutable accesses
190    impl RapierContextMut<'_> {
191        /// Shortcut to [`RapierContextSimulation::step_simulation`].
192        #[expect(clippy::too_many_arguments)]
193        pub fn step_simulation(
194            &mut self,
195            gravity: Vect,
196            timestep_mode: TimestepMode,
197            events: Option<(
198                &MessageWriter<CollisionEvent>,
199                &MessageWriter<ContactForceEvent>,
200            )>,
201            hooks: &dyn PhysicsHooks,
202            time: &Time,
203            sim_to_render_time: &mut SimulationToRenderTime,
204            interpolation_query: Option<
205                &mut Query<(&RapierRigidBodyHandle, &mut TransformInterpolation)>,
206            >,
207        ) {
208            self.simulation.step_simulation(
209                &mut self.colliders,
210                &mut self.joints,
211                &mut self.rigidbody_set,
212                gravity,
213                timestep_mode,
214                events,
215                hooks,
216                time,
217                sim_to_render_time,
218                interpolation_query,
219            )
220        }
221
222        /// Shortcut to [`RapierContextSimulation::move_shape`].
223        #[expect(clippy::too_many_arguments)]
224        pub fn move_shape(
225            &mut self,
226            query_pipeline_mut: &mut RapierQueryPipelineMut<'_>,
227            movement: Vect,
228            shape: &dyn Shape,
229            shape_translation: Vect,
230            shape_rotation: Rot,
231            shape_mass: Real,
232            options: &MoveShapeOptions,
233            events: impl FnMut(CharacterCollision),
234        ) -> MoveShapeOutput {
235            self.simulation.move_shape(
236                &self.colliders,
237                query_pipeline_mut,
238                movement,
239                shape,
240                shape_translation,
241                shape_rotation,
242                shape_mass,
243                options,
244                events,
245            )
246        }
247
248        /// Shortcut to [`RapierContextSimulation::contact_pair`].
249        pub fn contact_pair(
250            &self,
251            collider1: Entity,
252            collider2: Entity,
253        ) -> Option<ContactPairView<'_>> {
254            self.simulation
255                .contact_pair(&self.colliders, &self.rigidbody_set, collider1, collider2)
256        }
257
258        /// Shortcut to [`RapierContextSimulation::contact_pairs_with`].
259        pub fn contact_pairs_with(
260            &self,
261            collider: Entity,
262        ) -> impl Iterator<Item = ContactPairView<'_>> {
263            self.simulation
264                .contact_pairs_with(&self.colliders, &self.rigidbody_set, collider)
265        }
266
267        /// Shortcut to [`RapierContextSimulation::intersection_pair`].
268        pub fn intersection_pair(&self, collider1: Entity, collider2: Entity) -> Option<bool> {
269            self.simulation
270                .intersection_pair(&self.colliders, collider1, collider2)
271        }
272
273        /// Shortcut to [`RapierContextSimulation::intersection_pairs_with`].
274        pub fn intersection_pairs_with(
275            &self,
276            collider: Entity,
277        ) -> impl Iterator<Item = (Entity, Entity, bool)> + '_ {
278            self.simulation
279                .intersection_pairs_with(&self.colliders, collider)
280        }
281    }
282}
283
284mod query_pipeline {
285    use rapier::{
286        parry::query::{DefaultQueryDispatcher, ShapeCastOptions},
287        prelude::Shape,
288    };
289
290    use crate::prelude::{PointProjection, QueryFilter, RayIntersection, ShapeCastHit};
291
292    use super::*;
293
294    impl RapierContext<'_> {
295        /// Shortcut to [RapierQueryPipeline::new_scoped].
296        pub fn with_query_pipeline<'a, T>(
297            &'a self,
298            filter: QueryFilter<'a>,
299            scoped_fn: impl FnOnce(RapierQueryPipeline<'_>) -> T,
300        ) -> T {
301            crate::prelude::RapierQueryPipeline::new_scoped(
302                &self.simulation.broad_phase,
303                self.colliders,
304                self.rigidbody_set,
305                &filter,
306                &DefaultQueryDispatcher,
307                scoped_fn,
308            )
309        }
310        /// Shortcut to [`RapierQueryPipeline::cast_ray`].
311        pub fn cast_ray(
312            &self,
313            ray_origin: Vect,
314            ray_dir: Vect,
315            max_toi: Real,
316            solid: bool,
317            filter: QueryFilter,
318        ) -> Option<(Entity, Real)> {
319            self.with_query_pipeline(filter, |query_pipeline| {
320                query_pipeline.cast_ray(ray_origin, ray_dir, max_toi, solid)
321            })
322        }
323
324        /// Shortcut to [`RapierQueryPipeline::cast_ray_and_get_normal`].
325        pub fn cast_ray_and_get_normal(
326            &self,
327            ray_origin: Vect,
328            ray_dir: Vect,
329            max_toi: Real,
330            solid: bool,
331            filter: QueryFilter,
332        ) -> Option<(Entity, RayIntersection)> {
333            self.with_query_pipeline(filter, |query_pipeline| {
334                query_pipeline.cast_ray_and_get_normal(ray_origin, ray_dir, max_toi, solid)
335            })
336        }
337
338        /// Shortcut to [`RapierQueryPipeline::intersect_point`].
339        ///
340        /// Stops the query if `callback` returns false.
341        pub fn intersect_point(
342            &self,
343            point: Vect,
344            filter: QueryFilter,
345            mut callback: impl FnMut(Entity) -> bool,
346        ) {
347            self.with_query_pipeline(filter, |query_pipeline| {
348                for e in query_pipeline.intersect_point(point) {
349                    if !callback(e) {
350                        break;
351                    }
352                }
353            });
354        }
355
356        /// Shortcut to [`RapierQueryPipeline::intersect_ray`].
357        ///
358        /// Stops the query if `callback` returns false.
359        pub fn intersect_ray(
360            &self,
361            ray_origin: Vect,
362            ray_dir: Vect,
363            max_toi: Real,
364            solid: bool,
365            filter: QueryFilter,
366            mut callback: impl FnMut(Entity, RayIntersection) -> bool,
367        ) {
368            self.with_query_pipeline(filter, |query_pipeline| {
369                for (e, intersection) in
370                    query_pipeline.intersect_ray(ray_origin, ray_dir, max_toi, solid)
371                {
372                    if !callback(e, intersection) {
373                        break;
374                    }
375                }
376            });
377        }
378
379        /// Shortcut to [`RapierQueryPipeline::intersect_shape`].
380        pub fn intersect_shape(
381            &self,
382            shape_pos: Vect,
383            shape_rot: Rot,
384            shape: &dyn Shape,
385            filter: QueryFilter,
386            mut callback: impl FnMut(Entity) -> bool,
387        ) {
388            self.with_query_pipeline(filter, |query_pipeline| {
389                for e in query_pipeline.intersect_shape(shape_pos, shape_rot, shape) {
390                    if !callback(e) {
391                        break;
392                    }
393                }
394            });
395        }
396
397        /// Shortcut to [`RapierQueryPipeline::intersect_aabb_conservative`].
398        pub fn intersect_aabb_conservative(
399            &self,
400            #[cfg(feature = "dim2")] aabb: bevy::math::bounding::Aabb2d,
401            #[cfg(feature = "dim3")] aabb: bevy::math::bounding::Aabb3d,
402            filter: QueryFilter,
403            mut callback: impl FnMut(Entity) -> bool,
404        ) {
405            self.with_query_pipeline(filter, |query_pipeline| {
406                for e in query_pipeline.intersect_aabb_conservative(aabb) {
407                    if !callback(e) {
408                        break;
409                    }
410                }
411            });
412        }
413
414        /// Shortcut to [`RapierQueryPipeline::cast_shape`].
415        pub fn cast_shape(
416            &self,
417            shape_pos: Vect,
418            shape_rot: Rot,
419            shape_vel: Vect,
420            shape: &dyn Shape,
421            options: ShapeCastOptions,
422            filter: QueryFilter,
423        ) -> Option<(Entity, ShapeCastHit)> {
424            self.with_query_pipeline(filter, |query_pipeline| {
425                query_pipeline.cast_shape(shape_pos, shape_rot, shape_vel, shape, options)
426            })
427        }
428
429        /// Shortcut to [`RapierQueryPipeline::project_point`].
430        pub fn project_point(
431            &self,
432            point: Vect,
433            max_dist: f32,
434            solid: bool,
435            filter: QueryFilter,
436        ) -> Option<(Entity, PointProjection)> {
437            self.with_query_pipeline(filter, |query_pipeline| {
438                query_pipeline.project_point(point, max_dist, solid)
439            })
440        }
441    }
442
443    // Copied from `RapierContext`.
444    impl RapierContextMut<'_> {
445        /// Shortcut to [`RapierQueryPipeline::cast_ray`].
446        pub fn cast_ray(
447            &self,
448            ray_origin: Vect,
449            ray_dir: Vect,
450            max_toi: Real,
451            solid: bool,
452            filter: QueryFilter,
453        ) -> Option<(Entity, Real)> {
454            self.with_query_pipeline(filter, |query_pipeline| {
455                query_pipeline.cast_ray(ray_origin, ray_dir, max_toi, solid)
456            })
457        }
458
459        /// Shortcut to [`RapierQueryPipeline::cast_ray_and_get_normal`].
460        pub fn cast_ray_and_get_normal(
461            &self,
462            ray_origin: Vect,
463            ray_dir: Vect,
464            max_toi: Real,
465            solid: bool,
466            filter: QueryFilter,
467        ) -> Option<(Entity, RayIntersection)> {
468            self.with_query_pipeline(filter, |query_pipeline| {
469                query_pipeline.cast_ray_and_get_normal(ray_origin, ray_dir, max_toi, solid)
470            })
471        }
472
473        /// Shortcut to [`RapierQueryPipeline::intersect_point`].
474        ///
475        /// Stops the query if `callback` returns false.
476        pub fn intersect_point(
477            &self,
478            point: Vect,
479            filter: QueryFilter,
480            mut callback: impl FnMut(Entity) -> bool,
481        ) {
482            self.with_query_pipeline(filter, |query_pipeline| {
483                for e in query_pipeline.intersect_point(point) {
484                    if !callback(e) {
485                        break;
486                    }
487                }
488            });
489        }
490
491        /// Shortcut to [`RapierQueryPipeline::intersect_ray`].
492        ///
493        /// Stops the query if `callback` returns false.
494        pub fn intersect_ray(
495            &self,
496            ray_origin: Vect,
497            ray_dir: Vect,
498            max_toi: Real,
499            solid: bool,
500            filter: QueryFilter,
501            mut callback: impl FnMut(Entity, RayIntersection) -> bool,
502        ) {
503            self.with_query_pipeline(filter, |query_pipeline| {
504                for (e, intersection) in
505                    query_pipeline.intersect_ray(ray_origin, ray_dir, max_toi, solid)
506                {
507                    if !callback(e, intersection) {
508                        break;
509                    }
510                }
511            });
512        }
513
514        /// Shortcut to [`RapierQueryPipeline::intersect_shape`].
515        pub fn intersect_shape(
516            &self,
517            shape_pos: Vect,
518            shape_rot: Rot,
519            shape: &dyn Shape,
520            filter: QueryFilter,
521            mut callback: impl FnMut(Entity) -> bool,
522        ) {
523            self.with_query_pipeline(filter, |query_pipeline| {
524                for e in query_pipeline.intersect_shape(shape_pos, shape_rot, shape) {
525                    if !callback(e) {
526                        break;
527                    }
528                }
529            });
530        }
531
532        /// Shortcut to [`RapierQueryPipeline::intersect_aabb_conservative`].
533        pub fn intersect_aabb_conservative(
534            &self,
535            #[cfg(feature = "dim2")] aabb: bevy::math::bounding::Aabb2d,
536            #[cfg(feature = "dim3")] aabb: bevy::math::bounding::Aabb3d,
537            filter: QueryFilter,
538            mut callback: impl FnMut(Entity) -> bool,
539        ) {
540            self.with_query_pipeline(filter, |query_pipeline| {
541                for e in query_pipeline.intersect_aabb_conservative(aabb) {
542                    if !callback(e) {
543                        break;
544                    }
545                }
546            });
547        }
548
549        /// Shortcut to [`RapierQueryPipeline::cast_shape`].
550        pub fn cast_shape(
551            &self,
552            shape_pos: Vect,
553            shape_rot: Rot,
554            shape_vel: Vect,
555            shape: &dyn Shape,
556            options: ShapeCastOptions,
557            filter: QueryFilter,
558        ) -> Option<(Entity, ShapeCastHit)> {
559            self.with_query_pipeline(filter, |query_pipeline| {
560                query_pipeline.cast_shape(shape_pos, shape_rot, shape_vel, shape, options)
561            })
562        }
563
564        /// Shortcut to [`RapierQueryPipeline::project_point`].
565        pub fn project_point(
566            &self,
567            point: Vect,
568            max_dist: f32,
569            solid: bool,
570            filter: QueryFilter,
571        ) -> Option<(Entity, PointProjection)> {
572            self.with_query_pipeline(filter, |query_pipeline| {
573                query_pipeline.project_point(point, max_dist, solid)
574            })
575        }
576    }
577
578    impl RapierContextMut<'_> {
579        /// Shortcut to [RapierQueryPipeline::new_scoped].
580        pub fn with_query_pipeline<'a, T>(
581            &'a self,
582            filter: QueryFilter<'a>,
583            scoped_fn: impl FnOnce(RapierQueryPipeline<'_>) -> T,
584        ) -> T {
585            crate::prelude::RapierQueryPipeline::new_scoped(
586                &self.simulation.broad_phase,
587                &self.colliders,
588                &self.rigidbody_set,
589                &filter,
590                &DefaultQueryDispatcher,
591                scoped_fn,
592            )
593        }
594    }
595}
596
597mod rigidbody_set {
598    use std::collections::HashMap;
599
600    use super::*;
601    pub use rapier::prelude::RigidBodyHandle;
602
603    impl RapierContext<'_> {
604        /// Shortcut to [`RapierRigidBodySet::entity2body`].
605        pub fn entity2body(&self) -> &HashMap<Entity, RigidBodyHandle> {
606            self.rigidbody_set.entity2body()
607        }
608
609        /// Shortcut to [`RapierRigidBodySet::rigid_body_entity`].
610        pub fn rigid_body_entity(&self, handle: RigidBodyHandle) -> Option<Entity> {
611            self.rigidbody_set.rigid_body_entity(handle)
612        }
613
614        /// Shortcut to [`RapierRigidBodySet::impulse_revolute_joint_angle`].
615        pub fn impulse_revolute_joint_angle(&self, entity: Entity) -> Option<f32> {
616            self.rigidbody_set
617                .impulse_revolute_joint_angle(self.joints, entity)
618        }
619    }
620
621    impl RapierContextMut<'_> {
622        /// Shortcut to [`RapierRigidBodySet::propagate_modified_body_positions_to_colliders`].
623        pub fn propagate_modified_body_positions_to_colliders(&mut self) {
624            self.rigidbody_set
625                .propagate_modified_body_positions_to_colliders(&mut self.colliders)
626        }
627
628        /// Shortcut to [`RapierRigidBodySet::entity2body`].
629        pub fn entity2body(&self) -> &HashMap<Entity, RigidBodyHandle> {
630            self.rigidbody_set.entity2body()
631        }
632
633        /// Shortcut to [`RapierRigidBodySet::rigid_body_entity`].
634        pub fn rigid_body_entity(&self, handle: RigidBodyHandle) -> Option<Entity> {
635            self.rigidbody_set.rigid_body_entity(handle)
636        }
637
638        /// Shortcut to [`RapierRigidBodySet::impulse_revolute_joint_angle`].
639        pub fn impulse_revolute_joint_angle(&self, entity: Entity) -> Option<f32> {
640            self.rigidbody_set
641                .impulse_revolute_joint_angle(&self.joints, entity)
642        }
643    }
644}