use super::sphere::ray_cast_sphere;
use super::types::{Capsule, MassData, PlaneResult, RayCastInput, ShapeCastInput, Sphere};
use crate::constants::{linear_slop, overlap_slop};
use crate::distance::{
make_proxy, shape_cast, shape_distance, CastOutput, DistanceInput, ShapeCastPairInput,
ShapeProxy, SimplexCache,
};
use crate::math_functions::{
add, add_mm, clamp_float, compute_quat_between_unit_vectors, cylinder_inertia, distance, dot,
get_length_and_normalize, inv_mul_transforms, length_squared, make_matrix_from_quat, max, min,
mul_add, mul_mm, mul_sub, mul_sv, normalize, perp, segment_distance, sphere_inertia, sub,
transform_point, transpose, Aabb, Plane, Transform, Vec3, MAT3_IDENTITY, TRANSFORM_IDENTITY,
VEC3_AXIS_Y,
};
pub fn compute_capsule_mass(shape: &Capsule, density: f32) -> MassData {
let c1 = shape.center1;
let c2 = shape.center2;
let r = shape.radius;
let cylinder_height = distance(c1, c2);
let cylinder_volume = crate::math_functions::PI * r * r * cylinder_height;
let cylinder_mass = cylinder_volume * density;
let sphere_volume = (4.0 / 3.0) * crate::math_functions::PI * r * r * r;
let sphere_mass = sphere_volume * density;
let mut inertia = add_mm(
cylinder_inertia(cylinder_mass, r, cylinder_height),
sphere_inertia(sphere_mass, r),
);
let steiner = 0.125 * sphere_mass * (3.0 * r + 2.0 * cylinder_height) * cylinder_height;
inertia.cx.x += steiner;
inertia.cz.z += steiner;
let mut rotation = MAT3_IDENTITY;
if cylinder_height * cylinder_height > 1000.0 * f32::MIN_POSITIVE {
let direction = normalize(sub(c2, c1));
let q = compute_quat_between_unit_vectors(VEC3_AXIS_Y, direction);
rotation = make_matrix_from_quat(q);
}
let mass = sphere_mass + cylinder_mass;
let center = mul_sv(0.5, add(c1, c2));
MassData {
mass,
center,
inertia: mul_mm(rotation, mul_mm(inertia, transpose(rotation))),
}
}
pub fn compute_capsule_aabb(shape: &Capsule, transform: Transform) -> Aabb {
let r = shape.radius;
let center1 = transform_point(transform, shape.center1);
let center2 = transform_point(transform, shape.center2);
let extent = Vec3 { x: r, y: r, z: r };
Aabb {
lower_bound: sub(min(center1, center2), extent),
upper_bound: add(max(center1, center2), extent),
}
}
pub fn compute_swept_capsule_aabb(shape: &Capsule, xf1: Transform, xf2: Transform) -> Aabb {
let r = Vec3 {
x: shape.radius,
y: shape.radius,
z: shape.radius,
};
let a = transform_point(xf1, shape.center1);
let b = transform_point(xf1, shape.center2);
let c = transform_point(xf2, shape.center1);
let d = transform_point(xf2, shape.center2);
Aabb {
lower_bound: sub(min(min(a, b), min(c, d)), r),
upper_bound: add(max(max(a, b), max(c, d)), r),
}
}
pub fn overlap_capsule(shape: &Capsule, shape_transform: Transform, proxy: &ShapeProxy) -> bool {
let input = DistanceInput {
proxy_a: make_proxy(&[shape.center1, shape.center2], shape.radius),
proxy_b: *proxy,
transform: inv_mul_transforms(shape_transform, TRANSFORM_IDENTITY),
use_radii: true,
};
let mut cache = SimplexCache::default();
let output = shape_distance(&input, &mut cache, None);
output.distance < overlap_slop()
}
pub fn ray_cast_capsule(shape: &Capsule, input: &RayCastInput) -> CastOutput {
debug_assert!(super::is_valid_ray(input));
let c1 = shape.center1;
let c2 = shape.center2;
let r = shape.radius;
let mut output = CastOutput::default();
let d = sub(c2, c1);
let tol = 0.01 * linear_slop();
let length_squared_d = length_squared(d);
if length_squared_d < tol * tol {
let sphere_center = mul_sv(0.5, add(shape.center1, shape.center2));
let sphere = Sphere {
center: sphere_center,
radius: shape.radius,
};
return ray_cast_sphere(&sphere, input);
}
let s = sub(input.origin, c1);
let length = length_squared_d.sqrt();
let axis = mul_sv(1.0 / length, d);
let u = dot(s, axis);
let c = mul_sv(u, axis);
let sc = sub(s, c);
let sc2 = length_squared(sc);
if sc2 < r * r {
let u_clamped = clamp_float(u, 0.0, length);
let cp = mul_sv(u_clamped, axis);
let scp = sub(s, cp);
let scp2 = length_squared(scp);
if scp2 < r * r {
output.hit = true;
output.point = input.origin;
return output;
}
let sphere = Sphere {
center: add(c1, cp),
radius: r,
};
return ray_cast_sphere(&sphere, input);
}
let dr = input.translation;
let mut ray_length = 0.0;
let ray_axis = get_length_and_normalize(&mut ray_length, dr);
if ray_length == 0.0 {
return output;
}
let v = u + input.max_fraction * dot(dr, axis);
if (u < -r && v < -r) || (length + r < u && length + r < v) {
return output;
}
let a1 = axis;
let a2 = ray_axis;
let a12 = dot(a1, a2);
let tr;
let det = 1.0 - a12 * a12;
if det < f32::EPSILON {
let perp = mul_sub(a2, a12, a1);
let perp2 = length_squared(perp);
let beta = dot(sc, perp);
let gamma = sc2 - r * r;
let disc = beta * beta - perp2 * gamma;
if beta >= 0.0 || disc < 0.0 {
return output;
}
tr = gamma / (-beta + disc.sqrt());
} else {
let inv_det = 1.0 / det;
let sa1 = u;
let sa2 = dot(s, a2);
let t1 = (sa1 - a12 * sa2) * inv_det;
let t2 = (a12 * sa1 - sa2) * inv_det;
let p1 = mul_sv(t1, a1);
let p2 = mul_add(s, t2, a2);
let g = sub(p2, p1);
let g2 = length_squared(g);
if g2 > r * r {
return output;
}
let h = ((r * r - g2) * inv_det).sqrt();
tr = t2 - h;
}
if tr < 0.0 || input.max_fraction * ray_length < tr {
return output;
}
let tc = u + tr * a12;
if tc < 0.0 {
let sphere = Sphere {
center: c1,
radius: r,
};
return ray_cast_sphere(&sphere, input);
}
if length < tc {
let sphere = Sphere {
center: c2,
radius: r,
};
return ray_cast_sphere(&sphere, input);
}
let p = mul_add(s, tr, ray_axis);
let mut normal = mul_sub(p, tc, axis);
normal = normalize(normal);
output.point = add(c1, p);
output.normal = normal;
output.fraction = clamp_float(tr / ray_length, 0.0, input.max_fraction);
output.hit = true;
output
}
pub fn shape_cast_capsule(capsule: &Capsule, input: &ShapeCastInput) -> CastOutput {
let pair_input = ShapeCastPairInput {
proxy_a: make_proxy(&[capsule.center1, capsule.center2], capsule.radius),
proxy_b: input.proxy,
transform: TRANSFORM_IDENTITY,
translation_b: input.translation,
max_fraction: input.max_fraction,
can_encroach: input.can_encroach,
};
shape_cast(&pair_input)
}
pub fn collide_mover_and_capsule(
result: &mut PlaneResult,
shape: &Capsule,
mover: &Capsule,
) -> i32 {
let total_radius = mover.radius + shape.radius;
let approach = segment_distance(shape.center1, shape.center2, mover.center1, mover.center2);
let mut distance = 0.0;
let mut normal = get_length_and_normalize(&mut distance, sub(approach.point2, approach.point1));
if distance > total_radius {
return 0;
}
let linear_slop = linear_slop();
if distance < linear_slop {
let mut mover_length = 0.0;
let mover_axis =
get_length_and_normalize(&mut mover_length, sub(mover.center2, mover.center1));
normal = if mover_length > linear_slop {
perp(mover_axis)
} else {
VEC3_AXIS_Y
};
distance = 0.0;
}
result.plane = Plane {
normal,
offset: total_radius - distance,
};
result.point = approach.point1;
1
}