Skip to main content

pfx_physics/rigid/
query.rs

1use rapier3d::parry::query::ShapeCastOptions;
2use rapier3d::prelude::{Collider, ColliderHandle, Group, QueryFilter, QueryPipeline, Ray};
3
4use super::convert::{body_handle, body_id, collider_id, iso, vec};
5use super::{BodyId, ColliderId, Pose, Shape, World};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct Filter {
9    pub mask: u32,
10    pub sensors: bool,
11    pub exclude: Option<BodyId>,
12}
13
14impl Filter {
15    pub const ALL: Self = Self {
16        mask: u32::MAX,
17        sensors: false,
18        exclude: None,
19    };
20
21    pub fn layers(mask: u32) -> Self {
22        Self { mask, ..Self::ALL }
23    }
24
25    pub fn with_sensors(mut self) -> Self {
26        self.sensors = true;
27        self
28    }
29
30    pub fn excluding(mut self, body: BodyId) -> Self {
31        self.exclude = Some(body);
32        self
33    }
34
35    fn with_pipeline<R>(self, world: &World, run: impl FnOnce(&QueryPipeline<'_>) -> R) -> R {
36        let mask = Group::from_bits_retain(self.mask);
37        let by_layer = move |_: ColliderHandle, collider: &Collider| {
38            collider.collision_groups().memberships.intersects(mask)
39        };
40        let mut filter = QueryFilter::new().predicate(&by_layer);
41        if !self.sensors {
42            filter = filter.exclude_sensors();
43        }
44        if let Some(body) = self.exclude {
45            filter = filter.exclude_rigid_body(body_handle(body));
46        }
47        let physics = &world.physics;
48        let pipeline = world.queries.as_query_pipeline(
49            physics.narrow_phase.query_dispatcher(),
50            &physics.bodies,
51            &physics.colliders,
52            filter,
53        );
54        run(&pipeline)
55    }
56}
57
58impl Default for Filter {
59    fn default() -> Self {
60        Self::ALL
61    }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq)]
65pub struct Hit {
66    pub collider: ColliderId,
67    pub body: Option<BodyId>,
68    pub distance: f32,
69    pub point: [f32; 3],
70    pub normal: [f32; 3],
71}
72
73impl World {
74    pub fn raycast(
75        &self,
76        origin: [f32; 3],
77        direction: [f32; 3],
78        max_distance: f32,
79        filter: Filter,
80    ) -> Option<Hit> {
81        let direction = vec(direction).normalize_or_zero();
82        if direction == super::convert::Vec3::ZERO {
83            return None;
84        }
85        let ray = Ray::new(vec(origin), direction);
86        let physics = &self.physics;
87        let (handle, hit) = filter.with_pipeline(self, |pipeline| {
88            pipeline.cast_ray_and_get_normal(&ray, max_distance, true)
89        })?;
90        Some(Hit {
91            collider: collider_id(handle),
92            body: physics
93                .colliders
94                .get(handle)
95                .and_then(|c| c.parent())
96                .map(body_id),
97            distance: hit.time_of_impact,
98            point: ray.point_at(hit.time_of_impact).to_array(),
99            normal: hit.normal.to_array(),
100        })
101    }
102
103    pub fn shape_cast(
104        &self,
105        shape: &Shape,
106        pose: Pose,
107        direction: [f32; 3],
108        max_distance: f32,
109        filter: Filter,
110    ) -> Option<Hit> {
111        let shared = shape.shared()?;
112        let direction = vec(direction).normalize_or_zero();
113        if direction == super::convert::Vec3::ZERO {
114            return None;
115        }
116        let start = iso(pose);
117        let physics = &self.physics;
118        let (handle, hit) = filter.with_pipeline(self, |pipeline| {
119            pipeline.cast_shape(
120                &start,
121                direction,
122                shared.as_ref(),
123                ShapeCastOptions::with_max_time_of_impact(max_distance),
124            )
125        })?;
126        let mut at = start;
127        at.translation += direction * hit.time_of_impact;
128        Some(Hit {
129            collider: collider_id(handle),
130            body: physics
131                .colliders
132                .get(handle)
133                .and_then(|c| c.parent())
134                .map(body_id),
135            distance: hit.time_of_impact,
136            point: at.transform_point(hit.witness2).to_array(),
137            normal: hit.normal1.to_array(),
138        })
139    }
140
141    pub fn point_overlaps(&self, point: [f32; 3], filter: Filter) -> Vec<ColliderId> {
142        let mut found: Vec<ColliderId> = filter.with_pipeline(self, |pipeline| {
143            pipeline
144                .intersect_point(vec(point))
145                .map(|(handle, _)| collider_id(handle))
146                .collect()
147        });
148        found.sort();
149        found
150    }
151
152    pub fn shape_overlaps(&self, shape: &Shape, pose: Pose, filter: Filter) -> Vec<ColliderId> {
153        let Some(shared) = shape.shared() else {
154            return Vec::new();
155        };
156        let mut found: Vec<ColliderId> = filter.with_pipeline(self, |pipeline| {
157            pipeline
158                .intersect_shape(iso(pose), shared.as_ref())
159                .map(|(handle, _)| collider_id(handle))
160                .collect()
161        });
162        found.sort();
163        found
164    }
165}