parry3d/query/contact_manifolds/
contact_manifolds_voxels_composite_shape.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{Isometry, Real, Translation, 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::IsometryOpt;
13use alloc::{boxed::Box, vec::Vec};
14use na::Vector3;
15
16impl WorkspaceData for VoxelsShapeContactManifoldsWorkspace<3> {
17    fn as_typed_workspace_data(&self) -> TypedWorkspaceData<'_> {
18        TypedWorkspaceData::VoxelsCompositeShapeContactManifoldsWorkspace(self)
19    }
20
21    fn clone_dyn(&self) -> Box<dyn WorkspaceData> {
22        Box::new(self.clone())
23    }
24}
25
26/// Computes the contact manifold between voxels and a composite shape, both represented as a `Shape` trait-object.
27pub fn contact_manifolds_voxels_composite_shape_shapes<ManifoldData, ContactData>(
28    dispatcher: &dyn PersistentQueryDispatcher<ManifoldData, ContactData>,
29    pos12: &Isometry<Real>,
30    shape1: &dyn Shape,
31    shape2: &dyn Shape,
32    prediction: Real,
33    manifolds: &mut Vec<ContactManifold<ManifoldData, ContactData>>,
34    workspace: &mut Option<ContactManifoldsWorkspace>,
35) where
36    ManifoldData: Default + Clone,
37    ContactData: Default + Copy,
38{
39    if let (Some(voxels1), Some(shape2)) = (shape1.as_voxels(), shape2.as_composite_shape()) {
40        contact_manifolds_voxels_composite_shape(
41            dispatcher, pos12, voxels1, shape2, prediction, manifolds, workspace, false,
42        );
43    } else if let (Some(shape1), Some(voxels2)) = (shape1.as_composite_shape(), shape2.as_voxels())
44    {
45        contact_manifolds_voxels_composite_shape(
46            dispatcher,
47            &pos12.inverse(),
48            voxels2,
49            shape1,
50            prediction,
51            manifolds,
52            workspace,
53            true,
54        );
55    }
56}
57
58/// Computes the contact manifold between voxels and a composite shape.
59pub fn contact_manifolds_voxels_composite_shape<ManifoldData, ContactData>(
60    dispatcher: &dyn PersistentQueryDispatcher<ManifoldData, ContactData>,
61    pos12: &Isometry<Real>,
62    voxels1: &Voxels,
63    shape2: &dyn CompositeShape,
64    prediction: Real,
65    manifolds: &mut Vec<ContactManifold<ManifoldData, ContactData>>,
66    workspace: &mut Option<ContactManifoldsWorkspace>,
67    flipped: bool,
68) where
69    ManifoldData: Default + Clone,
70    ContactData: Default + Copy,
71{
72    VoxelsShapeContactManifoldsWorkspace::<3>::ensure_exists(workspace);
73    let workspace: &mut VoxelsShapeContactManifoldsWorkspace<3> =
74        workspace.as_mut().unwrap().0.downcast_mut().unwrap();
75    let new_timestamp = !workspace.timestamp;
76    workspace.timestamp = new_timestamp;
77
78    // TODO: avoid reallocating the new `manifolds` vec at each step.
79    let mut old_manifolds = core::mem::take(manifolds);
80    let bvh2 = shape2.bvh();
81
82    let radius1 = voxels1.voxel_size() / 2.0;
83
84    let aabb1 = voxels1.local_aabb();
85    let aabb2_1 = shape2.bvh().root_aabb().transform_by(pos12);
86    let domain2_1 = Aabb {
87        mins: aabb2_1.mins - radius1 * 10.0,
88        maxs: aabb2_1.maxs + radius1 * 10.0,
89    };
90
91    if let Some(intersection_aabb1) = aabb1.intersection(&aabb2_1) {
92        for vox1 in voxels1.voxels_intersecting_local_aabb(&intersection_aabb1) {
93            let vox_type1 = vox1.state.voxel_type();
94
95            // TODO: would be nice to have a strategy to handle interior voxels for depenetration.
96            if vox_type1 == VoxelType::Empty || vox_type1 == VoxelType::Interior {
97                continue;
98            }
99
100            // PERF: could we avoid repeated BVH traversals involving the same canonical shape?
101            //       The issue is that we need to figure out what contact manifolds are associated
102            //       to that canonical shape so we can update the included contact bitmask (and
103            //       one way of figuring this out is to re-traverse the bvh).
104            let canon1 = CanonicalVoxelShape::from_voxel(voxels1, &vox1);
105            let (canonical_center1, canonical_shape1) = canon1.cuboid(voxels1, &vox1, domain2_1);
106            let canonical_pose12 = Translation::from(-canonical_center1) * pos12;
107
108            let mut detect_hit = |leaf2: u32| {
109                let key = Vector3::new(canon1.workspace_key.x, canon1.workspace_key.y, leaf2);
110
111                // TODO: could we refactor the workspace system between Voxels, HeightField, and CompoundShape?
112                //       (and maybe TriMesh too but it’s using a different approach).
113                let (sub_detector, manifold_updated) = match workspace.sub_detectors.entry(key) {
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;
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.vector)
174                                .unwrap_or_default()
175                        } else {
176                            manifold
177                                .subshape_pos1
178                                .as_ref()
179                                .map(|p| p.translation.vector)
180                                .unwrap_or_default()
181                        };
182                        let delta_center = canonical_center1.coords - 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 = Some(Isometry::from(canonical_center1));
198                            let _ = dispatcher.contact_manifold_convex_convex(
199                                &relative_pos12.inverse(),
200                                part_shape2,
201                                &canonical_shape1,
202                                None,
203                                None,
204                                prediction,
205                                manifold,
206                            );
207                        } else {
208                            manifold.subshape_pos1 = Some(Isometry::from(canonical_center1));
209                            manifold.subshape_pos2 = part_pos2.copied();
210                            let _ = dispatcher.contact_manifold_convex_convex(
211                                &relative_pos12,
212                                &canonical_shape1,
213                                part_shape2,
214                                None,
215                                None,
216                                prediction,
217                                manifold,
218                            );
219                        }
220                    }
221
222                    /*
223                     * Filter-out points that don’t belong to this block.
224                     */
225                    let test_voxel = Cuboid::new(radius1 + Vector::repeat(1.0e-2));
226                    let penetration_dir1 = if flipped {
227                        manifold.local_n2
228                    } else {
229                        manifold.local_n1
230                    };
231
232                    for (i, pt) in manifold.points.iter().enumerate() {
233                        if pt.dist < 0.0 {
234                            // If this is a penetration, double-check that we are not hitting the
235                            // interior of the infinitely expanded canonical shape by checking if
236                            // the opposite normal had led to a better vector.
237                            let cuboid1 = Cuboid::new(radius1);
238                            let sp1 = cuboid1.local_support_point(&-penetration_dir1)
239                                + vox1.center.coords;
240                            let sm2 = part_shape2
241                                .as_support_map()
242                                .expect("Unsupported collision pair.");
243                            let sp2 = sm2.support_point(&relative_pos12, &penetration_dir1)
244                                + canonical_center1.coords;
245                            let test_dist = (sp2 - sp1).dot(&-penetration_dir1);
246                            let keep = test_dist < pt.dist;
247
248                            if !keep {
249                                // We don’t want to keep this point as it is an incorrect penetration
250                                // caused by the canonical shape expansion.
251                                continue;
252                            }
253                        }
254
255                        let pt_in_voxel_space = if flipped {
256                            manifold.subshape_pos2.transform_point(&pt.local_p2)
257                                - vox1.center.coords
258                        } else {
259                            manifold.subshape_pos1.transform_point(&pt.local_p1)
260                                - vox1.center.coords
261                        };
262                        sub_detector.selected_contacts |=
263                            (test_voxel.contains_local_point(&pt_in_voxel_space) as u32) << i;
264                    }
265                });
266            };
267
268            let canon_aabb1_2 = canonical_shape1.compute_aabb(&canonical_pose12.inverse());
269
270            for leaf_id in bvh2.intersect_aabb(&canon_aabb1_2) {
271                detect_hit(leaf_id);
272            }
273        }
274    }
275
276    // Remove contacts marked as ignored.
277    for sub_detector in workspace.sub_detectors.values() {
278        if sub_detector.timestamp == new_timestamp {
279            let manifold = &mut manifolds[sub_detector.manifold_id];
280            let mut k = 0;
281            manifold.points.retain(|_| {
282                let keep = (sub_detector.selected_contacts & (1 << k)) != 0;
283                k += 1;
284                keep
285            });
286        }
287    }
288
289    // Remove detectors which no longer index a valid manifold.
290    workspace
291        .sub_detectors
292        .retain(|_, detector| detector.timestamp == new_timestamp);
293}