Add ConvexHull struct storing vertices with a support function that returns the farthest point in a given direction, enabling GJK/EPA collision detection. Update all Collider match arms across the physics crate (collision, raycast, integrator, solver) to handle the new variant. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
134 lines
4.1 KiB
Rust
134 lines
4.1 KiB
Rust
use voltex_math::{Vec3, AABB};
|
|
|
|
/// A convex hull collider defined by a set of vertices.
|
|
#[derive(Debug, Clone)]
|
|
pub struct ConvexHull {
|
|
pub vertices: Vec<Vec3>,
|
|
}
|
|
|
|
impl ConvexHull {
|
|
pub fn new(vertices: Vec<Vec3>) -> Self {
|
|
ConvexHull { vertices }
|
|
}
|
|
|
|
/// Support function for GJK: returns the vertex farthest in the given direction.
|
|
pub fn support(&self, direction: Vec3) -> Vec3 {
|
|
let mut best = self.vertices[0];
|
|
let mut best_dot = best.dot(direction);
|
|
for &v in &self.vertices[1..] {
|
|
let d = v.dot(direction);
|
|
if d > best_dot {
|
|
best_dot = d;
|
|
best = v;
|
|
}
|
|
}
|
|
best
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Collider {
|
|
Sphere { radius: f32 },
|
|
Box { half_extents: Vec3 },
|
|
Capsule { radius: f32, half_height: f32 },
|
|
ConvexHull(ConvexHull),
|
|
}
|
|
|
|
impl Collider {
|
|
pub fn aabb(&self, position: Vec3) -> AABB {
|
|
match self {
|
|
Collider::Sphere { radius } => {
|
|
let r = Vec3::new(*radius, *radius, *radius);
|
|
AABB::new(position - r, position + r)
|
|
}
|
|
Collider::Box { half_extents } => {
|
|
AABB::new(position - *half_extents, position + *half_extents)
|
|
}
|
|
Collider::Capsule { radius, half_height } => {
|
|
let r = Vec3::new(*radius, *half_height + *radius, *radius);
|
|
AABB::new(position - r, position + r)
|
|
}
|
|
Collider::ConvexHull(hull) => {
|
|
let mut min = position + hull.vertices[0];
|
|
let mut max = min;
|
|
for &v in &hull.vertices[1..] {
|
|
let p = position + v;
|
|
min = Vec3::new(min.x.min(p.x), min.y.min(p.y), min.z.min(p.z));
|
|
max = Vec3::new(max.x.max(p.x), max.y.max(p.y), max.z.max(p.z));
|
|
}
|
|
AABB::new(min, max)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_sphere_aabb() {
|
|
let c = Collider::Sphere { radius: 2.0 };
|
|
let aabb = c.aabb(Vec3::new(1.0, 0.0, 0.0));
|
|
assert_eq!(aabb.min, Vec3::new(-1.0, -2.0, -2.0));
|
|
assert_eq!(aabb.max, Vec3::new(3.0, 2.0, 2.0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_box_aabb() {
|
|
let c = Collider::Box { half_extents: Vec3::new(1.0, 2.0, 3.0) };
|
|
let aabb = c.aabb(Vec3::ZERO);
|
|
assert_eq!(aabb.min, Vec3::new(-1.0, -2.0, -3.0));
|
|
assert_eq!(aabb.max, Vec3::new(1.0, 2.0, 3.0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_capsule_aabb() {
|
|
let c = Collider::Capsule { radius: 0.5, half_height: 1.0 };
|
|
let aabb = c.aabb(Vec3::new(1.0, 2.0, 3.0));
|
|
assert_eq!(aabb.min, Vec3::new(0.5, 0.5, 2.5));
|
|
assert_eq!(aabb.max, Vec3::new(1.5, 3.5, 3.5));
|
|
}
|
|
|
|
#[test]
|
|
fn test_convex_hull_support() {
|
|
let hull = ConvexHull::new(vec![
|
|
Vec3::new(-1.0, -1.0, -1.0),
|
|
Vec3::new(1.0, -1.0, -1.0),
|
|
Vec3::new(0.0, 1.0, 0.0),
|
|
Vec3::new(0.0, -1.0, 1.0),
|
|
]);
|
|
// Support in +Y direction should return the top vertex
|
|
let s = hull.support(Vec3::new(0.0, 1.0, 0.0));
|
|
assert!((s.y - 1.0).abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_convex_hull_support_negative() {
|
|
let hull = ConvexHull::new(vec![
|
|
Vec3::new(0.0, 0.0, 0.0),
|
|
Vec3::new(1.0, 0.0, 0.0),
|
|
Vec3::new(0.0, 1.0, 0.0),
|
|
]);
|
|
let s = hull.support(Vec3::new(-1.0, 0.0, 0.0));
|
|
assert!((s.x - 0.0).abs() < 1e-6); // origin is farthest in -X
|
|
}
|
|
|
|
#[test]
|
|
fn test_convex_hull_in_collider() {
|
|
// Test that ConvexHull variant works with existing collision system
|
|
let hull = ConvexHull::new(vec![
|
|
Vec3::new(-1.0, -1.0, -1.0),
|
|
Vec3::new(1.0, -1.0, -1.0),
|
|
Vec3::new(1.0, 1.0, -1.0),
|
|
Vec3::new(-1.0, 1.0, -1.0),
|
|
Vec3::new(-1.0, -1.0, 1.0),
|
|
Vec3::new(1.0, -1.0, 1.0),
|
|
Vec3::new(1.0, 1.0, 1.0),
|
|
Vec3::new(-1.0, 1.0, 1.0),
|
|
]);
|
|
// Just verify construction works
|
|
assert_eq!(hull.vertices.len(), 8);
|
|
}
|
|
}
|