use super::{make_capsule_polygon, make_id};
use crate::collision::{LocalManifold, Polygon, Segment};
use crate::constants::{linear_slop, speculative_distance};
use crate::distance::segment_distance;
use crate::math_functions::{
add, cross_sv, dot, lerp, min_float, mul_add, neg, rotate_vector, sub, transform_point,
Transform, VEC2_ZERO,
};
fn clip_polygons(
poly_a: &Polygon,
poly_b: &Polygon,
edge_a: i32,
edge_b: i32,
flip: bool,
) -> LocalManifold {
let mut manifold = LocalManifold::default();
let poly1: &Polygon;
let (i11, i12): (usize, usize);
let poly2: &Polygon;
let (i21, i22): (usize, usize);
if flip {
poly1 = poly_b;
poly2 = poly_a;
i11 = edge_b as usize;
i12 = if edge_b + 1 < poly_b.count {
(edge_b + 1) as usize
} else {
0
};
i21 = edge_a as usize;
i22 = if edge_a + 1 < poly_a.count {
(edge_a + 1) as usize
} else {
0
};
} else {
poly1 = poly_a;
poly2 = poly_b;
i11 = edge_a as usize;
i12 = if edge_a + 1 < poly_a.count {
(edge_a + 1) as usize
} else {
0
};
i21 = edge_b as usize;
i22 = if edge_b + 1 < poly_b.count {
(edge_b + 1) as usize
} else {
0
};
}
let normal = poly1.normals[i11];
let v11 = poly1.vertices[i11];
let v12 = poly1.vertices[i12];
let v21 = poly2.vertices[i21];
let v22 = poly2.vertices[i22];
let tangent = cross_sv(1.0, normal);
let lower1 = 0.0;
let upper1 = dot(sub(v12, v11), tangent);
let upper2 = dot(sub(v21, v11), tangent);
let lower2 = dot(sub(v22, v11), tangent);
if upper2 < lower1 || upper1 < lower2 {
return manifold;
}
let mut v_lower = if lower2 < lower1 && upper2 - lower2 > f32::EPSILON {
lerp(v22, v21, (lower1 - lower2) / (upper2 - lower2))
} else {
v22
};
let mut v_upper = if upper2 > upper1 && upper2 - lower2 > f32::EPSILON {
lerp(v22, v21, (upper1 - lower2) / (upper2 - lower2))
} else {
v21
};
let separation_lower = dot(sub(v_lower, v11), normal);
let separation_upper = dot(sub(v_upper, v11), normal);
let r1 = poly1.radius;
let r2 = poly2.radius;
v_lower = mul_add(v_lower, 0.5 * (r1 - r2 - separation_lower), normal);
v_upper = mul_add(v_upper, 0.5 * (r1 - r2 - separation_upper), normal);
let radius = r1 + r2;
if !flip {
manifold.normal = normal;
{
let cp = &mut manifold.points[0];
cp.point = v_lower;
cp.separation = separation_lower - radius;
cp.id = make_id(i11 as i32, i22 as i32);
manifold.point_count += 1;
}
{
let cp = &mut manifold.points[1];
cp.point = v_upper;
cp.separation = separation_upper - radius;
cp.id = make_id(i12 as i32, i21 as i32);
manifold.point_count += 1;
}
} else {
manifold.normal = neg(normal);
{
let cp = &mut manifold.points[0];
cp.point = v_upper;
cp.separation = separation_upper - radius;
cp.id = make_id(i21 as i32, i12 as i32);
manifold.point_count += 1;
}
{
let cp = &mut manifold.points[1];
cp.point = v_lower;
cp.separation = separation_lower - radius;
cp.id = make_id(i22 as i32, i11 as i32);
manifold.point_count += 1;
}
}
manifold
}
fn find_max_separation(poly1: &Polygon, poly2: &Polygon) -> (f32, i32) {
let count1 = poly1.count as usize;
let count2 = poly2.count as usize;
let n1s = &poly1.normals;
let v1s = &poly1.vertices;
let v2s = &poly2.vertices;
let mut best_index = 0i32;
let mut max_separation = -f32::MAX;
for i in 0..count1 {
let n = n1s[i];
let v1 = v1s[i];
let mut si = f32::MAX;
for v2 in v2s.iter().take(count2) {
let sij = dot(n, sub(*v2, v1));
if sij < si {
si = sij;
}
}
if si > max_separation {
max_separation = si;
best_index = i as i32;
}
}
(max_separation, best_index)
}
pub fn collide_polygons(polygon_a: &Polygon, polygon_b: &Polygon, xf: Transform) -> LocalManifold {
let origin = polygon_a.vertices[0];
let slop = linear_slop();
let speculative = speculative_distance();
let xfs = Transform {
p: sub(xf.p, origin),
q: xf.q,
};
let mut local_poly_a = Polygon {
count: polygon_a.count,
radius: polygon_a.radius,
..Default::default()
};
local_poly_a.vertices[0] = VEC2_ZERO;
local_poly_a.normals[0] = polygon_a.normals[0];
for i in 1..local_poly_a.count as usize {
local_poly_a.vertices[i] = sub(polygon_a.vertices[i], origin);
local_poly_a.normals[i] = polygon_a.normals[i];
}
let mut local_poly_b = Polygon {
count: polygon_b.count,
radius: polygon_b.radius,
..Default::default()
};
for i in 0..local_poly_b.count as usize {
local_poly_b.vertices[i] = transform_point(xfs, polygon_b.vertices[i]);
local_poly_b.normals[i] = rotate_vector(xfs.q, polygon_b.normals[i]);
}
let (separation_a, mut edge_a) = find_max_separation(&local_poly_a, &local_poly_b);
let (separation_b, mut edge_b) = find_max_separation(&local_poly_b, &local_poly_a);
let radius = local_poly_a.radius + local_poly_b.radius;
if separation_a > speculative + radius || separation_b > speculative + radius {
return LocalManifold::default();
}
let flip;
if separation_a >= separation_b {
flip = false;
let search_direction = local_poly_a.normals[edge_a as usize];
let count = local_poly_b.count as usize;
let normals = &local_poly_b.normals;
edge_b = 0;
let mut min_dot = f32::MAX;
for (i, normal) in normals.iter().enumerate().take(count) {
let dot_ = dot(search_direction, *normal);
if dot_ < min_dot {
min_dot = dot_;
edge_b = i as i32;
}
}
} else {
flip = true;
let search_direction = local_poly_b.normals[edge_b as usize];
let count = local_poly_a.count as usize;
let normals = &local_poly_a.normals;
edge_a = 0;
let mut min_dot = f32::MAX;
for (i, normal) in normals.iter().enumerate().take(count) {
let dot_ = dot(search_direction, *normal);
if dot_ < min_dot {
min_dot = dot_;
edge_a = i as i32;
}
}
}
let mut manifold = LocalManifold::default();
if separation_a > 0.1 * slop || separation_b > 0.1 * slop {
let i11 = edge_a as usize;
let i12 = if edge_a + 1 < local_poly_a.count {
(edge_a + 1) as usize
} else {
0
};
let i21 = edge_b as usize;
let i22 = if edge_b + 1 < local_poly_b.count {
(edge_b + 1) as usize
} else {
0
};
let v11 = local_poly_a.vertices[i11];
let v12 = local_poly_a.vertices[i12];
let v21 = local_poly_b.vertices[i21];
let v22 = local_poly_b.vertices[i22];
let result = segment_distance(v11, v12, v21, v22);
debug_assert!(result.distance_squared > 0.0);
let distance = result.distance_squared.sqrt();
let separation = distance - radius;
if distance - radius > speculative {
return manifold;
}
manifold = clip_polygons(&local_poly_a, &local_poly_b, edge_a, edge_b, flip);
let mut min_separation = f32::MAX;
for i in 0..manifold.point_count as usize {
min_separation = min_float(min_separation, manifold.points[i].separation);
}
if separation + 0.1 * slop < min_separation {
let feature: Option<(
crate::math_functions::Vec2,
crate::math_functions::Vec2,
usize,
usize,
)> = if result.fraction1 == 0.0 && result.fraction2 == 0.0 {
Some((v11, v21, i11, i21))
} else if result.fraction1 == 0.0 && result.fraction2 == 1.0 {
Some((v11, v22, i11, i22))
} else if result.fraction1 == 1.0 && result.fraction2 == 0.0 {
Some((v12, v21, i12, i21))
} else if result.fraction1 == 1.0 && result.fraction2 == 1.0 {
Some((v12, v22, i12, i22))
} else {
None
};
if let Some((va, vb, ia, ib)) = feature {
let mut normal = sub(vb, va);
let inv_distance = 1.0 / distance;
normal.x *= inv_distance;
normal.y *= inv_distance;
let c1 = mul_add(va, local_poly_a.radius, normal);
let c2 = mul_add(vb, -local_poly_b.radius, normal);
manifold.normal = normal;
manifold.points[0].point = lerp(c1, c2, 0.5);
manifold.points[0].separation = distance - radius;
manifold.points[0].id = make_id(ia as i32, ib as i32);
manifold.point_count = 1;
}
}
} else {
manifold = clip_polygons(&local_poly_a, &local_poly_b, edge_a, edge_b, flip);
}
for i in 0..manifold.point_count as usize {
manifold.points[i].point = add(manifold.points[i].point, origin);
}
manifold
}
pub fn collide_segment_and_polygon(
segment_a: &Segment,
polygon_b: &Polygon,
xf: Transform,
) -> LocalManifold {
let polygon_a = make_capsule_polygon(segment_a.point1, segment_a.point2, 0.0);
collide_polygons(&polygon_a, polygon_b, xf)
}