rapier3d/pipeline/
query_pipeline.rs

1use crate::dynamics::RigidBodyHandle;
2use crate::geometry::{Aabb, Collider, ColliderHandle, PointProjection, Ray, RayIntersection};
3use crate::geometry::{BroadPhaseBvh, InteractionGroups};
4use crate::math::{Isometry, Point, Real, Vector};
5use crate::{dynamics::RigidBodySet, geometry::ColliderSet};
6use parry::bounding_volume::BoundingVolume;
7use parry::partitioning::{Bvh, BvhNode};
8use parry::query::details::{NormalConstraints, ShapeCastOptions};
9use parry::query::{NonlinearRigidMotion, QueryDispatcher, RayCast, ShapeCastHit};
10use parry::shape::{CompositeShape, CompositeShapeRef, FeatureId, Shape, TypedCompositeShape};
11
12/// The query pipeline responsible for running scene queries on the physics world.
13///
14/// This structure is generally obtained by calling [`BroadPhaseBvh::as_query_pipeline_mut`].
15#[derive(Copy, Clone)]
16pub struct QueryPipeline<'a> {
17    /// The query dispatcher for running geometric queries on leaf geometries.
18    pub dispatcher: &'a dyn QueryDispatcher,
19    /// A bvh containing collider indices at its leaves.
20    pub bvh: &'a Bvh,
21    /// Rigid-bodies potentially involved in the scene queries.
22    pub bodies: &'a RigidBodySet,
23    /// Colliders potentially involved in the scene queries.
24    pub colliders: &'a ColliderSet,
25    /// The query filters for controlling what colliders should be ignored by the queries.
26    pub filter: QueryFilter<'a>,
27}
28
29/// Same as [`QueryPipeline`] but holds mutable references to the body and collider sets.
30///
31/// This structure is generally obtained by calling [`BroadPhaseBvh::as_query_pipeline_mut`].
32/// This is useful for argument passing. Call `.as_ref()` for obtaining a `QueryPipeline`
33/// to run the scene queries.
34pub struct QueryPipelineMut<'a> {
35    /// The query dispatcher for running geometric queries on leaf geometries.
36    pub dispatcher: &'a dyn QueryDispatcher,
37    /// A bvh containing collider indices at its leaves.
38    pub bvh: &'a Bvh,
39    /// Rigid-bodies potentially involved in the scene queries.
40    pub bodies: &'a mut RigidBodySet,
41    /// Colliders potentially involved in the scene queries.
42    pub colliders: &'a mut ColliderSet,
43    /// The query filters for controlling what colliders should be ignored by the queries.
44    pub filter: QueryFilter<'a>,
45}
46
47impl QueryPipelineMut<'_> {
48    /// Downgrades the mutable reference to an immutable reference.
49    pub fn as_ref(&self) -> QueryPipeline {
50        QueryPipeline {
51            dispatcher: self.dispatcher,
52            bvh: self.bvh,
53            bodies: &*self.bodies,
54            colliders: &*self.colliders,
55            filter: self.filter,
56        }
57    }
58}
59
60impl CompositeShape for QueryPipeline<'_> {
61    fn map_part_at(
62        &self,
63        shape_id: u32,
64        f: &mut dyn FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>),
65    ) {
66        self.map_untyped_part_at(shape_id, f);
67    }
68    fn bvh(&self) -> &Bvh {
69        self.bvh
70    }
71}
72
73impl TypedCompositeShape for QueryPipeline<'_> {
74    type PartNormalConstraints = ();
75    type PartShape = dyn Shape;
76    fn map_typed_part_at<T>(
77        &self,
78        shape_id: u32,
79        mut f: impl FnMut(
80            Option<&Isometry<Real>>,
81            &Self::PartShape,
82            Option<&Self::PartNormalConstraints>,
83        ) -> T,
84    ) -> Option<T> {
85        let (co, co_handle) = self.colliders.get_unknown_gen(shape_id)?;
86
87        if self.filter.test(self.bodies, co_handle, co) {
88            Some(f(Some(co.position()), co.shape(), None))
89        } else {
90            None
91        }
92    }
93
94    fn map_untyped_part_at<T>(
95        &self,
96        shape_id: u32,
97        mut f: impl FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>) -> T,
98    ) -> Option<T> {
99        let (co, co_handle) = self.colliders.get_unknown_gen(shape_id)?;
100
101        if self.filter.test(self.bodies, co_handle, co) {
102            Some(f(Some(co.position()), co.shape(), None))
103        } else {
104            None
105        }
106    }
107}
108
109impl BroadPhaseBvh {
110    /// Initialize a [`QueryPipeline`] for scene queries from this broad-phase.
111    pub fn as_query_pipeline<'a>(
112        &'a self,
113        dispatcher: &'a dyn QueryDispatcher,
114        bodies: &'a RigidBodySet,
115        colliders: &'a ColliderSet,
116        filter: QueryFilter<'a>,
117    ) -> QueryPipeline<'a> {
118        QueryPipeline {
119            dispatcher,
120            bvh: &self.tree,
121            bodies,
122            colliders,
123            filter,
124        }
125    }
126
127    /// Initialize a [`QueryPipelineMut`] for scene queries from this broad-phase.
128    pub fn as_query_pipeline_mut<'a>(
129        &'a self,
130        dispatcher: &'a dyn QueryDispatcher,
131        bodies: &'a mut RigidBodySet,
132        colliders: &'a mut ColliderSet,
133        filter: QueryFilter<'a>,
134    ) -> QueryPipelineMut<'a> {
135        QueryPipelineMut {
136            dispatcher,
137            bvh: &self.tree,
138            bodies,
139            colliders,
140            filter,
141        }
142    }
143}
144
145impl<'a> QueryPipeline<'a> {
146    fn id_to_handle<T>(&self, (id, data): (u32, T)) -> Option<(ColliderHandle, T)> {
147        self.colliders.get_unknown_gen(id).map(|(_, h)| (h, data))
148    }
149
150    /// Replaces [`Self::filter`] with different filtering rules.
151    pub fn with_filter(self, filter: QueryFilter<'a>) -> Self {
152        Self { filter, ..self }
153    }
154
155    /// Find the closest intersection between a ray and a set of colliders.
156    ///
157    /// # Parameters
158    /// * `colliders` - The set of colliders taking part in this pipeline.
159    /// * `ray`: the ray to cast.
160    /// * `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively
161    ///   limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray.
162    /// * `solid`: if this is `true` an impact at time 0.0 (i.e. at the ray origin) is returned if
163    ///            it starts inside a shape. If this `false` then the ray will hit the shape's boundary
164    ///            even if its starts inside of it.
165    /// * `filter`: set of rules used to determine which collider is taken into account by this scene query.
166    #[profiling::function]
167    pub fn cast_ray(
168        &self,
169        ray: &Ray,
170        max_toi: Real,
171        solid: bool,
172    ) -> Option<(ColliderHandle, Real)> {
173        CompositeShapeRef(self)
174            .cast_local_ray(ray, max_toi, solid)
175            .and_then(|hit| self.id_to_handle(hit))
176    }
177
178    /// Find the closest intersection between a ray and a set of colliders.
179    ///
180    /// # Parameters
181    /// * `colliders` - The set of colliders taking part in this pipeline.
182    /// * `ray`: the ray to cast.
183    /// * `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively
184    ///   limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray.
185    /// * `solid`: if this is `true` an impact at time 0.0 (i.e. at the ray origin) is returned if
186    ///            it starts inside a shape. If this `false` then the ray will hit the shape's boundary
187    ///            even if its starts inside of it.
188    /// * `filter`: set of rules used to determine which collider is taken into account by this scene query.
189    #[profiling::function]
190    pub fn cast_ray_and_get_normal(
191        &self,
192        ray: &Ray,
193        max_toi: Real,
194        solid: bool,
195    ) -> Option<(ColliderHandle, RayIntersection)> {
196        CompositeShapeRef(self)
197            .cast_local_ray_and_get_normal(ray, max_toi, solid)
198            .and_then(|hit| self.id_to_handle(hit))
199    }
200
201    /// Iterates through all the colliders intersecting a given ray.
202    ///
203    /// # Parameters
204    /// * `colliders` - The set of colliders taking part in this pipeline.
205    /// * `ray`: the ray to cast.
206    /// * `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively
207    ///   limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray.
208    /// * `solid`: if this is `true` an impact at time 0.0 (i.e. at the ray origin) is returned if
209    ///            it starts inside a shape. If this `false` then the ray will hit the shape's boundary
210    ///            even if its starts inside of it.
211    #[profiling::function]
212    pub fn intersect_ray(
213        &'a self,
214        ray: Ray,
215        max_toi: Real,
216        solid: bool,
217    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider, RayIntersection)> + 'a {
218        // TODO: add this to CompositeShapeRef?
219        self.bvh
220            .leaves(move |node: &BvhNode| node.aabb().intersects_local_ray(&ray, max_toi))
221            .filter_map(move |leaf| {
222                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
223                if self.filter.test(self.bodies, co_handle, co) {
224                    if let Some(intersection) =
225                        co.shape
226                            .cast_ray_and_get_normal(co.position(), &ray, max_toi, solid)
227                    {
228                        return Some((co_handle, co, intersection));
229                    }
230                }
231
232                None
233            })
234    }
235
236    /// Find the projection of a point on the closest collider.
237    ///
238    /// # Parameters
239    /// * `point` - The point to project.
240    /// * `solid` - If this is set to `true` then the collider shapes are considered to
241    ///   be plain (if the point is located inside of a plain shape, its projection is the point
242    ///   itself). If it is set to `false` the collider shapes are considered to be hollow
243    ///   (if the point is located inside of an hollow shape, it is projected on the shape's
244    ///   boundary).
245    #[profiling::function]
246    pub fn project_point(
247        &self,
248        point: &Point<Real>,
249        _max_dist: Real,
250        solid: bool,
251    ) -> Option<(ColliderHandle, PointProjection)> {
252        self.id_to_handle(CompositeShapeRef(self).project_local_point(point, solid))
253    }
254
255    /// Find all the colliders containing the given point.
256    ///
257    /// # Parameters
258    /// * `point` - The point used for the containment test.
259    #[profiling::function]
260    pub fn intersect_point(
261        &'a self,
262        point: Point<Real>,
263    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
264        // TODO: add to CompositeShapeRef?
265        self.bvh
266            .leaves(move |node: &BvhNode| node.aabb().contains_local_point(&point))
267            .filter_map(move |leaf| {
268                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
269                if self.filter.test(self.bodies, co_handle, co)
270                    && co.shape.contains_point(co.position(), &point)
271                {
272                    return Some((co_handle, co));
273                }
274
275                None
276            })
277    }
278
279    /// Find the projection of a point on the closest collider.
280    ///
281    /// The results include the ID of the feature hit by the point.
282    ///
283    /// # Parameters
284    /// * `point` - The point to project.
285    #[profiling::function]
286    pub fn project_point_and_get_feature(
287        &self,
288        point: &Point<Real>,
289    ) -> Option<(ColliderHandle, PointProjection, FeatureId)> {
290        let (id, (proj, feat)) = CompositeShapeRef(self).project_local_point_and_get_feature(point);
291        let handle = self.colliders.get_unknown_gen(id)?.1;
292        Some((handle, proj, feat))
293    }
294
295    /// Finds all handles of all the colliders with an [`Aabb`] intersecting the given [`Aabb`].
296    ///
297    /// Note that the collider AABB taken into account is the one currently stored in the query
298    /// pipeline’s BVH. It doesn’t recompute the latest collider AABB.
299    #[profiling::function]
300    pub fn intersect_aabb_conservative(
301        &'a self,
302        aabb: Aabb,
303    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
304        // TODO: add to ColliderRef?
305        self.bvh
306            .leaves(move |node: &BvhNode| node.aabb().intersects(&aabb))
307            .filter_map(move |leaf| {
308                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
309                // NOTE: do **not** recompute and check the latest collider AABB.
310                //       Checking only against the one in the BVH is useful, e.g., for conservative
311                //       scene queries for CCD.
312                if self.filter.test(self.bodies, co_handle, co) {
313                    return Some((co_handle, co));
314                }
315
316                None
317            })
318    }
319
320    /// Casts a shape at a constant linear velocity and retrieve the first collider it hits.
321    ///
322    /// This is similar to ray-casting except that we are casting a whole shape instead of just a
323    /// point (the ray origin). In the resulting `TOI`, witness and normal 1 refer to the world
324    /// collider, and are in world space.
325    ///
326    /// # Parameters
327    /// * `shape_pos` - The initial position of the shape to cast.
328    /// * `shape_vel` - The constant velocity of the shape to cast (i.e. the cast direction).
329    /// * `shape` - The shape to cast.
330    /// * `options` - Options controlling the shape cast limits and behavior.
331    #[profiling::function]
332    pub fn cast_shape(
333        &self,
334        shape_pos: &Isometry<Real>,
335        shape_vel: &Vector<Real>,
336        shape: &dyn Shape,
337        options: ShapeCastOptions,
338    ) -> Option<(ColliderHandle, ShapeCastHit)> {
339        CompositeShapeRef(self)
340            .cast_shape(self.dispatcher, shape_pos, shape_vel, shape, options)
341            .and_then(|hit| self.id_to_handle(hit))
342    }
343
344    /// Casts a shape with an arbitrary continuous motion and retrieve the first collider it hits.
345    ///
346    /// In the resulting `TOI`, witness and normal 1 refer to the world collider, and are in world
347    /// space.
348    ///
349    /// # Parameters
350    /// * `shape_motion` - The motion of the shape.
351    /// * `shape` - The shape to cast.
352    /// * `start_time` - The starting time of the interval where the motion takes place.
353    /// * `end_time` - The end time of the interval where the motion takes place.
354    /// * `stop_at_penetration` - If the casted shape starts in a penetration state with any
355    ///    collider, two results are possible. If `stop_at_penetration` is `true` then, the
356    ///    result will have a `toi` equal to `start_time`. If `stop_at_penetration` is `false`
357    ///    then the nonlinear shape-casting will see if further motion with respect to the penetration normal
358    ///    would result in tunnelling. If it does not (i.e. we have a separating velocity along
359    ///    that normal) then the nonlinear shape-casting will attempt to find another impact,
360    ///    at a time `> start_time` that could result in tunnelling.
361    #[profiling::function]
362    pub fn cast_shape_nonlinear(
363        &self,
364        shape_motion: &NonlinearRigidMotion,
365        shape: &dyn Shape,
366        start_time: Real,
367        end_time: Real,
368        stop_at_penetration: bool,
369    ) -> Option<(ColliderHandle, ShapeCastHit)> {
370        CompositeShapeRef(self)
371            .cast_shape_nonlinear(
372                self.dispatcher,
373                &NonlinearRigidMotion::identity(),
374                shape_motion,
375                shape,
376                start_time,
377                end_time,
378                stop_at_penetration,
379            )
380            .and_then(|hit| self.id_to_handle(hit))
381    }
382
383    /// Retrieve all the colliders intersecting the given shape.
384    ///
385    /// # Parameters
386    /// * `shapePos` - The pose of the shape to test.
387    /// * `shape` - The shape to test.
388    #[profiling::function]
389    pub fn intersect_shape(
390        &'a self,
391        shape_pos: Isometry<Real>,
392        shape: &'a dyn Shape,
393    ) -> impl Iterator<Item = (ColliderHandle, &'a Collider)> + 'a {
394        // TODO: add this to CompositeShapeRef?
395        let shape_aabb = shape.compute_aabb(&shape_pos);
396        self.bvh
397            .leaves(move |node: &BvhNode| node.aabb().intersects(&shape_aabb))
398            .filter_map(move |leaf| {
399                let (co, co_handle) = self.colliders.get_unknown_gen(leaf)?;
400                if self.filter.test(self.bodies, co_handle, co) {
401                    let pos12 = shape_pos.inv_mul(co.position());
402                    if self.dispatcher.intersection_test(&pos12, shape, co.shape()) == Ok(true) {
403                        return Some((co_handle, co));
404                    }
405                }
406
407                None
408            })
409    }
410}
411
412bitflags::bitflags! {
413    #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
414    /// Flags for excluding whole sets of colliders from a scene query.
415    pub struct QueryFilterFlags: u32 {
416        /// Exclude from the query any collider attached to a fixed rigid-body and colliders with no rigid-body attached.
417        const EXCLUDE_FIXED = 1 << 0;
418        /// Exclude from the query any collider attached to a kinematic rigid-body.
419        const EXCLUDE_KINEMATIC = 1 << 1;
420        /// Exclude from the query any collider attached to a dynamic rigid-body.
421        const EXCLUDE_DYNAMIC = 1 << 2;
422        /// Exclude from the query any collider that is a sensor.
423        const EXCLUDE_SENSORS = 1 << 3;
424        /// Exclude from the query any collider that is not a sensor.
425        const EXCLUDE_SOLIDS = 1 << 4;
426        /// Excludes all colliders not attached to a dynamic rigid-body.
427        const ONLY_DYNAMIC = Self::EXCLUDE_FIXED.bits() | Self::EXCLUDE_KINEMATIC.bits();
428        /// Excludes all colliders not attached to a kinematic rigid-body.
429        const ONLY_KINEMATIC = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_FIXED.bits();
430        /// Exclude all colliders attached to a non-fixed rigid-body
431        /// (this will not exclude colliders not attached to any rigid-body).
432        const ONLY_FIXED = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_KINEMATIC.bits();
433    }
434}
435
436impl QueryFilterFlags {
437    /// Tests if the given collider should be taken into account by a scene query, based
438    /// on the flags on `self`.
439    #[inline]
440    pub fn test(&self, bodies: &RigidBodySet, collider: &Collider) -> bool {
441        if self.is_empty() {
442            // No filter.
443            return true;
444        }
445
446        if (self.contains(QueryFilterFlags::EXCLUDE_SENSORS) && collider.is_sensor())
447            || (self.contains(QueryFilterFlags::EXCLUDE_SOLIDS) && !collider.is_sensor())
448        {
449            return false;
450        }
451
452        if self.contains(QueryFilterFlags::EXCLUDE_FIXED) && collider.parent.is_none() {
453            return false;
454        }
455
456        if let Some(parent) = collider.parent.and_then(|p| bodies.get(p.handle)) {
457            let parent_type = parent.body_type();
458
459            if (self.contains(QueryFilterFlags::EXCLUDE_FIXED) && parent_type.is_fixed())
460                || (self.contains(QueryFilterFlags::EXCLUDE_KINEMATIC)
461                    && parent_type.is_kinematic())
462                || (self.contains(QueryFilterFlags::EXCLUDE_DYNAMIC) && parent_type.is_dynamic())
463            {
464                return false;
465            }
466        }
467
468        true
469    }
470}
471
472/// A filter that describes what collider should be included or excluded from a scene query.
473#[derive(Copy, Clone, Default)]
474pub struct QueryFilter<'a> {
475    /// Flags indicating what particular type of colliders should be excluded from the scene query.
476    pub flags: QueryFilterFlags,
477    /// If set, only colliders with collision groups compatible with this one will
478    /// be included in the scene query.
479    pub groups: Option<InteractionGroups>,
480    /// If set, this collider will be excluded from the scene query.
481    pub exclude_collider: Option<ColliderHandle>,
482    /// If set, any collider attached to this rigid-body will be excluded from the scene query.
483    pub exclude_rigid_body: Option<RigidBodyHandle>,
484    /// If set, any collider for which this closure returns false will be excluded from the scene query.
485    #[allow(clippy::type_complexity)] // Type doesn’t look really complex?
486    pub predicate: Option<&'a dyn Fn(ColliderHandle, &Collider) -> bool>,
487}
488
489impl QueryFilter<'_> {
490    /// Applies the filters described by `self` to a collider to determine if it has to be
491    /// included in a scene query (`true`) or not (`false`).
492    #[inline]
493    pub fn test(&self, bodies: &RigidBodySet, handle: ColliderHandle, collider: &Collider) -> bool {
494        self.exclude_collider != Some(handle)
495            && (self.exclude_rigid_body.is_none() // NOTE: deal with the `None` case separately otherwise the next test is incorrect if the collider’s parent is `None` too.
496            || self.exclude_rigid_body != collider.parent.map(|p| p.handle))
497            && self
498                .groups
499                .map(|grps| collider.flags.collision_groups.test(grps))
500                .unwrap_or(true)
501            && self.flags.test(bodies, collider)
502            && self.predicate.map(|f| f(handle, collider)).unwrap_or(true)
503    }
504}
505
506impl From<QueryFilterFlags> for QueryFilter<'_> {
507    fn from(flags: QueryFilterFlags) -> Self {
508        Self {
509            flags,
510            ..QueryFilter::default()
511        }
512    }
513}
514
515impl From<InteractionGroups> for QueryFilter<'_> {
516    fn from(groups: InteractionGroups) -> Self {
517        Self {
518            groups: Some(groups),
519            ..QueryFilter::default()
520        }
521    }
522}
523
524impl<'a> QueryFilter<'a> {
525    /// A query filter that doesn’t exclude any collider.
526    pub fn new() -> Self {
527        Self::default()
528    }
529
530    /// Exclude from the query any collider attached to a fixed rigid-body and colliders with no rigid-body attached.
531    pub fn exclude_fixed() -> Self {
532        QueryFilterFlags::EXCLUDE_FIXED.into()
533    }
534
535    /// Exclude from the query any collider attached to a kinematic rigid-body.
536    pub fn exclude_kinematic() -> Self {
537        QueryFilterFlags::EXCLUDE_KINEMATIC.into()
538    }
539
540    /// Exclude from the query any collider attached to a dynamic rigid-body.
541    pub fn exclude_dynamic() -> Self {
542        QueryFilterFlags::EXCLUDE_DYNAMIC.into()
543    }
544
545    /// Excludes all colliders not attached to a dynamic rigid-body.
546    pub fn only_dynamic() -> Self {
547        QueryFilterFlags::ONLY_DYNAMIC.into()
548    }
549
550    /// Excludes all colliders not attached to a kinematic rigid-body.
551    pub fn only_kinematic() -> Self {
552        QueryFilterFlags::ONLY_KINEMATIC.into()
553    }
554
555    /// Exclude all colliders attached to a non-fixed rigid-body
556    /// (this will not exclude colliders not attached to any rigid-body).
557    pub fn only_fixed() -> Self {
558        QueryFilterFlags::ONLY_FIXED.into()
559    }
560
561    /// Exclude from the query any collider that is a sensor.
562    pub fn exclude_sensors(mut self) -> Self {
563        self.flags |= QueryFilterFlags::EXCLUDE_SENSORS;
564        self
565    }
566
567    /// Exclude from the query any collider that is not a sensor.
568    pub fn exclude_solids(mut self) -> Self {
569        self.flags |= QueryFilterFlags::EXCLUDE_SOLIDS;
570        self
571    }
572
573    /// Only colliders with collision groups compatible with this one will
574    /// be included in the scene query.
575    pub fn groups(mut self, groups: InteractionGroups) -> Self {
576        self.groups = Some(groups);
577        self
578    }
579
580    /// Set the collider that will be excluded from the scene query.
581    pub fn exclude_collider(mut self, collider: ColliderHandle) -> Self {
582        self.exclude_collider = Some(collider);
583        self
584    }
585
586    /// Set the rigid-body that will be excluded from the scene query.
587    pub fn exclude_rigid_body(mut self, rigid_body: RigidBodyHandle) -> Self {
588        self.exclude_rigid_body = Some(rigid_body);
589        self
590    }
591
592    /// Set the predicate to apply a custom collider filtering during the scene query.
593    pub fn predicate(mut self, predicate: &'a impl Fn(ColliderHandle, &Collider) -> bool) -> Self {
594        self.predicate = Some(predicate);
595        self
596    }
597}