collide-mesh 0.1.0

Triangle mesh collider for the collide crate (3D)
Documentation
use ga3::Vector;
use inner_space::{DotProduct, InnerSpace};

const DEGENERATE_AREA: f32 = 1e-12;
const DEGENERATE_DENOMINATOR: f32 = 1e-10;
const BARYCENTRIC_TOLERANCE: f32 = 1e-4;
const PARALLEL_DETERMINANT: f32 = 1e-8;
const MINIMUM_RAY_DISTANCE: f32 = 1e-4;

pub fn face_normal(triangle: &[Vector<f32>; 3]) -> Option<Vector<f32>> {
    let [a, b, c] = *triangle;
    let normal = (b - a).cross(c - a);
    let area2 = normal.magnitude2();
    (area2 > DEGENERATE_AREA).then(|| normal / area2.sqrt())
}

pub fn ground_height_at(triangle: &[Vector<f32>; 3], x: f32, z: f32) -> Option<f32> {
    let [a, b, c] = *triangle;

    let edge_ab = [b.x - a.x, b.z - a.z];
    let edge_ac = [c.x - a.x, c.z - a.z];
    let to_point = [x - a.x, z - a.z];

    let ab_ab = dot2(edge_ab, edge_ab);
    let ab_ac = dot2(edge_ab, edge_ac);
    let ab_point = dot2(edge_ab, to_point);
    let ac_ac = dot2(edge_ac, edge_ac);
    let ac_point = dot2(edge_ac, to_point);

    let denominator = ab_ab * ac_ac - ab_ac * ab_ac;
    if denominator.abs() < DEGENERATE_DENOMINATOR {
        return None;
    }
    let inverse = 1.0 / denominator;

    let u = (ac_ac * ab_point - ab_ac * ac_point) * inverse;
    let v = (ab_ab * ac_point - ab_ac * ab_point) * inverse;

    let inside = u >= -BARYCENTRIC_TOLERANCE
        && v >= -BARYCENTRIC_TOLERANCE
        && u + v <= 1.0 + BARYCENTRIC_TOLERANCE;
    inside.then_some(a.y + u * (b.y - a.y) + v * (c.y - a.y))
}

pub fn capsule_push(
    segment_bottom: Vector<f32>,
    segment_top: Vector<f32>,
    radius: f32,
    triangle: &[Vector<f32>; 3],
) -> Option<Vector<f32>> {
    let on_triangle = closest_point(triangle, segment_bottom);
    let on_segment = closest_point_on_segment(segment_bottom, segment_top, on_triangle);
    let on_triangle = closest_point(triangle, on_segment);

    let offset = on_segment - on_triangle;
    let distance2 = offset.magnitude2();
    if distance2 >= radius * radius || distance2 < DEGENERATE_DENOMINATOR {
        return None;
    }

    let distance = distance2.sqrt();
    Some(offset * ((radius - distance) / distance))
}

pub fn ray_intersection(
    origin: Vector<f32>,
    direction: Vector<f32>,
    triangle: &[Vector<f32>; 3],
) -> Option<f32> {
    let [a, b, c] = *triangle;
    let edge_ab = b - a;
    let edge_ac = c - a;

    let perpendicular = direction.cross(edge_ac);
    let determinant = edge_ab.dot(&perpendicular);
    if determinant.abs() < PARALLEL_DETERMINANT {
        return None;
    }
    let inverse = 1.0 / determinant;

    let from_a = origin - a;
    let u = from_a.dot(&perpendicular) * inverse;
    if !(0.0..=1.0).contains(&u) {
        return None;
    }

    let cross_ab = from_a.cross(edge_ab);
    let v = direction.dot(&cross_ab) * inverse;
    if v < 0.0 || u + v > 1.0 {
        return None;
    }

    let distance = edge_ac.dot(&cross_ab) * inverse;
    (distance > MINIMUM_RAY_DISTANCE).then_some(distance)
}

fn closest_point(triangle: &[Vector<f32>; 3], point: Vector<f32>) -> Vector<f32> {
    let [a, b, c] = *triangle;
    let ab = b - a;
    let ac = c - a;

    let to_point_a = point - a;
    let ab_a = ab.dot(&to_point_a);
    let ac_a = ac.dot(&to_point_a);
    if ab_a <= 0.0 && ac_a <= 0.0 {
        return a;
    }

    let to_point_b = point - b;
    let ab_b = ab.dot(&to_point_b);
    let ac_b = ac.dot(&to_point_b);
    if ab_b >= 0.0 && ac_b <= ab_b {
        return b;
    }

    let region_ab = ab_a * ac_b - ab_b * ac_a;
    if region_ab <= 0.0 && ab_a >= 0.0 && ab_b <= 0.0 {
        return a + ab * (ab_a / (ab_a - ab_b));
    }

    let to_point_c = point - c;
    let ab_c = ab.dot(&to_point_c);
    let ac_c = ac.dot(&to_point_c);
    if ac_c >= 0.0 && ab_c <= ac_c {
        return c;
    }

    let region_ac = ab_c * ac_a - ab_a * ac_c;
    if region_ac <= 0.0 && ac_a >= 0.0 && ac_c <= 0.0 {
        return a + ac * (ac_a / (ac_a - ac_c));
    }

    let region_bc = ab_b * ac_c - ab_c * ac_b;
    if region_bc <= 0.0 && ac_b - ab_b >= 0.0 && ab_c - ac_c >= 0.0 {
        return b + (c - b) * ((ac_b - ab_b) / ((ac_b - ab_b) + (ab_c - ac_c)));
    }

    let inverse = 1.0 / (region_bc + region_ac + region_ab);
    a + ab * (region_ac * inverse) + ac * (region_ab * inverse)
}

fn closest_point_on_segment(
    start: Vector<f32>,
    end: Vector<f32>,
    point: Vector<f32>,
) -> Vector<f32> {
    let segment = end - start;
    let length2 = segment.magnitude2();
    if length2 < DEGENERATE_DENOMINATOR {
        return start;
    }
    let parameter = ((point - start).dot(&segment) / length2).clamp(0.0, 1.0);
    start + segment * parameter
}

fn dot2(a: [f32; 2], b: [f32; 2]) -> f32 {
    a[0] * b[0] + a[1] * b[1]
}