parry2d/query/contact_manifolds/
contact_manifolds_capsule_capsule.rs

1use crate::math::{Pose, Real, Vector};
2use crate::query::{ContactManifold, TrackedContact};
3#[cfg(feature = "dim2")]
4use crate::shape::SegmentPointLocation;
5use crate::shape::{Capsule, PackedFeatureId, Shape};
6#[cfg(feature = "dim2")]
7use approx::AbsDiffEq;
8
9/// Computes the contact manifold between two capsules given as `Shape` trait-objects.
10pub fn contact_manifold_capsule_capsule_shapes<ManifoldData, ContactData>(
11    pos12: &Pose,
12    shape1: &dyn Shape,
13    shape2: &dyn Shape,
14    prediction: Real,
15    manifold: &mut ContactManifold<ManifoldData, ContactData>,
16) where
17    ContactData: Default + Copy,
18{
19    if let (Some(capsule1), Some(capsule2)) = (shape1.as_capsule(), shape2.as_capsule()) {
20        contact_manifold_capsule_capsule(pos12, capsule1, capsule2, prediction, manifold);
21    }
22}
23
24/// Computes the contact manifold between two capsules.
25#[cfg(feature = "dim2")]
26pub fn contact_manifold_capsule_capsule<'a, ManifoldData, ContactData>(
27    pos12: &Pose,
28    capsule1: &'a Capsule,
29    capsule2: &'a Capsule,
30    prediction: Real,
31    manifold: &mut ContactManifold<ManifoldData, ContactData>,
32) where
33    ContactData: Default + Copy,
34{
35    let seg1 = capsule1.segment;
36    let seg2_1 = capsule2.segment.transformed(pos12);
37    let (loc1, loc2) = crate::query::details::closest_points_segment_segment_with_locations_nD(
38        (&seg1.a, &seg1.b),
39        (&seg2_1.a, &seg2_1.b),
40    );
41
42    // We do this clone to perform contact tracking and transfer impulses.
43    // TODO: find a more efficient way of doing this.
44    let old_manifold_points = manifold.points.clone();
45    manifold.clear();
46
47    let fid1 = if let SegmentPointLocation::OnVertex(v1) = loc1 {
48        v1 * 2
49    } else {
50        1
51    };
52    let fid2 = if let SegmentPointLocation::OnVertex(v2) = loc2 {
53        v2 * 2
54    } else {
55        1
56    };
57
58    let bcoords1 = loc1.barycentric_coordinates();
59    let bcoords2 = loc2.barycentric_coordinates();
60    let local_p1 = seg1.a * bcoords1[0] + seg1.b * bcoords1[1];
61    let local_p2_1 = seg2_1.a * bcoords2[0] + seg2_1.b * bcoords2[1];
62
63    let local_n1 = (local_p2_1 - local_p1).try_normalize().unwrap_or(Vector::Y);
64    let dist = (local_p2_1 - local_p1).dot(local_n1);
65
66    if dist <= prediction + capsule1.radius + capsule2.radius {
67        let local_n2 = pos12.rotation.inverse() * -local_n1;
68        let local_p2 = pos12.inverse_transform_point(local_p2_1);
69        let contact = TrackedContact::new(
70            local_p1,
71            local_p2,
72            PackedFeatureId::face(fid1),
73            PackedFeatureId::face(fid2),
74            dist,
75        );
76        manifold.points.push(contact);
77
78        manifold.local_n1 = local_n1;
79        manifold.local_n2 = local_n2;
80    } else {
81        // No contact within tolerance.
82        return;
83    }
84
85    if let (Some(dir1), Some(dir2)) = (seg1.direction(), seg2_1.direction()) {
86        if dir1.dot(dir2).abs() >= crate::utils::COS_FRAC_PI_8
87            && dir1.dot(local_n1).abs() < crate::utils::SIN_FRAC_PI_8
88        {
89            // Capsules axes are almost parallel and are almost perpendicular to the normal.
90            // Find a second contact point.
91            if let Some((clip_a, clip_b)) = crate::query::details::clip_segment_segment_with_normal(
92                (seg1.a, seg1.b),
93                (seg2_1.a, seg2_1.b),
94                local_n1,
95            ) {
96                let contact =
97                    if (clip_a.0 - local_p1).length_squared() > Real::default_epsilon() * 100.0 {
98                        // Use clip_a as the second contact.
99                        TrackedContact::new(
100                            clip_a.0,
101                            pos12.inverse_transform_point(clip_a.1),
102                            PackedFeatureId::face(clip_a.2 as u32),
103                            PackedFeatureId::face(clip_a.3 as u32),
104                            (clip_a.1 - clip_a.0).dot(local_n1),
105                        )
106                    } else {
107                        // Use clip_b as the second contact.
108                        TrackedContact::new(
109                            clip_b.0,
110                            pos12.inverse_transform_point(clip_b.1),
111                            PackedFeatureId::face(clip_b.2 as u32),
112                            PackedFeatureId::face(clip_b.3 as u32),
113                            (clip_b.1 - clip_b.0).dot(local_n1),
114                        )
115                    };
116
117                manifold.points.push(contact);
118            }
119        }
120    }
121
122    for point in &mut manifold.points {
123        point.local_p1 += manifold.local_n1 * capsule1.radius;
124        point.local_p2 += manifold.local_n2 * capsule2.radius;
125        point.dist -= capsule1.radius + capsule2.radius;
126    }
127
128    manifold.match_contacts(&old_manifold_points);
129}
130
131/// Computes the contact manifold between two capsules.
132#[cfg(feature = "dim3")]
133pub fn contact_manifold_capsule_capsule<'a, ManifoldData, ContactData>(
134    pos12: &Pose,
135    capsule1: &'a Capsule,
136    capsule2: &'a Capsule,
137    prediction: Real,
138    manifold: &mut ContactManifold<ManifoldData, ContactData>,
139) where
140    ContactData: Default + Copy,
141{
142    let seg1 = capsule1.segment;
143    let seg2_1 = capsule2.segment.transformed(pos12);
144    let (loc1, loc2) =
145        crate::query::closest_points::closest_points_segment_segment_with_locations_nD(
146            (&seg1.a, &seg1.b),
147            (&seg2_1.a, &seg2_1.b),
148        );
149
150    let bcoords1 = loc1.barycentric_coordinates();
151    let bcoords2 = loc2.barycentric_coordinates();
152    let local_p1 = seg1.a * bcoords1[0] + seg1.b * bcoords1[1];
153    let local_p2_1 = seg2_1.a * bcoords2[0] + seg2_1.b * bcoords2[1];
154
155    let local_n1 = (local_p2_1 - local_p1).try_normalize().unwrap_or(Vector::Y);
156    let dist = (local_p2_1 - local_p1).dot(local_n1) - capsule1.radius - capsule2.radius;
157
158    if dist <= prediction {
159        let local_n2 = pos12.rotation.inverse() * -local_n1;
160        let fid = PackedFeatureId::face(0);
161        let contact = TrackedContact::new(
162            local_p1 + local_n1 * capsule1.radius,
163            pos12.inverse_transform_point(local_p2_1) + local_n2 * capsule2.radius,
164            fid,
165            fid,
166            dist,
167        );
168
169        if !manifold.points.is_empty() {
170            manifold.points[0].copy_geometry_from(contact);
171        } else {
172            manifold.points.push(contact);
173        }
174
175        manifold.local_n1 = local_n1;
176        manifold.local_n2 = local_n2;
177    } else {
178        manifold.clear();
179    }
180}