Skip to main content

box2d_rust/manifold/
polygons.rs

1// SAT polygon clipper and polygon-vs-polygon manifold from manifold.c.
2// SPDX-FileCopyrightText: 2023 Erin Catto
3// SPDX-License-Identifier: MIT
4
5use super::{make_capsule_polygon, make_id};
6use crate::collision::{LocalManifold, Polygon, Segment};
7use crate::constants::{linear_slop, speculative_distance};
8use crate::distance::segment_distance;
9use crate::math_functions::{
10    add, cross_sv, dot, lerp, min_float, mul_add, neg, rotate_vector, sub, transform_point,
11    Transform, VEC2_ZERO,
12};
13
14/// Polygon clipper used to compute contact points when there are potentially
15/// two contact points. (static b2ClipPolygons)
16fn clip_polygons(
17    poly_a: &Polygon,
18    poly_b: &Polygon,
19    edge_a: i32,
20    edge_b: i32,
21    flip: bool,
22) -> LocalManifold {
23    let mut manifold = LocalManifold::default();
24
25    // reference polygon
26    let poly1: &Polygon;
27    let (i11, i12): (usize, usize);
28
29    // incident polygon
30    let poly2: &Polygon;
31    let (i21, i22): (usize, usize);
32
33    if flip {
34        poly1 = poly_b;
35        poly2 = poly_a;
36        i11 = edge_b as usize;
37        i12 = if edge_b + 1 < poly_b.count {
38            (edge_b + 1) as usize
39        } else {
40            0
41        };
42        i21 = edge_a as usize;
43        i22 = if edge_a + 1 < poly_a.count {
44            (edge_a + 1) as usize
45        } else {
46            0
47        };
48    } else {
49        poly1 = poly_a;
50        poly2 = poly_b;
51        i11 = edge_a as usize;
52        i12 = if edge_a + 1 < poly_a.count {
53            (edge_a + 1) as usize
54        } else {
55            0
56        };
57        i21 = edge_b as usize;
58        i22 = if edge_b + 1 < poly_b.count {
59            (edge_b + 1) as usize
60        } else {
61            0
62        };
63    }
64
65    let normal = poly1.normals[i11];
66
67    // Reference edge vertices
68    let v11 = poly1.vertices[i11];
69    let v12 = poly1.vertices[i12];
70
71    // Incident edge vertices
72    let v21 = poly2.vertices[i21];
73    let v22 = poly2.vertices[i22];
74
75    let tangent = cross_sv(1.0, normal);
76
77    let lower1 = 0.0;
78    let upper1 = dot(sub(v12, v11), tangent);
79
80    // Incident edge points opposite of tangent due to CCW winding
81    let upper2 = dot(sub(v21, v11), tangent);
82    let lower2 = dot(sub(v22, v11), tangent);
83
84    // Are the segments disjoint?
85    if upper2 < lower1 || upper1 < lower2 {
86        return manifold;
87    }
88
89    let mut v_lower = if lower2 < lower1 && upper2 - lower2 > f32::EPSILON {
90        lerp(v22, v21, (lower1 - lower2) / (upper2 - lower2))
91    } else {
92        v22
93    };
94
95    let mut v_upper = if upper2 > upper1 && upper2 - lower2 > f32::EPSILON {
96        lerp(v22, v21, (upper1 - lower2) / (upper2 - lower2))
97    } else {
98        v21
99    };
100
101    let separation_lower = dot(sub(v_lower, v11), normal);
102    let separation_upper = dot(sub(v_upper, v11), normal);
103
104    let r1 = poly1.radius;
105    let r2 = poly2.radius;
106
107    // Put contact points at midpoint, accounting for radii
108    v_lower = mul_add(v_lower, 0.5 * (r1 - r2 - separation_lower), normal);
109    v_upper = mul_add(v_upper, 0.5 * (r1 - r2 - separation_upper), normal);
110
111    let radius = r1 + r2;
112
113    if !flip {
114        manifold.normal = normal;
115
116        {
117            let cp = &mut manifold.points[0];
118            cp.point = v_lower;
119            cp.separation = separation_lower - radius;
120            cp.id = make_id(i11 as i32, i22 as i32);
121            manifold.point_count += 1;
122        }
123        {
124            let cp = &mut manifold.points[1];
125            cp.point = v_upper;
126            cp.separation = separation_upper - radius;
127            cp.id = make_id(i12 as i32, i21 as i32);
128            manifold.point_count += 1;
129        }
130    } else {
131        manifold.normal = neg(normal);
132
133        {
134            let cp = &mut manifold.points[0];
135            cp.point = v_upper;
136            cp.separation = separation_upper - radius;
137            cp.id = make_id(i21 as i32, i12 as i32);
138            manifold.point_count += 1;
139        }
140        {
141            let cp = &mut manifold.points[1];
142            cp.point = v_lower;
143            cp.separation = separation_lower - radius;
144            cp.id = make_id(i22 as i32, i11 as i32);
145            manifold.point_count += 1;
146        }
147    }
148
149    manifold
150}
151
152/// Find the max separation between poly1 and poly2 using edge normals from
153/// poly1. Returns (max separation, edge index). (static b2FindMaxSeparation)
154fn find_max_separation(poly1: &Polygon, poly2: &Polygon) -> (f32, i32) {
155    let count1 = poly1.count as usize;
156    let count2 = poly2.count as usize;
157    let n1s = &poly1.normals;
158    let v1s = &poly1.vertices;
159    let v2s = &poly2.vertices;
160
161    let mut best_index = 0i32;
162    let mut max_separation = -f32::MAX;
163    for i in 0..count1 {
164        // Get poly1 normal in frame2.
165        let n = n1s[i];
166        let v1 = v1s[i];
167
168        // Find the deepest point for normal i.
169        let mut si = f32::MAX;
170        for v2 in v2s.iter().take(count2) {
171            let sij = dot(n, sub(*v2, v1));
172            if sij < si {
173                si = sij;
174            }
175        }
176
177        if si > max_separation {
178            max_separation = si;
179            best_index = i as i32;
180        }
181    }
182
183    (max_separation, best_index)
184}
185
186/// Compute the contact manifold between two polygons. (b2CollidePolygons)
187///
188/// Due to speculation, every polygon is rounded.
189/// Algorithm:
190///
191/// compute edge separation using the separating axis test (SAT)
192/// if (separation > speculation_distance)
193///   return
194/// find reference and incident edge
195/// if separation >= 0.1f * B2_LINEAR_SLOP
196///   compute closest points between reference and incident edge
197///   if vertices are closest
198///      single vertex-vertex contact
199///   else
200///      clip edges
201///   end
202/// else
203///   clip edges
204/// end
205pub fn collide_polygons(polygon_a: &Polygon, polygon_b: &Polygon, xf: Transform) -> LocalManifold {
206    let origin = polygon_a.vertices[0];
207    let slop = linear_slop();
208    let speculative = speculative_distance();
209
210    // Shift to the origin in frame A for round-off, a pure translation in A's frame
211    let xfs = Transform {
212        p: sub(xf.p, origin),
213        q: xf.q,
214    };
215
216    let mut local_poly_a = Polygon {
217        count: polygon_a.count,
218        radius: polygon_a.radius,
219        ..Default::default()
220    };
221    local_poly_a.vertices[0] = VEC2_ZERO;
222    local_poly_a.normals[0] = polygon_a.normals[0];
223    for i in 1..local_poly_a.count as usize {
224        local_poly_a.vertices[i] = sub(polygon_a.vertices[i], origin);
225        local_poly_a.normals[i] = polygon_a.normals[i];
226    }
227
228    // Put polyB in polyA's frame to reduce round-off error
229    let mut local_poly_b = Polygon {
230        count: polygon_b.count,
231        radius: polygon_b.radius,
232        ..Default::default()
233    };
234    for i in 0..local_poly_b.count as usize {
235        local_poly_b.vertices[i] = transform_point(xfs, polygon_b.vertices[i]);
236        local_poly_b.normals[i] = rotate_vector(xfs.q, polygon_b.normals[i]);
237    }
238
239    let (separation_a, mut edge_a) = find_max_separation(&local_poly_a, &local_poly_b);
240    let (separation_b, mut edge_b) = find_max_separation(&local_poly_b, &local_poly_a);
241
242    let radius = local_poly_a.radius + local_poly_b.radius;
243
244    if separation_a > speculative + radius || separation_b > speculative + radius {
245        return LocalManifold::default();
246    }
247
248    // Find incident edge
249    let flip;
250    if separation_a >= separation_b {
251        flip = false;
252
253        let search_direction = local_poly_a.normals[edge_a as usize];
254
255        // Find the incident edge on polyB
256        let count = local_poly_b.count as usize;
257        let normals = &local_poly_b.normals;
258        edge_b = 0;
259        let mut min_dot = f32::MAX;
260        for (i, normal) in normals.iter().enumerate().take(count) {
261            let dot_ = dot(search_direction, *normal);
262            if dot_ < min_dot {
263                min_dot = dot_;
264                edge_b = i as i32;
265            }
266        }
267    } else {
268        flip = true;
269
270        let search_direction = local_poly_b.normals[edge_b as usize];
271
272        // Find the incident edge on polyA
273        let count = local_poly_a.count as usize;
274        let normals = &local_poly_a.normals;
275        edge_a = 0;
276        let mut min_dot = f32::MAX;
277        for (i, normal) in normals.iter().enumerate().take(count) {
278            let dot_ = dot(search_direction, *normal);
279            if dot_ < min_dot {
280                min_dot = dot_;
281                edge_a = i as i32;
282            }
283        }
284    }
285
286    let mut manifold = LocalManifold::default();
287
288    // Using slop here to ensure vertex-vertex normal vectors can be safely normalized
289    if separation_a > 0.1 * slop || separation_b > 0.1 * slop {
290        // Edges are disjoint. Find closest points between reference edge and incident edge
291        // Reference edge on polygon A
292        let i11 = edge_a as usize;
293        let i12 = if edge_a + 1 < local_poly_a.count {
294            (edge_a + 1) as usize
295        } else {
296            0
297        };
298        let i21 = edge_b as usize;
299        let i22 = if edge_b + 1 < local_poly_b.count {
300            (edge_b + 1) as usize
301        } else {
302            0
303        };
304
305        let v11 = local_poly_a.vertices[i11];
306        let v12 = local_poly_a.vertices[i12];
307        let v21 = local_poly_b.vertices[i21];
308        let v22 = local_poly_b.vertices[i22];
309
310        let result = segment_distance(v11, v12, v21, v22);
311        debug_assert!(result.distance_squared > 0.0);
312        let distance = result.distance_squared.sqrt();
313        let separation = distance - radius;
314
315        if distance - radius > speculative {
316            // This can happen in the vertex-vertex case
317            return manifold;
318        }
319
320        // Attempt to clip edges
321        manifold = clip_polygons(&local_poly_a, &local_poly_b, edge_a, edge_b, flip);
322
323        let mut min_separation = f32::MAX;
324        for i in 0..manifold.point_count as usize {
325            min_separation = min_float(min_separation, manifold.points[i].separation);
326        }
327
328        // Does vertex-vertex have substantially larger separation?
329        if separation + 0.1 * slop < min_separation {
330            // The four vertex-vertex feature pairs, matching the C branches.
331            let feature: Option<(
332                crate::math_functions::Vec2,
333                crate::math_functions::Vec2,
334                usize,
335                usize,
336            )> = if result.fraction1 == 0.0 && result.fraction2 == 0.0 {
337                Some((v11, v21, i11, i21))
338            } else if result.fraction1 == 0.0 && result.fraction2 == 1.0 {
339                Some((v11, v22, i11, i22))
340            } else if result.fraction1 == 1.0 && result.fraction2 == 0.0 {
341                Some((v12, v21, i12, i21))
342            } else if result.fraction1 == 1.0 && result.fraction2 == 1.0 {
343                Some((v12, v22, i12, i22))
344            } else {
345                None
346            };
347
348            if let Some((va, vb, ia, ib)) = feature {
349                let mut normal = sub(vb, va);
350                let inv_distance = 1.0 / distance;
351                normal.x *= inv_distance;
352                normal.y *= inv_distance;
353
354                let c1 = mul_add(va, local_poly_a.radius, normal);
355                let c2 = mul_add(vb, -local_poly_b.radius, normal);
356
357                manifold.normal = normal;
358                manifold.points[0].point = lerp(c1, c2, 0.5);
359                manifold.points[0].separation = distance - radius;
360                manifold.points[0].id = make_id(ia as i32, ib as i32);
361                manifold.point_count = 1;
362            }
363        }
364    } else {
365        // Polygons overlap
366        manifold = clip_polygons(&local_poly_a, &local_poly_b, edge_a, edge_b, flip);
367    }
368
369    // Undo the origin shift so points are in frame A
370    for i in 0..manifold.point_count as usize {
371        manifold.points[i].point = add(manifold.points[i].point, origin);
372    }
373
374    manifold
375}
376
377/// Compute the contact manifold between a segment and a polygon.
378/// (b2CollideSegmentAndPolygon)
379pub fn collide_segment_and_polygon(
380    segment_a: &Segment,
381    polygon_b: &Polygon,
382    xf: Transform,
383) -> LocalManifold {
384    let polygon_a = make_capsule_polygon(segment_a.point1, segment_a.point2, 0.0);
385    collide_polygons(&polygon_a, polygon_b, xf)
386}