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}