parry3d/query/contact_manifolds/
contact_manifolds_voxels_composite_shape.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{Pose, Real, Vector};
3use crate::query::details::{
4    CanonicalVoxelShape, VoxelsShapeContactManifoldsWorkspace, VoxelsShapeSubDetector,
5};
6use crate::query::{
7    ContactManifold, ContactManifoldsWorkspace, PersistentQueryDispatcher, PointQuery,
8    TypedWorkspaceData, WorkspaceData,
9};
10use crate::shape::{CompositeShape, Cuboid, Shape, SupportMap, VoxelType, Voxels};
11use crate::utils::hashmap::Entry;
12use crate::utils::PoseOpt;
13use alloc::{boxed::Box, vec::Vec};
14
15impl WorkspaceData for VoxelsShapeContactManifoldsWorkspace<3> {
16    fn as_typed_workspace_data(&self) -> TypedWorkspaceData<'_> {
17        TypedWorkspaceData::VoxelsCompositeShapeContactManifoldsWorkspace(self)
18    }
19
20    fn clone_dyn(&self) -> Box<dyn WorkspaceData> {
21        Box::new(self.clone())
22    }
23}
24
25/// Computes the contact manifold between voxels and a composite shape, both represented as a `Shape` trait-object.
26pub fn contact_manifolds_voxels_composite_shape_shapes<ManifoldData, ContactData>(
27    dispatcher: &dyn PersistentQueryDispatcher<ManifoldData, ContactData>,
28    pos12: &Pose,
29    shape1: &dyn Shape,
30    shape2: &dyn Shape,
31    prediction: Real,
32    manifolds: &mut Vec<ContactManifold<ManifoldData, ContactData>>,
33    workspace: &mut Option<ContactManifoldsWorkspace>,
34) where
35    ManifoldData: Default + Clone,
36    ContactData: Default + Copy,
37{
38    if let (Some(voxels1), Some(shape2)) = (shape1.as_voxels(), shape2.as_composite_shape()) {
39        contact_manifolds_voxels_composite_shape(
40            dispatcher, pos12, voxels1, shape2, prediction, manifolds, workspace, false,
41        );
42    } else if let (Some(shape1), Some(voxels2)) = (shape1.as_composite_shape(), shape2.as_voxels())
43    {
44        contact_manifolds_voxels_composite_shape(
45            dispatcher,
46            &pos12.inverse(),
47            voxels2,
48            shape1,
49            prediction,
50            manifolds,
51            workspace,
52            true,
53        );
54    }
55}
56
57/// Computes the contact manifold between voxels and a composite shape.
58pub fn contact_manifolds_voxels_composite_shape<ManifoldData, ContactData>(
59    dispatcher: &dyn PersistentQueryDispatcher<ManifoldData, ContactData>,
60    pos12: &Pose,
61    voxels1: &Voxels,
62    shape2: &dyn CompositeShape,
63    prediction: Real,
64    manifolds: &mut Vec<ContactManifold<ManifoldData, ContactData>>,
65    workspace: &mut Option<ContactManifoldsWorkspace>,
66    flipped: bool,
67) where
68    ManifoldData: Default + Clone,
69    ContactData: Default + Copy,
70{
71    VoxelsShapeContactManifoldsWorkspace::<3>::ensure_exists(workspace);
72    let workspace: &mut VoxelsShapeContactManifoldsWorkspace<3> =
73        workspace.as_mut().unwrap().0.downcast_mut().unwrap();
74    let new_timestamp = !workspace.timestamp;
75    workspace.timestamp = new_timestamp;
76
77    // TODO: avoid reallocating the new `manifolds` vec at each step.
78    let mut old_manifolds = core::mem::take(manifolds);
79    let bvh2 = shape2.bvh();
80
81    let radius1 = voxels1.voxel_size() / 2.0;
82
83    let aabb1 = voxels1.local_aabb();
84    let aabb2_1 = shape2.bvh().root_aabb().transform_by(pos12);
85    let domain2_1 = Aabb {
86        mins: aabb2_1.mins - radius1 * 10.0,
87        maxs: aabb2_1.maxs + radius1 * 10.0,
88    };
89
90    if let Some(intersection_aabb1) = aabb1.intersection(&aabb2_1) {
91        for vox1 in voxels1.voxels_intersecting_local_aabb(&intersection_aabb1) {
92            let vox_type1 = vox1.state.voxel_type();
93
94            // TODO: would be nice to have a strategy to handle interior voxels for depenetration.
95            if vox_type1 == VoxelType::Empty || vox_type1 == VoxelType::Interior {
96                continue;
97            }
98
99            // PERF: could we avoid repeated BVH traversals involving the same canonical shape?
100            //       The issue is that we need to figure out what contact manifolds are associated
101            //       to that canonical shape so we can update the included contact bitmask (and
102            //       one way of figuring this out is to re-traverse the bvh).
103            let canon1 = CanonicalVoxelShape::from_voxel(voxels1, &vox1);
104            let (canonical_center1, canonical_shape1) = canon1.cuboid(voxels1, &vox1, domain2_1);
105            let canonical_pose12 = Pose::from_translation(-canonical_center1) * pos12;
106
107            let mut detect_hit = |leaf2: u32| {
108                let key = [canon1.workspace_key[0], canon1.workspace_key[1], leaf2];
109
110                // TODO: could we refactor the workspace system between Voxels, HeightField, and CompoundShape?
111                //       (and maybe TriMesh too but it's using a different approach).
112                let (sub_detector, manifold_updated) =
113                    match workspace.sub_detectors.entry(key.into()) {
114                        Entry::Occupied(entry) => {
115                            let sub_detector = entry.into_mut();
116
117                            if sub_detector.timestamp != new_timestamp {
118                                let manifold = old_manifolds[sub_detector.manifold_id].take();
119                                *sub_detector = VoxelsShapeSubDetector {
120                                    manifold_id: manifolds.len(),
121                                    timestamp: new_timestamp,
122                                    selected_contacts: 0,
123                                };
124
125                                manifolds.push(manifold);
126                                (sub_detector, false)
127                            } else {
128                                (sub_detector, true)
129                            }
130                        }
131                        Entry::Vacant(entry) => {
132                            let sub_detector = VoxelsShapeSubDetector {
133                                manifold_id: manifolds.len(),
134                                selected_contacts: 0,
135                                timestamp: new_timestamp,
136                            };
137
138                            let vox_id = vox1.linear_id.flat_id() as u32;
139                            let (id1, id2) = if flipped {
140                                (leaf2, vox_id)
141                            } else {
142                                (vox_id, leaf2)
143                            };
144                            manifolds.push(ContactManifold::with_data(
145                                id1,
146                                id2,
147                                ManifoldData::default(),
148                            ));
149
150                            (entry.insert(sub_detector), false)
151                        }
152                    };
153
154                /*
155                 * Update the contact manifold if needed.
156                 */
157                let manifold = &mut manifolds[sub_detector.manifold_id];
158
159                shape2.map_part_at(leaf2, &mut |part_pos2, part_shape2, _| {
160                    let relative_pos12 = part_pos2.prepend_to(&canonical_pose12);
161
162                    if !manifold_updated {
163                        // If we already computed contacts in the previous simulation step, their
164                        // local points are relative to the previously calculated canonical shape
165                        // which might not have the same local center as the one computed in this
166                        // step (because it’s based on the position of shape2 relative to voxels1).
167                        // So we need to adjust the local points to account for the position difference
168                        // and keep the point at the same "canonical-shape-space" location as in the previous frame.
169                        let prev_center = if flipped {
170                            manifold
171                                .subshape_pos2
172                                .as_ref()
173                                .map(|p| p.translation)
174                                .unwrap_or_default()
175                        } else {
176                            manifold
177                                .subshape_pos1
178                                .as_ref()
179                                .map(|p| p.translation)
180                                .unwrap_or_default()
181                        };
182                        let delta_center = canonical_center1 - prev_center;
183
184                        if flipped {
185                            for pt in &mut manifold.points {
186                                pt.local_p2 -= delta_center;
187                            }
188                        } else {
189                            for pt in &mut manifold.points {
190                                pt.local_p1 -= delta_center;
191                            }
192                        }
193
194                        // Update contacts.
195                        if flipped {
196                            manifold.subshape_pos1 = part_pos2.copied();
197                            manifold.subshape_pos2 =
198                                Some(Pose::from_translation(canonical_center1));
199                            let _ = dispatcher.contact_manifold_convex_convex(
200                                &relative_pos12.inverse(),
201                                part_shape2,
202                                &canonical_shape1,
203                                None,
204                                None,
205                                prediction,
206                                manifold,
207                            );
208                        } else {
209                            manifold.subshape_pos1 =
210                                Some(Pose::from_translation(canonical_center1));
211                            manifold.subshape_pos2 = part_pos2.copied();
212                            let _ = dispatcher.contact_manifold_convex_convex(
213                                &relative_pos12,
214                                &canonical_shape1,
215                                part_shape2,
216                                None,
217                                None,
218                                prediction,
219                                manifold,
220                            );
221                        }
222                    }
223
224                    /*
225                     * Filter-out points that don’t belong to this block.
226                     */
227                    let test_voxel = Cuboid::new(radius1 + Vector::splat(1.0e-2));
228                    let penetration_dir1 = if flipped {
229                        manifold.local_n2
230                    } else {
231                        manifold.local_n1
232                    };
233
234                    for (i, pt) in manifold.points.iter().enumerate() {
235                        if pt.dist < 0.0 {
236                            // If this is a penetration, double-check that we are not hitting the
237                            // interior of the infinitely expanded canonical shape by checking if
238                            // the opposite normal had led to a better vector.
239                            let cuboid1 = Cuboid::new(radius1);
240                            let sp1 = cuboid1.local_support_point(-penetration_dir1) + vox1.center;
241                            let sm2 = part_shape2
242                                .as_support_map()
243                                .expect("Unsupported collision pair.");
244                            let sp2 = sm2.support_point(&relative_pos12, penetration_dir1)
245                                + canonical_center1;
246                            let test_dist = (sp2 - sp1).dot(-penetration_dir1);
247                            let keep = test_dist < pt.dist;
248
249                            if !keep {
250                                // We don’t want to keep this point as it is an incorrect penetration
251                                // caused by the canonical shape expansion.
252                                continue;
253                            }
254                        }
255
256                        let pt_in_voxel_space = if flipped {
257                            manifold.subshape_pos2.transform_point(pt.local_p2) - vox1.center
258                        } else {
259                            manifold.subshape_pos1.transform_point(pt.local_p1) - vox1.center
260                        };
261                        sub_detector.selected_contacts |=
262                            (test_voxel.contains_local_point(pt_in_voxel_space) as u32) << i;
263                    }
264                });
265            };
266
267            let canon_aabb1_2 = canonical_shape1.compute_aabb(&canonical_pose12.inverse());
268
269            for leaf_id in bvh2.intersect_aabb(&canon_aabb1_2) {
270                detect_hit(leaf_id);
271            }
272        }
273    }
274
275    // Remove contacts marked as ignored.
276    for sub_detector in workspace.sub_detectors.values() {
277        if sub_detector.timestamp == new_timestamp {
278            let manifold = &mut manifolds[sub_detector.manifold_id];
279            let mut k = 0;
280            manifold.points.retain(|_| {
281                let keep = (sub_detector.selected_contacts & (1 << k)) != 0;
282                k += 1;
283                keep
284            });
285        }
286    }
287
288    // Remove detectors which no longer index a valid manifold.
289    workspace
290        .sub_detectors
291        .retain(|_, detector| detector.timestamp == new_timestamp);
292}