#![allow(clippy::type_complexity)]
use crate::types::{CollisionPair, Contact, ContactManifold};
use oxiphysics_core::Transform;
use oxiphysics_geometry::Shape;
use std::collections::HashMap;
#[allow(unused_imports)]
use super::functions::*;
use super::functions::{NarrowPhaseFn, gjk_epa, shape_type_ordinal, try_specialized};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(dead_code)]
pub enum ShapeType {
Sphere,
Box,
Capsule,
Cylinder,
Cone,
ConvexHull,
TriangleMesh,
Compound,
HeightField,
Plane,
Custom(u32),
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct DispatchConfig {
pub use_gjk_fallback: bool,
pub max_gjk_iterations: usize,
pub max_epa_iterations: usize,
pub contact_tolerance: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct MeshTriangle {
pub vertices: [[f64; 3]; 3],
pub index: usize,
pub aabb: Aabb,
}
impl MeshTriangle {
pub fn new(vertices: [[f64; 3]; 3], index: usize) -> Self {
let min = [
vertices[0][0].min(vertices[1][0]).min(vertices[2][0]),
vertices[0][1].min(vertices[1][1]).min(vertices[2][1]),
vertices[0][2].min(vertices[1][2]).min(vertices[2][2]),
];
let max = [
vertices[0][0].max(vertices[1][0]).max(vertices[2][0]),
vertices[0][1].max(vertices[1][1]).max(vertices[2][1]),
vertices[0][2].max(vertices[1][2]).max(vertices[2][2]),
];
Self {
vertices,
index,
aabb: Aabb::new(min, max),
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ContactPatch {
pub points: Vec<[f64; 3]>,
pub normal: [f64; 3],
pub depth: f64,
}
impl ContactPatch {
#[allow(dead_code)]
pub fn new(normal: [f64; 3], depth: f64) -> Self {
Self {
points: Vec::new(),
normal,
depth,
}
}
#[allow(dead_code)]
pub fn add_point(&mut self, p: [f64; 3]) {
self.points.push(p);
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.points.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct ConvexPiece {
pub vertices: Vec<[f64; 3]>,
pub shape_type: ShapeType,
pub aabb_min: [f64; 3],
pub aabb_max: [f64; 3],
}
impl ConvexPiece {
#[allow(dead_code)]
pub fn new(vertices: Vec<[f64; 3]>) -> Self {
let aabb_min = vertices.iter().fold([f64::MAX; 3], |mut acc, v| {
acc[0] = acc[0].min(v[0]);
acc[1] = acc[1].min(v[1]);
acc[2] = acc[2].min(v[2]);
acc
});
let aabb_max = vertices.iter().fold([f64::MIN; 3], |mut acc, v| {
acc[0] = acc[0].max(v[0]);
acc[1] = acc[1].max(v[1]);
acc[2] = acc[2].max(v[2]);
acc
});
Self {
vertices,
shape_type: ShapeType::ConvexHull,
aabb_min,
aabb_max,
}
}
#[allow(dead_code)]
pub fn aabb_overlaps(&self, other: &ConvexPiece, expand: f64) -> bool {
for i in 0..3 {
if self.aabb_max[i] + expand < other.aabb_min[i] - expand {
return false;
}
if other.aabb_max[i] + expand < self.aabb_min[i] - expand {
return false;
}
}
true
}
}
pub struct NarrowPhaseDispatcher {
pub(super) table: HashMap<DispatchKey, NarrowPhaseFn>,
#[allow(dead_code)]
pub(super) config: DispatchConfig,
}
impl NarrowPhaseDispatcher {
#[allow(dead_code)]
pub fn empty() -> Self {
NarrowPhaseDispatcher {
table: HashMap::new(),
config: DispatchConfig::default(),
}
}
#[allow(dead_code)]
pub fn with_config(config: DispatchConfig) -> Self {
NarrowPhaseDispatcher {
table: HashMap::new(),
config,
}
}
#[allow(dead_code)]
pub fn register_pair(&mut self, type_a: ShapeType, type_b: ShapeType, func: NarrowPhaseFn) {
let key = DispatchKey::new(type_a, type_b);
self.table.insert(key, func);
}
#[allow(dead_code)]
pub fn unregister_pair(&mut self, type_a: ShapeType, type_b: ShapeType) -> bool {
let key = DispatchKey::new(type_a, type_b);
self.table.remove(&key).is_some()
}
#[allow(dead_code)]
pub fn has_pair(&self, type_a: ShapeType, type_b: ShapeType) -> bool {
let key = DispatchKey::new(type_a, type_b);
self.table.contains_key(&key)
}
#[allow(dead_code)]
pub fn registered_count(&self) -> usize {
self.table.len()
}
#[allow(dead_code)]
pub fn registered_keys(&self) -> Vec<DispatchKey> {
self.table.keys().copied().collect()
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn dispatch(
&self,
shape_a: &dyn Shape,
type_a: ShapeType,
transform_a: &Transform,
shape_b: &dyn Shape,
type_b: ShapeType,
transform_b: &Transform,
pair: CollisionPair,
) -> NarrowPhaseResult {
let key = DispatchKey::new(type_a, type_b);
if let Some(func) = self.table.get(&key) {
return func(shape_a, transform_a, shape_b, transform_b, pair);
}
match gjk_epa(shape_a, transform_a, shape_b, transform_b, pair) {
Some(manifold) => NarrowPhaseResult::contact(manifold),
None => NarrowPhaseResult::separated(),
}
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn dispatch_symmetric(
&self,
shape_a: &dyn Shape,
type_a: ShapeType,
transform_a: &Transform,
shape_b: &dyn Shape,
type_b: ShapeType,
transform_b: &Transform,
pair: CollisionPair,
) -> NarrowPhaseResult {
let key = DispatchKey::new(type_a, type_b);
if let Some(func) = self.table.get(&key) {
let need_swap = shape_type_ordinal(type_a) > shape_type_ordinal(type_b);
if need_swap {
let swapped_pair = CollisionPair::new(pair.b, pair.a);
let mut result = func(shape_b, transform_b, shape_a, transform_a, swapped_pair);
if let Some(ref mut manifold) = result.manifold {
for contact in &mut manifold.contacts {
contact.normal = -contact.normal;
std::mem::swap(&mut contact.point_a, &mut contact.point_b);
}
manifold.pair = pair;
}
return result;
}
return func(shape_a, transform_a, shape_b, transform_b, pair);
}
match gjk_epa(shape_a, transform_a, shape_b, transform_b, pair) {
Some(manifold) => NarrowPhaseResult::contact(manifold),
None => NarrowPhaseResult::separated(),
}
}
pub fn generate_contacts(
shape_a: &dyn Shape,
transform_a: &Transform,
shape_b: &dyn Shape,
transform_b: &Transform,
pair: CollisionPair,
) -> Option<ContactManifold> {
if let Some(contact) = try_specialized(shape_a, transform_a, shape_b, transform_b) {
let mut manifold = ContactManifold::new(pair);
manifold.add_contact(contact);
return Some(manifold);
}
gjk_epa(shape_a, transform_a, shape_b, transform_b, pair)
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn dispatch_batch(
&self,
pairs: &[(
&dyn Shape,
ShapeType,
&Transform,
&dyn Shape,
ShapeType,
&Transform,
CollisionPair,
)],
) -> Vec<NarrowPhaseResult> {
pairs
.iter()
.map(|&(sa, ta_type, ta, sb, tb_type, tb, pair)| {
self.dispatch(sa, ta_type, ta, sb, tb_type, tb, pair)
})
.collect()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Aabb {
pub min: [f64; 3],
pub max: [f64; 3],
}
impl Aabb {
pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
Self { min, max }
}
pub fn overlaps(&self, other: &Aabb) -> bool {
self.min[0] <= other.max[0]
&& self.max[0] >= other.min[0]
&& self.min[1] <= other.max[1]
&& self.max[1] >= other.min[1]
&& self.min[2] <= other.max[2]
&& self.max[2] >= other.min[2]
}
pub fn surface_area(&self) -> f64 {
let dx = self.max[0] - self.min[0];
let dy = self.max[1] - self.min[1];
let dz = self.max[2] - self.min[2];
2.0 * (dx * dy + dy * dz + dx * dz)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SpeculativeConfig {
pub margin: f64,
pub velocity_scale: f64,
}
#[derive(Debug, Clone, Default)]
pub struct DispatchStats {
pub total_dispatches: usize,
pub contacts_found: usize,
pub gjk_fallbacks: usize,
pub specialized_calls: usize,
pub broad_phase_pruned: usize,
}
impl DispatchStats {
pub fn record(&mut self, had_contact: bool, used_gjk: bool) {
self.total_dispatches += 1;
if had_contact {
self.contacts_found += 1;
}
if used_gjk {
self.gjk_fallbacks += 1;
} else {
self.specialized_calls += 1;
}
}
pub fn record_pruned(&mut self) {
self.broad_phase_pruned += 1;
}
pub fn contact_rate(&self) -> f64 {
if self.total_dispatches == 0 {
0.0
} else {
self.contacts_found as f64 / self.total_dispatches as f64
}
}
pub fn specialization_rate(&self) -> f64 {
if self.total_dispatches == 0 {
0.0
} else {
self.specialized_calls as f64 / self.total_dispatches as f64
}
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct HeightFieldSample {
pub position: [f64; 3],
pub normal: [f64; 3],
pub height: f64,
}
#[allow(dead_code)]
pub struct SimpleHeightField {
pub heights: Vec<f64>,
pub rows: usize,
pub cols: usize,
pub spacing: f64,
}
impl SimpleHeightField {
#[allow(dead_code)]
pub fn new(heights: Vec<f64>, rows: usize, cols: usize, spacing: f64) -> Self {
Self {
heights,
rows,
cols,
spacing,
}
}
#[allow(dead_code)]
pub fn height_at(&self, row: usize, col: usize) -> f64 {
let r = row.min(self.rows - 1);
let c = col.min(self.cols - 1);
self.heights[r * self.cols + c]
}
#[allow(dead_code)]
pub fn normal_at(&self, row: usize, col: usize) -> [f64; 3] {
let h = self.height_at(row, col);
let hx = if col + 1 < self.cols {
self.height_at(row, col + 1)
} else {
h
};
let hz = if row + 1 < self.rows {
self.height_at(row + 1, col)
} else {
h
};
let dx = hx - h;
let dz = hz - h;
let nx = -dx;
let ny = self.spacing;
let nz = -dz;
let len = (nx * nx + ny * ny + nz * nz).sqrt();
if len < 1e-12 {
[0.0, 1.0, 0.0]
} else {
[nx / len, ny / len, nz / len]
}
}
#[allow(dead_code)]
pub fn test_sphere(
&self,
sphere_center: [f64; 3],
sphere_radius: f64,
) -> Option<([f64; 3], [f64; 3], f64)> {
let col_f = sphere_center[0] / self.spacing;
let row_f = sphere_center[2] / self.spacing;
if col_f < 0.0 || row_f < 0.0 {
return None;
}
let col = col_f as usize;
let row = row_f as usize;
if col >= self.cols || row >= self.rows {
return None;
}
let h = self.height_at(row, col);
let penetration = h + sphere_radius - sphere_center[1];
if penetration > 0.0 {
let normal = self.normal_at(row, col);
let point = [sphere_center[0], h, sphere_center[2]];
Some((point, normal, penetration))
} else {
None
}
}
}
#[allow(dead_code)]
pub struct DispatchQueue {
pub(super) entries: Vec<DispatchQueueEntry>,
}
impl DispatchQueue {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
#[allow(dead_code)]
pub fn push(&mut self, pair: CollisionPair, priority: f64) {
self.entries.push(DispatchQueueEntry { pair, priority });
}
#[allow(dead_code)]
pub fn pop(&mut self) -> Option<DispatchQueueEntry> {
if self.entries.is_empty() {
return None;
}
let idx = self
.entries
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| {
a.priority
.partial_cmp(&b.priority)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)?;
Some(self.entries.swap_remove(idx))
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.entries.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.entries.clear();
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct DispatchQueueEntry {
pub pair: CollisionPair,
pub priority: f64,
}
#[derive(Debug, Clone)]
pub struct CompoundDispatchResult {
pub manifolds: Vec<ContactManifold>,
pub tests_performed: usize,
pub pairs_pruned: usize,
}
impl CompoundDispatchResult {
pub fn has_contacts(&self) -> bool {
!self.manifolds.is_empty()
}
pub fn total_contacts(&self) -> usize {
self.manifolds.iter().map(|m| m.contacts.len()).sum()
}
}
#[allow(dead_code)]
pub struct ContactPatchReducer {
pub max_contacts: usize,
}
impl ContactPatchReducer {
#[allow(dead_code)]
pub fn new(max_contacts: usize) -> Self {
Self { max_contacts }
}
#[allow(dead_code)]
pub fn reduce(&self, manifold: &mut ContactManifold) {
if manifold.contacts.len() <= self.max_contacts {
return;
}
let mut kept: Vec<Contact> = Vec::with_capacity(self.max_contacts);
if let Some(idx) = manifold
.contacts
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| {
a.depth
.partial_cmp(&b.depth)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)
{
kept.push(manifold.contacts[idx].clone());
}
if kept.len() < self.max_contacts && !manifold.contacts.is_empty() {
let ref_pt = kept[0].point_a;
if let Some(idx) = manifold
.contacts
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| {
let da = (a.point_a - ref_pt).norm_squared();
let db = (b.point_a - ref_pt).norm_squared();
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)
{
kept.push(manifold.contacts[idx].clone());
}
}
while kept.len() < self.max_contacts {
let mut best_area = -1.0;
let mut best_idx = 0;
'outer: for (i, c) in manifold.contacts.iter().enumerate() {
for k in &kept {
if (c.point_a - k.point_a).norm_squared() < 1e-10 {
continue 'outer;
}
}
let area = if kept.len() >= 2 {
let ab = kept[1].point_a - kept[0].point_a;
let ac = c.point_a - kept[0].point_a;
ab.cross(&ac).norm()
} else {
(c.point_a - kept[0].point_a).norm()
};
if area > best_area {
best_area = area;
best_idx = i;
}
}
if best_area <= 0.0 {
break;
}
kept.push(manifold.contacts[best_idx].clone());
}
manifold.contacts = kept;
}
}
#[allow(dead_code)]
pub struct CompoundShape {
pub children: Vec<Box<dyn Shape>>,
pub child_types: Vec<ShapeType>,
pub local_transforms: Vec<Transform>,
}
impl CompoundShape {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
children: Vec::new(),
child_types: Vec::new(),
local_transforms: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_child(
&mut self,
shape: Box<dyn Shape>,
shape_type: ShapeType,
local_transform: Transform,
) {
self.children.push(shape);
self.child_types.push(shape_type);
self.local_transforms.push(local_transform);
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.children.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct NarrowPhaseResult {
pub manifold: Option<ContactManifold>,
}
impl NarrowPhaseResult {
#[allow(dead_code)]
pub fn separated() -> Self {
NarrowPhaseResult { manifold: None }
}
#[allow(dead_code)]
pub fn contact(manifold: ContactManifold) -> Self {
NarrowPhaseResult {
manifold: Some(manifold),
}
}
#[allow(dead_code)]
pub fn has_contact(&self) -> bool {
self.manifold.is_some()
}
#[allow(dead_code)]
pub fn penetration_depth(&self) -> Option<f64> {
self.manifold
.as_ref()
.and_then(|m| m.contacts.first())
.map(|c| c.depth)
}
}
#[allow(dead_code)]
pub struct ConcaveMesh {
pub pieces: Vec<ConvexPiece>,
}
impl ConcaveMesh {
#[allow(dead_code)]
pub fn new(pieces: Vec<ConvexPiece>) -> Self {
Self { pieces }
}
#[allow(dead_code)]
pub fn num_pieces(&self) -> usize {
self.pieces.len()
}
}
#[derive(Debug, Clone)]
pub struct ShapeFeatureCache {
pub(super) support_cache: std::collections::HashMap<u64, [f64; 3]>,
pub hits: usize,
pub misses: usize,
}
impl ShapeFeatureCache {
pub fn new() -> Self {
Self {
support_cache: std::collections::HashMap::new(),
hits: 0,
misses: 0,
}
}
pub fn get_or_insert(&mut self, dir_key: u64, compute: impl FnOnce() -> [f64; 3]) -> [f64; 3] {
if let Some(&cached) = self.support_cache.get(&dir_key) {
self.hits += 1;
cached
} else {
self.misses += 1;
let val = compute();
self.support_cache.insert(dir_key, val);
val
}
}
pub fn hit_ratio(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
pub fn clear(&mut self) {
self.support_cache.clear();
self.hits = 0;
self.misses = 0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(dead_code)]
pub struct DispatchKey(pub ShapeType, pub ShapeType);
impl DispatchKey {
#[allow(dead_code)]
pub fn new(a: ShapeType, b: ShapeType) -> Self {
if shape_type_ordinal(a) <= shape_type_ordinal(b) {
DispatchKey(a, b)
} else {
DispatchKey(b, a)
}
}
}