parry3d/query/contact_manifolds/
contact_manifolds_voxels_shape.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{IVector, Int, Pose, Real, Vector, DIM};
3use crate::query::{
4    ContactManifold, ContactManifoldsWorkspace, PersistentQueryDispatcher, PointQuery,
5    TypedWorkspaceData, WorkspaceData,
6};
7use crate::shape::{AxisMask, Cuboid, Shape, SupportMap, VoxelData, VoxelType, Voxels};
8use crate::utils::hashmap::{Entry, HashMap};
9use crate::utils::PoseOpt;
10use alloc::{boxed::Box, vec::Vec};
11use num::AsPrimitive;
12
13#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
14#[cfg_attr(
15    feature = "rkyv",
16    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
17)]
18#[derive(Clone)]
19pub(crate) struct VoxelsShapeSubDetector {
20    pub manifold_id: usize,
21    pub selected_contacts: u32,
22    pub timestamp: bool,
23}
24
25#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
26#[derive(Clone, Eq, Hash, PartialEq)]
27pub(crate) struct VoxelsWorkspaceKey<const N: usize> {
28    #[cfg_attr(feature = "serde-serialize", serde(with = "serde_arrays"))]
29    idx: [u32; N],
30}
31
32impl<const N: usize> From<[u32; N]> for VoxelsWorkspaceKey<N> {
33    fn from(idx: [u32; N]) -> Self {
34        Self { idx }
35    }
36}
37
38// NOTE: this is using a similar kind of cache as compound shape and height-field.
39//       It is different from the trimesh cash though. Which one is better?
40/// A workspace for collision-detection against voxels shape.
41#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
42#[derive(Clone, Default)]
43pub struct VoxelsShapeContactManifoldsWorkspace<const N: usize> {
44    pub(crate) timestamp: bool,
45    pub(crate) sub_detectors: HashMap<VoxelsWorkspaceKey<N>, VoxelsShapeSubDetector>,
46}
47
48impl<const N: usize> VoxelsShapeContactManifoldsWorkspace<N> {
49    /// A new empty workspace for collision-detection against voxels shape.
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    pub(crate) fn ensure_exists(workspace: &mut Option<ContactManifoldsWorkspace>)
55    where
56        Self: WorkspaceData,
57    {
58        if workspace
59            .as_ref()
60            .and_then(|w| {
61                w.0.downcast_ref::<VoxelsShapeContactManifoldsWorkspace<N>>()
62            })
63            .is_some()
64        {
65            return;
66        }
67
68        *workspace = Some(ContactManifoldsWorkspace(Box::new(
69            VoxelsShapeContactManifoldsWorkspace::new(),
70        )));
71    }
72}
73
74impl WorkspaceData for VoxelsShapeContactManifoldsWorkspace<2> {
75    fn as_typed_workspace_data(&self) -> TypedWorkspaceData<'_> {
76        TypedWorkspaceData::VoxelsShapeContactManifoldsWorkspace(self)
77    }
78
79    fn clone_dyn(&self) -> Box<dyn WorkspaceData> {
80        Box::new(self.clone())
81    }
82}
83
84/// Computes the contact manifold between a convex shape and a voxels shape, both represented as a `Shape` trait-object.
85pub fn contact_manifolds_voxels_shape_shapes<ManifoldData, ContactData>(
86    dispatcher: &dyn PersistentQueryDispatcher<ManifoldData, ContactData>,
87    pos12: &Pose,
88    shape1: &dyn Shape,
89    shape2: &dyn Shape,
90    prediction: Real,
91    manifolds: &mut Vec<ContactManifold<ManifoldData, ContactData>>,
92    workspace: &mut Option<ContactManifoldsWorkspace>,
93) where
94    ManifoldData: Default + Clone,
95    ContactData: Default + Copy,
96{
97    if let Some(voxels1) = shape1.as_voxels() {
98        contact_manifolds_voxels_shape(
99            dispatcher, pos12, voxels1, shape2, prediction, manifolds, workspace, false,
100        );
101    } else if let Some(voxels2) = shape2.as_voxels() {
102        contact_manifolds_voxels_shape(
103            dispatcher,
104            &pos12.inverse(),
105            voxels2,
106            shape1,
107            prediction,
108            manifolds,
109            workspace,
110            true,
111        );
112    }
113}
114
115/// Computes the contact manifold between a convex shape and a voxels shape.
116pub fn contact_manifolds_voxels_shape<ManifoldData, ContactData>(
117    dispatcher: &dyn PersistentQueryDispatcher<ManifoldData, ContactData>,
118    pos12: &Pose,
119    voxels1: &Voxels,
120    shape2: &dyn Shape,
121    prediction: Real,
122    manifolds: &mut Vec<ContactManifold<ManifoldData, ContactData>>,
123    workspace: &mut Option<ContactManifoldsWorkspace>,
124    flipped: bool,
125) where
126    ManifoldData: Default + Clone,
127    ContactData: Default + Copy,
128{
129    VoxelsShapeContactManifoldsWorkspace::<2>::ensure_exists(workspace);
130    let workspace: &mut VoxelsShapeContactManifoldsWorkspace<2> =
131        workspace.as_mut().unwrap().0.downcast_mut().unwrap();
132    let new_timestamp = !workspace.timestamp;
133    workspace.timestamp = new_timestamp;
134
135    // TODO: avoid reallocating the new `manifolds` vec at each step.
136    let mut old_manifolds = core::mem::take(manifolds);
137
138    let radius1 = voxels1.voxel_size() / 2.0;
139
140    let aabb1 = voxels1.local_aabb();
141    let aabb2_1 = shape2.compute_aabb(pos12);
142    let domain2_1 = Aabb {
143        mins: aabb2_1.mins - radius1 * 10.0,
144        maxs: aabb2_1.maxs + radius1 * 10.0,
145    };
146
147    if let Some(intersection_aabb1) = aabb1.intersection(&aabb2_1) {
148        for vox1 in voxels1.voxels_intersecting_local_aabb(&intersection_aabb1) {
149            let vox_type1 = vox1.state.voxel_type();
150
151            // TODO: would be nice to have a strategy to handle interior voxels for depenetration.
152            if vox_type1 == VoxelType::Empty || vox_type1 == VoxelType::Interior {
153                continue;
154            }
155
156            let canon1 = CanonicalVoxelShape::from_voxel(voxels1, &vox1);
157
158            // TODO: could we refactor the workspace system between Voxels, HeightField, and CompoundShape?
159            //       (and maybe TriMesh too but it’s using a different approach).
160            let (sub_detector, manifold_updated) =
161                match workspace.sub_detectors.entry(canon1.workspace_key.into()) {
162                    Entry::Occupied(entry) => {
163                        let sub_detector = entry.into_mut();
164
165                        if sub_detector.timestamp != new_timestamp {
166                            let manifold = old_manifolds[sub_detector.manifold_id].take();
167                            *sub_detector = VoxelsShapeSubDetector {
168                                manifold_id: manifolds.len(),
169                                timestamp: new_timestamp,
170                                selected_contacts: 0,
171                            };
172
173                            manifolds.push(manifold);
174                            (sub_detector, false)
175                        } else {
176                            (sub_detector, true)
177                        }
178                    }
179                    Entry::Vacant(entry) => {
180                        let sub_detector = VoxelsShapeSubDetector {
181                            manifold_id: manifolds.len(),
182                            selected_contacts: 0,
183                            timestamp: new_timestamp,
184                        };
185
186                        let vid = vox1.linear_id.flat_id() as u32;
187                        let (id1, id2) = if flipped { (0, vid) } else { (vid, 0) };
188                        manifolds.push(ContactManifold::with_data(
189                            id1,
190                            id2,
191                            ManifoldData::default(),
192                        ));
193
194                        (entry.insert(sub_detector), false)
195                    }
196                };
197
198            /*
199             * Update the contact manifold if needed.
200             */
201            let manifold = &mut manifolds[sub_detector.manifold_id];
202
203            if !manifold_updated {
204                let (canonical_center1, canonical_pseudo_cube1) =
205                    canon1.cuboid(voxels1, &vox1, domain2_1);
206
207                let canonical_shape1 = &canonical_pseudo_cube1 as &dyn Shape;
208                let canonical_pos12 = Pose::from_translation(-canonical_center1) * pos12;
209
210                // If we already computed contacts in the previous simulation step, their
211                // local points are relative to the previously calculated canonical shape
212                // which might not have the same local center as the one computed in this
213                // step (because it’s based on the position of shape2 relative to voxels1).
214                // So we need to adjust the local points to account for the position difference
215                // and keep the point at the same "canonica-shape-space" location as in the previous frame.
216                let prev_center = if flipped {
217                    manifold
218                        .subshape_pos2
219                        .as_ref()
220                        .map(|p| p.translation)
221                        .unwrap_or_default()
222                } else {
223                    manifold
224                        .subshape_pos1
225                        .as_ref()
226                        .map(|p| p.translation)
227                        .unwrap_or_default()
228                };
229                let delta_center = canonical_center1 - prev_center;
230
231                if flipped {
232                    for pt in &mut manifold.points {
233                        pt.local_p2 -= delta_center;
234                    }
235                } else {
236                    for pt in &mut manifold.points {
237                        pt.local_p1 -= delta_center;
238                    }
239                }
240
241                // Update contacts.
242                if flipped {
243                    manifold.subshape_pos2 = Some(Pose::from_translation(canonical_center1));
244                    let _ = dispatcher.contact_manifold_convex_convex(
245                        &canonical_pos12.inverse(),
246                        shape2,
247                        canonical_shape1,
248                        None,
249                        None,
250                        prediction,
251                        manifold,
252                    );
253                } else {
254                    manifold.subshape_pos1 = Some(Pose::from_translation(canonical_center1));
255                    let _ = dispatcher.contact_manifold_convex_convex(
256                        &canonical_pos12,
257                        canonical_shape1,
258                        shape2,
259                        None,
260                        None,
261                        prediction,
262                        manifold,
263                    );
264                }
265            }
266
267            /*
268             * Filter-out points that don’t belong to this block.
269             */
270            let test_voxel = Cuboid::new(radius1 + Vector::splat(1.0e-2));
271            let penetration_dir1 = if flipped {
272                manifold.local_n2
273            } else {
274                manifold.local_n1
275            };
276
277            for (i, pt) in manifold.points.iter().enumerate() {
278                if pt.dist < 0.0 {
279                    // If this is a penetration, double-check that we are not hitting the
280                    // interior of the infinitely expanded canonical shape by checking if
281                    // the opposite normal had led to a better vector.
282                    let cuboid1 = Cuboid::new(radius1);
283                    let sp1 = cuboid1.local_support_point(-penetration_dir1) + vox1.center;
284                    let sm2 = shape2
285                        .as_support_map()
286                        .expect("Unsupported collision pair.");
287                    let sp2 = sm2.support_point(pos12, penetration_dir1);
288                    let test_dist = (sp2 - sp1).dot(-penetration_dir1);
289                    let keep = test_dist < pt.dist;
290
291                    if !keep {
292                        // We don’t want to keep this point as it is an incorrect penetration
293                        // caused by the canonical shape expansion.
294                        continue;
295                    }
296                }
297
298                let pt_in_voxel_space = if flipped {
299                    manifold.subshape_pos2.transform_point(pt.local_p2) - vox1.center
300                } else {
301                    manifold.subshape_pos1.transform_point(pt.local_p1) - vox1.center
302                };
303                sub_detector.selected_contacts |=
304                    (test_voxel.contains_local_point(pt_in_voxel_space) as u32) << i;
305            }
306        }
307    }
308
309    // Remove contacts marked as ignored.
310    for sub_detector in workspace.sub_detectors.values() {
311        if sub_detector.timestamp == new_timestamp {
312            let manifold = &mut manifolds[sub_detector.manifold_id];
313            let mut k = 0;
314            manifold.points.retain(|_| {
315                let keep = (sub_detector.selected_contacts & (1 << k)) != 0;
316                k += 1;
317                keep
318            });
319        }
320    }
321
322    // Remove detectors which no longer index a valid manifold.
323    workspace
324        .sub_detectors
325        .retain(|_, detector| detector.timestamp == new_timestamp);
326}
327
328#[derive(Copy, Clone, Debug)]
329pub(crate) struct CanonicalVoxelShape {
330    pub range: [IVector; 2],
331    pub workspace_key: [u32; 2],
332}
333
334impl CanonicalVoxelShape {
335    pub fn from_voxel(voxels: &Voxels, vox: &VoxelData) -> Self {
336        let mut key_low = vox.grid_coords;
337        let mut key_high = key_low;
338
339        // NOTE: the mins/maxs here are offset by 1 so we can expand past the last voxel if it
340        //       happens to also be infinite along the same axis (due to cross-voxels internal edges
341        //       detection).
342        let mins = voxels.domain()[0] - IVector::splat(1);
343        let maxs = voxels.domain()[1];
344        let counts = maxs - mins;
345        let mask1 = vox.state.free_faces();
346
347        let adjust_canon = |axis: AxisMask, i: usize, key: &mut IVector, val: Int| {
348            if !mask1.contains(axis) {
349                key[i] = val.as_();
350            }
351        };
352
353        adjust_canon(AxisMask::X_POS, 0, &mut key_high, maxs[0]);
354        adjust_canon(AxisMask::X_NEG, 0, &mut key_low, mins[0]);
355        adjust_canon(AxisMask::Y_POS, 1, &mut key_high, maxs[1]);
356        adjust_canon(AxisMask::Y_NEG, 1, &mut key_low, mins[1]);
357
358        #[cfg(feature = "dim3")]
359        {
360            adjust_canon(AxisMask::Z_POS, 2, &mut key_high, maxs[2]);
361            adjust_canon(AxisMask::Z_NEG, 2, &mut key_low, mins[2]);
362        }
363
364        #[cfg(feature = "dim2")]
365        let workspace_id = |vox: IVector| {
366            let local = vox - mins;
367            (local.x + local.y * counts.x) as u32
368        };
369
370        #[cfg(feature = "dim3")]
371        let workspace_id = |vox: IVector| {
372            let local = vox - mins;
373            (local.x + local.y * counts.x + local.z * counts.x * counts.y) as u32
374        };
375
376        Self {
377            range: [key_low, key_high],
378            workspace_key: [workspace_id(key_low), workspace_id(key_high)],
379        }
380    }
381
382    pub fn cuboid(&self, voxels: &Voxels, vox: &VoxelData, domain2_1: Aabb) -> (Vector, Cuboid) {
383        let radius = voxels.voxel_size() / 2.0;
384        let mut canonical_mins = voxels.voxel_center(self.range[0]);
385        let mut canonical_maxs = voxels.voxel_center(self.range[1]);
386
387        for k in 0..DIM {
388            if self.range[0][k] != vox.grid_coords[k] {
389                canonical_mins[k] = canonical_mins[k].max(domain2_1.mins[k]);
390            }
391
392            if self.range[1][k] != vox.grid_coords[k] {
393                canonical_maxs[k] = canonical_maxs[k].min(domain2_1.maxs[k]);
394            }
395        }
396
397        let canonical_half_extents = (canonical_maxs - canonical_mins) / 2.0 + radius;
398        let canonical_center = canonical_mins.midpoint(canonical_maxs);
399        let canonical_cube = Cuboid::new(canonical_half_extents);
400
401        (canonical_center, canonical_cube)
402    }
403}