use super::polygons::collide_polygons;
use super::{make_capsule_polygon, make_id};
use crate::collision::{Capsule, LocalManifold, Polygon, Segment};
use crate::constants::{linear_slop, speculative_distance};
use crate::math_functions::{
add, clamp_float, distance_squared, dot, get_length_and_normalize, left_perp, lerp, mul_add,
neg, normalize, sub, transform_point, Transform, VEC2_ZERO,
};
pub fn collide_capsules(capsule_a: &Capsule, capsule_b: &Capsule, xf: Transform) -> LocalManifold {
let origin = capsule_a.center1;
let xfs = Transform {
p: sub(xf.p, origin),
q: xf.q,
};
let p1 = VEC2_ZERO;
let q1 = sub(capsule_a.center2, origin);
let p2 = transform_point(xfs, capsule_b.center1);
let q2 = transform_point(xfs, capsule_b.center2);
let d1 = sub(q1, p1);
let d2 = sub(q2, p2);
let dd1 = dot(d1, d1);
let dd2 = dot(d2, d2);
let eps_sqr = f32::EPSILON * f32::EPSILON;
debug_assert!(dd1 > eps_sqr && dd2 > eps_sqr);
let r = sub(p1, p2);
let rd1 = dot(r, d1);
let rd2 = dot(r, d2);
let d12 = dot(d1, d2);
let denom = dd1 * dd2 - d12 * d12;
let mut f1 = 0.0;
if denom != 0.0 {
f1 = clamp_float((d12 * rd2 - rd1 * dd2) / denom, 0.0, 1.0);
}
let mut f2 = (d12 * f1 + rd2) / dd2;
if f2 < 0.0 {
f2 = 0.0;
f1 = clamp_float(-rd1 / dd1, 0.0, 1.0);
} else if f2 > 1.0 {
f2 = 1.0;
f1 = clamp_float((d12 - rd1) / dd1, 0.0, 1.0);
}
let closest1 = mul_add(p1, f1, d1);
let closest2 = mul_add(p2, f2, d2);
let distance_sq = distance_squared(closest1, closest2);
let mut manifold = LocalManifold::default();
let radius_a = capsule_a.radius;
let radius_b = capsule_b.radius;
let radius = radius_a + radius_b;
let max_distance = radius + speculative_distance();
if distance_sq > max_distance * max_distance {
return manifold;
}
let distance = distance_sq.sqrt();
let (mut length1, mut length2) = (0.0, 0.0);
let u1 = get_length_and_normalize(&mut length1, d1);
let u2 = get_length_and_normalize(&mut length2, d2);
let fp2 = dot(sub(p2, p1), u1);
let fq2 = dot(sub(q2, p1), u1);
let outside_a = (fp2 <= 0.0 && fq2 <= 0.0) || (fp2 >= length1 && fq2 >= length1);
let fp1 = dot(sub(p1, p2), u2);
let fq1 = dot(sub(q1, p2), u2);
let outside_b = (fp1 <= 0.0 && fq1 <= 0.0) || (fp1 >= length2 && fq1 >= length2);
if !outside_a && !outside_b {
let mut normal_a;
let separation_a;
{
normal_a = left_perp(u1);
let ss1 = dot(sub(p2, p1), normal_a);
let ss2 = dot(sub(q2, p1), normal_a);
let s1p = if ss1 < ss2 { ss1 } else { ss2 };
let s1n = if -ss1 < -ss2 { -ss1 } else { -ss2 };
if s1p > s1n {
separation_a = s1p;
} else {
separation_a = s1n;
normal_a = neg(normal_a);
}
}
let mut normal_b;
let separation_b;
{
normal_b = left_perp(u2);
let ss1 = dot(sub(p1, p2), normal_b);
let ss2 = dot(sub(q1, p2), normal_b);
let s1p = if ss1 < ss2 { ss1 } else { ss2 };
let s1n = if -ss1 < -ss2 { -ss1 } else { -ss2 };
if s1p > s1n {
separation_b = s1p;
} else {
separation_b = s1n;
normal_b = neg(normal_b);
}
}
if separation_a + 0.1 * linear_slop() >= separation_b {
manifold.normal = normal_a;
let mut cp = p2;
let mut cq = q2;
if fp2 < 0.0 && fq2 > 0.0 {
cp = lerp(p2, q2, (0.0 - fp2) / (fq2 - fp2));
} else if fq2 < 0.0 && fp2 > 0.0 {
cq = lerp(q2, p2, (0.0 - fq2) / (fp2 - fq2));
}
if fp2 > length1 && fq2 < length1 {
cp = lerp(p2, q2, (fp2 - length1) / (fp2 - fq2));
} else if fq2 > length1 && fp2 < length1 {
cq = lerp(q2, p2, (fq2 - length1) / (fq2 - fp2));
}
let sp = dot(sub(cp, p1), normal_a);
let sq = dot(sub(cq, p1), normal_a);
if sp <= distance + linear_slop() || sq <= distance + linear_slop() {
{
let mp = &mut manifold.points[0];
mp.point = mul_add(cp, 0.5 * (radius_a - radius_b - sp), normal_a);
mp.separation = sp - radius;
mp.id = make_id(0, 0);
}
{
let mp = &mut manifold.points[1];
mp.point = mul_add(cq, 0.5 * (radius_a - radius_b - sq), normal_a);
mp.separation = sq - radius;
mp.id = make_id(0, 1);
}
manifold.point_count = 2;
}
} else {
manifold.normal = neg(normal_b);
let mut cp = p1;
let mut cq = q1;
if fp1 < 0.0 && fq1 > 0.0 {
cp = lerp(p1, q1, (0.0 - fp1) / (fq1 - fp1));
} else if fq1 < 0.0 && fp1 > 0.0 {
cq = lerp(q1, p1, (0.0 - fq1) / (fp1 - fq1));
}
if fp1 > length2 && fq1 < length2 {
cp = lerp(p1, q1, (fp1 - length2) / (fp1 - fq1));
} else if fq1 > length2 && fp1 < length2 {
cq = lerp(q1, p1, (fq1 - length2) / (fq1 - fp1));
}
let sp = dot(sub(cp, p2), normal_b);
let sq = dot(sub(cq, p2), normal_b);
if sp <= distance + linear_slop() || sq <= distance + linear_slop() {
{
let mp = &mut manifold.points[0];
mp.point = mul_add(cp, 0.5 * (radius_b - radius_a - sp), normal_b);
mp.separation = sp - radius;
mp.id = make_id(0, 0);
}
{
let mp = &mut manifold.points[1];
mp.point = mul_add(cq, 0.5 * (radius_b - radius_a - sq), normal_b);
mp.separation = sq - radius;
mp.id = make_id(1, 0);
}
manifold.point_count = 2;
}
}
}
if manifold.point_count == 0 {
let mut normal = sub(closest2, closest1);
if dot(normal, normal) > eps_sqr {
normal = normalize(normal);
} else {
normal = left_perp(u1);
}
let c1 = mul_add(closest1, radius_a, normal);
let c2 = mul_add(closest2, -radius_b, normal);
let i1 = if f1 == 0.0 { 0 } else { 1 };
let i2 = if f2 == 0.0 { 0 } else { 1 };
manifold.normal = normal;
manifold.points[0].point = lerp(c1, c2, 0.5);
manifold.points[0].separation = distance_sq.sqrt() - radius;
manifold.points[0].id = make_id(i1, i2);
manifold.point_count = 1;
}
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_capsule(
segment_a: &Segment,
capsule_b: &Capsule,
xf: Transform,
) -> LocalManifold {
let capsule_a = Capsule {
center1: segment_a.point1,
center2: segment_a.point2,
radius: 0.0,
};
collide_capsules(&capsule_a, capsule_b, xf)
}
pub fn collide_polygon_and_capsule(
polygon_a: &Polygon,
capsule_b: &Capsule,
xf: Transform,
) -> LocalManifold {
let poly_b = make_capsule_polygon(capsule_b.center1, capsule_b.center2, capsule_b.radius);
collide_polygons(polygon_a, &poly_b, xf)
}