Skip to main content

parry2d/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    let deformable = shape2.is_deformable();
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 = Pose::from_translation(-canonical_center1) * pos12;
107
108            let mut detect_hit = |leaf2: u32| {
109                let key = [canon1.workspace_key[0], canon1.workspace_key[1], 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) =
114                    match workspace.sub_detectors.entry(key.into()) {
115                        Entry::Occupied(entry) => {
116                            let sub_detector = entry.into_mut();
117
118                            if sub_detector.timestamp != new_timestamp {
119                                let manifold = old_manifolds[sub_detector.manifold_id].take();
120                                *sub_detector = VoxelsShapeSubDetector {
121                                    manifold_id: manifolds.len(),
122                                    timestamp: new_timestamp,
123                                    selected_contacts: 0,
124                                };
125
126                                manifolds.push(manifold);
127                                (sub_detector, false)
128                            } else {
129                                (sub_detector, true)
130                            }
131                        }
132                        Entry::Vacant(entry) => {
133                            let sub_detector = VoxelsShapeSubDetector {
134                                manifold_id: manifolds.len(),
135                                selected_contacts: 0,
136                                timestamp: new_timestamp,
137                            };
138
139                            let vox_id = vox1.linear_id.flat_id() as u32;
140                            let (id1, id2) = if flipped {
141                                (leaf2, vox_id)
142                            } else {
143                                (vox_id, leaf2)
144                            };
145                            manifolds.push(ContactManifold::with_data(
146                                id1,
147                                id2,
148                                ManifoldData::default(),
149                            ));
150
151                            (entry.insert(sub_detector), false)
152                        }
153                    };
154
155                /*
156                 * Update the contact manifold if needed.
157                 */
158                let manifold = &mut manifolds[sub_detector.manifold_id];
159
160                shape2.map_part_at(leaf2, &mut |part_pos2, part_shape2, _| {
161                    let relative_pos12 = part_pos2.prepend_to(&canonical_pose12);
162
163                    if !manifold_updated {
164                        if deformable {
165                            manifold.mark_shapes_deformed();
166                        }
167
168                        // If we already computed contacts in the previous simulation step, their
169                        // local points are relative to the previously calculated canonical shape
170                        // which might not have the same local center as the one computed in this
171                        // step (because it’s based on the position of shape2 relative to voxels1).
172                        // So we need to adjust the local points to account for the position difference
173                        // and keep the point at the same "canonical-shape-space" location as in the previous frame.
174                        let prev_center = if flipped {
175                            manifold
176                                .subshape_pos2()
177                                .as_ref()
178                                .map(|p| p.translation)
179                                .unwrap_or_default()
180                        } else {
181                            manifold
182                                .subshape_pos1()
183                                .as_ref()
184                                .map(|p| p.translation)
185                                .unwrap_or_default()
186                        };
187                        let delta_center = canonical_center1 - prev_center;
188
189                        if flipped {
190                            for pt in &mut manifold.points {
191                                pt.local_p2 -= delta_center;
192                            }
193                        } else {
194                            for pt in &mut manifold.points {
195                                pt.local_p1 -= delta_center;
196                            }
197                        }
198
199                        // Update contacts.
200                        if flipped {
201                            manifold.set_subshape_pos1(part_pos2.copied());
202                            manifold
203                                .set_subshape_pos2(Some(Pose::from_translation(canonical_center1)));
204                            let _ = dispatcher.contact_manifold_convex_convex(
205                                &relative_pos12.inverse(),
206                                part_shape2,
207                                &canonical_shape1,
208                                None,
209                                None,
210                                prediction,
211                                manifold,
212                            );
213                        } else {
214                            manifold
215                                .set_subshape_pos1(Some(Pose::from_translation(canonical_center1)));
216                            manifold.set_subshape_pos2(part_pos2.copied());
217                            let _ = dispatcher.contact_manifold_convex_convex(
218                                &relative_pos12,
219                                &canonical_shape1,
220                                part_shape2,
221                                None,
222                                None,
223                                prediction,
224                                manifold,
225                            );
226                        }
227                    }
228
229                    /*
230                     * Filter-out points that don’t belong to this block.
231                     */
232                    let test_voxel = Cuboid::new(radius1 + Vector::splat(1.0e-2));
233                    let penetration_dir1 = if flipped {
234                        manifold.local_n2
235                    } else {
236                        manifold.local_n1
237                    };
238
239                    for (i, pt) in manifold.points.iter().enumerate() {
240                        if pt.dist < 0.0 {
241                            // If this is a penetration, double-check that we are not hitting the
242                            // interior of the infinitely expanded canonical shape by checking if
243                            // the opposite normal had led to a better vector.
244                            let cuboid1 = Cuboid::new(radius1);
245                            let sp1 = cuboid1.local_support_point(-penetration_dir1) + vox1.center;
246                            let sm2 = part_shape2
247                                .as_support_map()
248                                .expect("Unsupported collision pair.");
249                            let sp2 = sm2.support_point(&relative_pos12, penetration_dir1)
250                                + canonical_center1;
251                            let test_dist = (sp2 - sp1).dot(-penetration_dir1);
252                            let keep = test_dist < pt.dist;
253
254                            if !keep {
255                                // We don’t want to keep this point as it is an incorrect penetration
256                                // caused by the canonical shape expansion.
257                                continue;
258                            }
259                        }
260
261                        let pt_in_voxel_space = if flipped {
262                            manifold.subshape_pos2().transform_point(pt.local_p2) - vox1.center
263                        } else {
264                            manifold.subshape_pos1().transform_point(pt.local_p1) - vox1.center
265                        };
266                        sub_detector.selected_contacts |=
267                            (test_voxel.contains_local_point(pt_in_voxel_space) as u32) << i;
268                    }
269                });
270            };
271
272            let canon_aabb1_2 = canonical_shape1.compute_aabb(&canonical_pose12.inverse());
273
274            for leaf_id in bvh2.intersect_aabb(&canon_aabb1_2) {
275                detect_hit(leaf_id);
276            }
277        }
278    }
279
280    // Remove contacts marked as ignored.
281    for sub_detector in workspace.sub_detectors.values() {
282        if sub_detector.timestamp == new_timestamp {
283            let manifold = &mut manifolds[sub_detector.manifold_id];
284            let mut k = 0;
285            manifold.points.retain(|_| {
286                let keep = (sub_detector.selected_contacts & (1 << k)) != 0;
287                k += 1;
288                keep
289            });
290        }
291    }
292
293    // Remove detectors which no longer index a valid manifold.
294    workspace
295        .sub_detectors
296        .retain(|_, detector| detector.timestamp == new_timestamp);
297}