box2d_rs/private/collision/
b2_collide_edge.rs

1use crate::b2_collision::*;
2use crate::b2_math::*;
3use crate::b2_common::*;
4use crate::b2_settings::*;
5use crate::shapes::b2_circle_shape::*;
6use crate::shapes::b2_edge_shape::*;
7use crate::shapes::b2_polygon_shape::*;
8
9// Compute contact points for edge versus circle.
10// This accounts for edge connectivity.
11pub fn b2_collide_edge_and_circle(
12	manifold: &mut B2manifold,
13	edge_a: &B2edgeShape,
14	xf_a: &B2Transform,
15	circle_b: &B2circleShape,
16	xf_b: &B2Transform,
17) {
18	manifold.point_count = 0;
19	// Compute circle in frame of edge
20	let q: B2vec2 =
21		b2_mul_t_transform_by_vec2(*xf_a, b2_mul_transform_by_vec2(*xf_b, circle_b.m_p));
22
23	let (a, b) = (edge_a.m_vertex1, edge_a.m_vertex2);
24	let e: B2vec2 = b - a;
25
26	// Normal points to the right for a CCW winding
27	let mut n = B2vec2::new(e.y, -e.x);
28	let offset: f32 = b2_dot(n, q - a);
29
30	let one_sided: bool = edge_a.m_one_sided;
31	if one_sided && offset < 0.0 {
32		return;
33	}
34
35	// Barycentric coordinates
36	let u: f32 = b2_dot(e, b - q);
37	let v: f32 = b2_dot(e, q - a);
38
39	let radius: f32 = edge_a.base.m_radius + circle_b.base.m_radius;
40	let mut cf = B2contactFeature::default();
41	cf.index_b = 0;
42	cf.type_b = B2contactFeatureType::EVertex as u8;
43	// Region A
44	if v <= 0.0 {
45		let p: B2vec2 = a;
46		let d: B2vec2 = q - p;
47		let dd: f32 = b2_dot(d, d);
48		if dd > radius * radius {
49			return;
50		}
51		// Is there an edge connected to A?
52		if edge_a.m_one_sided {
53			let a1: B2vec2 = edge_a.m_vertex0;
54			let b1: B2vec2 = a;
55			let e1: B2vec2 = b1 - a1;
56			let u1: f32 = b2_dot(e1, b1 - q);
57			// Is the circle in Region AB of the previous edge?
58			if u1 > 0.0 {
59				return;
60			}
61		}
62		cf.index_a = 0;
63		cf.type_a = B2contactFeatureType::EVertex as u8;
64		manifold.point_count = 1;
65		manifold.manifold_type = B2manifoldType::ECircles;
66		manifold.local_normal.set_zero();
67		manifold.local_point = p;
68		manifold.points[0].id.cf = cf;
69		manifold.points[0].local_point = circle_b.m_p;
70		return;
71	}
72	// Region b
73	if u <= 0.0 {
74		let p: B2vec2 = b;
75		let d: B2vec2 = q - p;
76		let dd: f32 = b2_dot(d, d);
77		if dd > radius * radius {
78			return;
79		}
80		// Is there an edge connected to b?
81		if edge_a.m_one_sided {
82			let b2: B2vec2 = edge_a.m_vertex3;
83			let a2: B2vec2 = b;
84			let e2: B2vec2 = b2 - a2;
85			let v2: f32 = b2_dot(e2, q - a2);
86			// Is the circle in Region AB of the next edge?
87			if v2 > 0.0 {
88				return;
89			}
90		}
91		cf.index_a = 1;
92		cf.type_a = B2contactFeatureType::EVertex as u8;
93		manifold.point_count = 1;
94		manifold.manifold_type = B2manifoldType::ECircles;
95		manifold.local_normal.set_zero();
96		manifold.local_point = p;
97		manifold.points[0].id.cf = cf;
98		manifold.points[0].local_point = circle_b.m_p;
99		return;
100	}
101	// Region AB
102	let den: f32 = b2_dot(e, e);
103	b2_assert(den > 0.0);
104	let p: B2vec2 = (1.0 / den) * (u * a + v * b);
105	let d: B2vec2 = q - p;
106	let dd: f32 = b2_dot(d, d);
107	if dd > radius * radius {
108		return;
109	}
110
111	if offset < 0.0 {
112		n.set(-n.x, -n.y);
113	}
114	n.normalize();
115	cf.index_a = 0;
116	cf.type_a = B2contactFeatureType::EFace as u8;
117	manifold.point_count = 1;
118	manifold.manifold_type = B2manifoldType::EFaceA;
119	manifold.local_normal = n;
120	manifold.local_point = a;
121	manifold.points[0].id.cf = cf;
122	manifold.points[0].local_point = circle_b.m_p;
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126enum B2ePAxisType {
127	EUnknown,
128	EEdgeA,
129	EEdgeB,
130}
131
132impl Default for B2ePAxisType {
133	fn default() -> Self {
134		return B2ePAxisType::EUnknown;
135	}
136}
137
138// This structure is used to keep track of the best separating axis.
139#[derive(Clone, Default, Copy, Debug)]
140struct B2epaxis {
141	normal: B2vec2,
142	axis_type: B2ePAxisType,
143	index: i32,
144	separation: f32,
145}
146
147// This holds polygon b expressed in frame A.
148#[derive(Clone, Default, Copy, Debug)]
149struct B2tempPolygon {
150	vertices: [B2vec2; B2_MAX_POLYGON_VERTICES],
151	normals: [B2vec2; B2_MAX_POLYGON_VERTICES],
152	count: usize,
153}
154
155// Reference face used for clipping
156#[derive(Clone, Default, Copy, Debug)]
157struct B2referenceFace {
158	i1: usize,
159	i2: usize,
160
161	v1: B2vec2,
162	v2: B2vec2,
163
164	normal: B2vec2,
165	side_normal1: B2vec2,
166	side_offset1: f32,
167
168	side_normal2: B2vec2,
169	side_offset2: f32,
170}
171
172fn b2_compute_edge_separation(polygon_b: B2tempPolygon, v1: B2vec2, normal1: B2vec2) -> B2epaxis {
173	let mut axis = B2epaxis {
174		axis_type: B2ePAxisType::EEdgeA,
175		index: -1,
176		separation: -B2_MAX_FLOAT,
177		normal: B2vec2::zero(),
178	};
179
180	let axes: [B2vec2; 2] = [normal1, -normal1];
181
182	// Find axis with least overlap (min-max problem)
183	for j in 0..2 {
184		let mut sj: f32 = B2_MAX_FLOAT;
185
186		// Find deepest polygon vertex along axis j
187		for i in 0..polygon_b.count {
188			let si: f32 = b2_dot(axes[j], polygon_b.vertices[i] - v1);
189			if si < sj {
190				sj = si;
191			}
192		}
193
194		if sj > axis.separation {
195			axis.index = j as i32;
196			axis.separation = sj;
197			axis.normal = axes[j];
198		}
199	}
200
201	return axis;
202}
203
204fn b2_compute_polygon_separation(polygon_b: B2tempPolygon, v1: B2vec2, v2: B2vec2) -> B2epaxis {
205	let mut axis = B2epaxis {
206		axis_type: B2ePAxisType::EUnknown,
207		index: -1,
208		separation: -B2_MAX_FLOAT,
209		normal: B2vec2::zero(),
210	};
211
212	for i in 0..polygon_b.count {
213		let n: B2vec2 = -polygon_b.normals[i];
214
215		let s1: f32 = b2_dot(n, polygon_b.vertices[i] - v1);
216		let s2: f32 = b2_dot(n, polygon_b.vertices[i] - v2);
217		let s: f32 = b2_min(s1, s2);
218
219		if s > axis.separation {
220			axis.axis_type = B2ePAxisType::EEdgeB;
221			axis.index = i as i32;
222			axis.separation = s;
223			axis.normal = n;
224		}
225	}
226
227	return axis;
228}
229
230pub fn b2_collide_edge_and_polygon(
231	manifold: &mut B2manifold,
232	edge_a: &B2edgeShape,
233	xf_a: &B2Transform,
234	polygon_b: &B2polygonShape,
235	xf_b: &B2Transform,
236) {
237	manifold.point_count = 0;
238
239	let xf: B2Transform = b2_mul_t_transform(*xf_a, *xf_b);
240
241	let centroid_b: B2vec2 = b2_mul_transform_by_vec2(xf, polygon_b.m_centroid);
242
243	let v1: B2vec2 = edge_a.m_vertex1;
244	let v2: B2vec2 = edge_a.m_vertex2;
245
246	let mut edge1: B2vec2 = v2 - v1;
247	edge1.normalize();
248
249	// Normal points to the right for a CCW winding
250	let normal1 = B2vec2::new(edge1.y, -edge1.x);
251	let offset1: f32 = b2_dot(normal1, centroid_b - v1);
252
253	let one_sided: bool = edge_a.m_one_sided;
254	if one_sided && offset1 < 0.0 {
255		return;
256	}
257
258	// Get polygon_b in frameA
259	let mut temp_polygon_b = B2tempPolygon::default();
260	temp_polygon_b.count = polygon_b.m_count;
261	for i in 0..polygon_b.m_count {
262		temp_polygon_b.vertices[i] = b2_mul_transform_by_vec2(xf, polygon_b.m_vertices[i]);
263		temp_polygon_b.normals[i] = b2_mul_rot_by_vec2(xf.q, polygon_b.m_normals[i]);
264	}
265
266	let radius: f32 = polygon_b.base.m_radius + edge_a.base.m_radius;
267
268	let edge_axis: B2epaxis = b2_compute_edge_separation(temp_polygon_b, v1, normal1);
269	if edge_axis.separation > radius {
270		return;
271	}
272
273	let polygon_axis: B2epaxis = b2_compute_polygon_separation(temp_polygon_b, v1, v2);
274	if polygon_axis.separation > radius {
275		return;
276	}
277
278	// Use hysteresis for jitter reduction.
279	const K_RELATIVE_TOL: f32 = 0.98;
280	const K_ABSOLUTE_TOL: f32 = 0.001;
281
282	let mut primary_axis: B2epaxis;
283	if polygon_axis.separation - radius
284		> K_RELATIVE_TOL * (edge_axis.separation - radius) + K_ABSOLUTE_TOL
285	{
286		primary_axis = polygon_axis;
287	} else {
288		primary_axis = edge_axis;
289	}
290
291	if one_sided {
292		// Smooth collision
293		// See https://box2d.org/posts/2020/06/ghost-collisions/
294
295		let mut edge0: B2vec2 = v1 - edge_a.m_vertex0;
296		edge0.normalize();
297		let normal0 = B2vec2::new(edge0.y, -edge0.x);
298		let convex1: bool = b2_cross(edge0, edge1) >= 0.0;
299
300		let mut edge2: B2vec2 = edge_a.m_vertex3 - v2;
301		edge2.normalize();
302		let normal2 = B2vec2::new(edge2.y, -edge2.x);
303		let convex2: bool = b2_cross(edge1, edge2) >= 0.0;
304
305		const SIN_TOL: f32 = 0.1;
306		let side1: bool = b2_dot(primary_axis.normal, edge1) <= 0.0;
307
308		// Check Gauss Map
309		if side1 {
310			if convex1 {
311				if b2_cross(primary_axis.normal, normal0) > SIN_TOL {
312					// Skip region
313					return;
314				}
315
316			// Admit region
317			} else {
318				// Snap region
319				primary_axis = edge_axis;
320			}
321		} else {
322			if convex2 {
323				if b2_cross(normal2, primary_axis.normal) > SIN_TOL {
324					// Skip region
325					return;
326				}
327
328			// Admit region
329			} else {
330				// Snap region
331				primary_axis = edge_axis;
332			}
333		}
334	}
335
336	let mut clip_points = <[B2clipVertex; 2]>::default();
337	let mut rf = B2referenceFace::default();
338	if primary_axis.axis_type == B2ePAxisType::EEdgeA {
339		manifold.manifold_type = B2manifoldType::EFaceA;
340
341		// Search for the polygon normal that is most anti-parallel to the edge normal.
342		let mut best_index: i32 = 0;
343		let mut best_value: f32 = b2_dot(primary_axis.normal, temp_polygon_b.normals[0]);
344		for i in 1..temp_polygon_b.count {
345			let value: f32 = b2_dot(primary_axis.normal, temp_polygon_b.normals[i]);
346			if value < best_value {
347				best_value = value;
348				best_index = i as i32;
349			}
350		}
351
352		let i1: i32 = best_index;
353		let i2: i32 = if i1 + 1 < temp_polygon_b.count as i32 {
354			i1 + 1
355		} else {
356			0
357		};
358
359		clip_points[0].v = temp_polygon_b.vertices[i1 as usize];
360		clip_points[0].id.cf.index_a = 0;
361		clip_points[0].id.cf.index_b = i1 as u8;
362		clip_points[0].id.cf.type_a = B2contactFeatureType::EFace as u8;
363		clip_points[0].id.cf.type_b = B2contactFeatureType::EVertex as u8;
364
365		clip_points[1].v = temp_polygon_b.vertices[i2 as usize];
366		clip_points[1].id.cf.index_a = 0;
367		clip_points[1].id.cf.index_b = i2 as u8;
368		clip_points[1].id.cf.type_a = B2contactFeatureType::EFace as u8;
369		clip_points[1].id.cf.type_b = B2contactFeatureType::EVertex as u8;
370
371		rf.i1 = 0;
372		rf.i2 = 1;
373		rf.v1 = v1;
374		rf.v2 = v2;
375		rf.normal = primary_axis.normal;
376		rf.side_normal1 = -edge1;
377		rf.side_normal2 = edge1;
378	} else {
379		manifold.manifold_type = B2manifoldType::EFaceB;
380
381		clip_points[0].v = v2;
382		clip_points[0].id.cf.index_a = 1;
383		clip_points[0].id.cf.index_b = primary_axis.index as u8;
384		clip_points[0].id.cf.type_a = B2contactFeatureType::EVertex as u8;
385		clip_points[0].id.cf.type_b = B2contactFeatureType::EFace as u8;
386
387		clip_points[1].v = v1;
388		clip_points[1].id.cf.index_a = 0;
389		clip_points[1].id.cf.index_b = primary_axis.index as u8;
390		clip_points[1].id.cf.type_a = B2contactFeatureType::EVertex as u8;
391		clip_points[1].id.cf.type_b = B2contactFeatureType::EFace as u8;
392
393		rf.i1 = primary_axis.index as usize;
394		rf.i2 = if rf.i1 + 1 < temp_polygon_b.count {
395			rf.i1 + 1
396		} else {
397			0
398		};
399		rf.v1 = temp_polygon_b.vertices[rf.i1];
400		rf.v2 = temp_polygon_b.vertices[rf.i2];
401		rf.normal = temp_polygon_b.normals[rf.i1];
402
403		// CCW winding
404		rf.side_normal1.set(rf.normal.y, -rf.normal.x);
405		rf.side_normal2 = -rf.side_normal1;
406	}
407
408	rf.side_offset1 = b2_dot(rf.side_normal1, rf.v1);
409	rf.side_offset2 = b2_dot(rf.side_normal2, rf.v2);
410
411	// Clip incident edge against reference face side planes
412	let mut clip_points1 = <[B2clipVertex; 2]>::default();
413	let mut clip_points2 = <[B2clipVertex; 2]>::default();
414
415	// Clip to side 1
416	let np: usize = b2_clip_segment_to_line(
417		&mut clip_points1,
418		clip_points,
419		rf.side_normal1,
420		rf.side_offset1,
421		rf.i1,
422	);
423
424	if np < B2_MAX_MANIFOLD_POINTS {
425		return;
426	}
427
428	// Clip to side 2
429	let np: usize = b2_clip_segment_to_line(
430		&mut clip_points2,
431		clip_points1,
432		rf.side_normal2,
433		rf.side_offset2,
434		rf.i2,
435	);
436
437	if np < B2_MAX_MANIFOLD_POINTS {
438		return;
439	}
440
441	// Now clip_points2 contains the clipped points.
442	if primary_axis.axis_type == B2ePAxisType::EEdgeA {
443		manifold.local_normal = rf.normal;
444		manifold.local_point = rf.v1;
445	} else {
446		manifold.local_normal = polygon_b.m_normals[rf.i1];
447		manifold.local_point = polygon_b.m_vertices[rf.i1];
448	}
449
450	let mut point_count: usize = 0;
451	for i in 0..B2_MAX_MANIFOLD_POINTS {
452		let separation: f32;
453
454		separation = b2_dot(rf.normal, clip_points2[i].v - rf.v1);
455
456		if separation <= radius {
457			let cp: &mut B2manifoldPoint = &mut manifold.points[point_count];
458
459			if primary_axis.axis_type == B2ePAxisType::EEdgeA {
460				cp.local_point = b2_mul_t_transform_by_vec2(xf, clip_points2[i].v);
461				cp.id = clip_points2[i].id;
462			} else {
463				cp.local_point = clip_points2[i].v;
464				cp.id.cf.type_a = clip_points2[i].id.cf.type_b;
465				cp.id.cf.type_b = clip_points2[i].id.cf.type_a;
466				cp.id.cf.index_a = clip_points2[i].id.cf.index_b;
467				cp.id.cf.index_b = clip_points2[i].id.cf.index_a;
468			}
469
470			point_count += 1;
471		}
472	}
473
474	manifold.point_count = point_count;
475}