use super::is_valid_ray;
use crate::collision::{Capsule, CastOutput, Circle, Polygon, RayCastInput, Segment};
use crate::distance::{
make_proxy, shape_cast, shape_distance, DistanceInput, ShapeCastPairInput, SimplexCache,
};
use crate::math_functions::{
add, clamp_float, cross, distance_squared, dot, get_length_and_normalize, length_squared, lerp,
mul_add, mul_sub, mul_sv, neg, normalize, right_perp, sub, Vec2, TRANSFORM_IDENTITY,
};
pub fn point_in_circle(shape: &Circle, point: Vec2) -> bool {
let center = shape.center;
distance_squared(point, center) <= shape.radius * shape.radius
}
pub fn point_in_capsule(shape: &Capsule, point: Vec2) -> bool {
let rr = shape.radius * shape.radius;
let p1 = shape.center1;
let p2 = shape.center2;
let d = sub(p2, p1);
let dd = dot(d, d);
if dd == 0.0 {
return distance_squared(point, p1) <= rr;
}
let mut t = dot(sub(point, p1), d) / dd;
t = clamp_float(t, 0.0, 1.0);
let c = mul_add(p1, t, d);
distance_squared(point, c) <= rr
}
pub fn point_in_polygon(shape: &Polygon, point: Vec2) -> bool {
let input = DistanceInput {
proxy_a: make_proxy(&shape.vertices[..shape.count as usize], 0.0),
proxy_b: make_proxy(&[point], 0.0),
transform: TRANSFORM_IDENTITY,
use_radii: false,
};
let mut cache = SimplexCache::default();
let output = shape_distance(&input, &mut cache, None);
output.distance <= shape.radius
}
pub fn ray_cast_circle(shape: &Circle, input: &RayCastInput) -> CastOutput {
debug_assert!(is_valid_ray(input));
let p = shape.center;
let mut output = CastOutput::default();
let s = sub(input.origin, p);
let r = shape.radius;
let rr = r * r;
let mut length = 0.0;
let d = get_length_and_normalize(&mut length, input.translation);
if length == 0.0 {
if length_squared(s) < rr {
output.point = input.origin;
output.hit = true;
}
return output;
}
let t = -dot(s, d);
let c = mul_add(s, t, d);
let cc = dot(c, c);
if cc > rr {
return output;
}
let h = (rr - cc).sqrt();
let fraction = t - h;
if fraction < 0.0 || input.max_fraction * length < fraction {
if length_squared(s) < rr {
output.point = input.origin;
output.hit = true;
}
return output;
}
let hit_point = mul_add(s, fraction, d);
output.fraction = fraction / length;
output.normal = normalize(hit_point);
output.point = mul_add(p, shape.radius, output.normal);
output.hit = true;
output
}
pub fn ray_cast_capsule(shape: &Capsule, input: &RayCastInput) -> CastOutput {
debug_assert!(is_valid_ray(input));
let mut output = CastOutput::default();
let v1 = shape.center1;
let v2 = shape.center2;
let e = sub(v2, v1);
let mut capsule_length = 0.0;
let a = get_length_and_normalize(&mut capsule_length, e);
if capsule_length < f32::EPSILON {
let circle = Circle {
center: v1,
radius: shape.radius,
};
return ray_cast_circle(&circle, input);
}
let p1 = input.origin;
let d = input.translation;
let q = sub(p1, v1);
let qa = dot(q, a);
let qp = mul_add(q, -qa, a);
let radius = shape.radius;
if dot(qp, qp) < radius * radius {
if qa < 0.0 {
let circle = Circle {
center: v1,
radius: shape.radius,
};
return ray_cast_circle(&circle, input);
}
if qa > capsule_length {
let circle = Circle {
center: v2,
radius: shape.radius,
};
return ray_cast_circle(&circle, input);
}
output.point = input.origin;
output.hit = true;
return output;
}
let mut n = Vec2 { x: a.y, y: -a.x };
let mut ray_length = 0.0;
let u = get_length_and_normalize(&mut ray_length, d);
let den = -a.x * u.y + u.x * a.y;
if -f32::EPSILON < den && den < f32::EPSILON {
return output;
}
let b1 = mul_sub(q, radius, n);
let b2 = mul_add(q, radius, n);
let inv_den = 1.0 / den;
let s21 = (a.x * b1.y - b1.x * a.y) * inv_den;
let s22 = (a.x * b2.y - b2.x * a.y) * inv_den;
let s2;
let b;
if s21 < s22 {
s2 = s21;
b = b1;
} else {
s2 = s22;
b = b2;
n = neg(n);
}
if s2 < 0.0 || input.max_fraction * ray_length < s2 {
return output;
}
let s1 = (-b.x * u.y + u.x * b.y) * inv_den;
if s1 < 0.0 {
let circle = Circle {
center: v1,
radius: shape.radius,
};
ray_cast_circle(&circle, input)
} else if capsule_length < s1 {
let circle = Circle {
center: v2,
radius: shape.radius,
};
ray_cast_circle(&circle, input)
} else {
output.fraction = s2 / ray_length;
output.point = add(lerp(v1, v2, s1 / capsule_length), mul_sv(shape.radius, n));
output.normal = n;
output.hit = true;
output
}
}
pub fn ray_cast_segment(shape: &Segment, input: &RayCastInput, one_sided: bool) -> CastOutput {
if one_sided {
let offset = cross(
sub(input.origin, shape.point1),
sub(shape.point2, shape.point1),
);
if offset < 0.0 {
return CastOutput::default();
}
}
let p1 = input.origin;
let d = input.translation;
let v1 = shape.point1;
let v2 = shape.point2;
let e = sub(v2, v1);
let mut output = CastOutput::default();
let mut length = 0.0;
let e_unit = get_length_and_normalize(&mut length, e);
if length == 0.0 {
return output;
}
let mut normal = right_perp(e_unit);
let numerator = dot(normal, sub(v1, p1));
let denominator = dot(normal, d);
if denominator == 0.0 {
return output;
}
let t = numerator / denominator;
if t < 0.0 || input.max_fraction < t {
return output;
}
let p = mul_add(p1, t, d);
let s = dot(sub(p, v1), e_unit);
if s < 0.0 || length < s {
return output;
}
if numerator > 0.0 {
normal = neg(normal);
}
output.fraction = t;
output.point = p;
output.normal = normal;
output.hit = true;
output
}
pub fn ray_cast_polygon(shape: &Polygon, input: &RayCastInput) -> CastOutput {
debug_assert!(is_valid_ray(input));
if shape.radius == 0.0 {
let base = shape.vertices[0];
let p1 = sub(input.origin, base);
let d = input.translation;
let (mut lower, mut upper) = (0.0f32, input.max_fraction);
let mut index = -1i32;
let mut output = CastOutput::default();
for edge_index in 0..shape.count as usize {
let vertex = sub(shape.vertices[edge_index], base);
let numerator = dot(shape.normals[edge_index], sub(vertex, p1));
let denominator = dot(shape.normals[edge_index], d);
if denominator == 0.0 {
if numerator < 0.0 {
return output;
}
} else {
if denominator < 0.0 && numerator < lower * denominator {
lower = numerator / denominator;
index = edge_index as i32;
} else if denominator > 0.0 && numerator < upper * denominator {
upper = numerator / denominator;
}
}
if upper < lower {
return output;
}
}
debug_assert!(0.0 <= lower && lower <= input.max_fraction);
if index >= 0 {
output.fraction = lower;
output.normal = shape.normals[index as usize];
output.point = mul_add(input.origin, lower, d);
output.hit = true;
} else {
output.point = input.origin;
output.hit = true;
}
return output;
}
let cast_input = ShapeCastPairInput {
proxy_a: make_proxy(&shape.vertices[..shape.count as usize], shape.radius),
proxy_b: make_proxy(&[input.origin], 0.0),
transform: TRANSFORM_IDENTITY,
translation_b: input.translation,
max_fraction: input.max_fraction,
can_encroach: false,
};
shape_cast(&cast_input)
}