1#![deny(missing_docs)]
2
3mod bvh;
14mod mesh;
15mod triangle;
16
17pub use mesh::{TriangleHit, TriangleMesh};
18
19use collide_capsule::Capsule;
20use collide_ray::Ray;
21use ga3::Vector;
22use inner_space::DotProduct;
23
24use bvh::Bvh;
25
26#[derive(Clone, Copy)]
28pub struct Aabb {
29 pub min: [f32; 3],
31 pub max: [f32; 3],
33}
34
35impl Aabb {
36 pub const EMPTY: Self = Self {
38 min: [f32::INFINITY; 3],
39 max: [f32::NEG_INFINITY; 3],
40 };
41
42 #[must_use]
44 pub fn overlaps(&self, other: &Self) -> bool {
45 (0..3).all(|axis| self.max[axis] >= other.min[axis] && self.min[axis] <= other.max[axis])
46 }
47
48 pub fn include(&mut self, point: [f32; 3]) {
50 for ((minimum, maximum), value) in self.min.iter_mut().zip(&mut self.max).zip(point) {
51 *minimum = minimum.min(value);
52 *maximum = maximum.max(value);
53 }
54 }
55
56 #[must_use]
58 pub fn merged(&self, other: &Self) -> Self {
59 let mut result = *self;
60 result.include(other.min);
61 result.include(other.max);
62 result
63 }
64}
65
66#[derive(Clone, Copy)]
68pub struct Ground {
69 pub distance: f32,
73 pub normal: Vector<f32>,
75}
76
77#[derive(Clone, Copy, PartialEq, Eq)]
79pub enum Motion {
80 Falling,
82 Rising,
84}
85
86impl Motion {
87 #[must_use]
89 pub fn from_velocity(velocity: Vector<f32>, up: Vector<f32>) -> Self {
90 if velocity.dot(&up) <= 0.0 {
91 Self::Falling
92 } else {
93 Self::Rising
94 }
95 }
96}
97
98pub struct CollisionResult {
100 pub ground: Option<Ground>,
102 pub push: Vector<f32>,
104}
105
106pub struct CollisionWorld {
108 meshes: Vec<TriangleMesh>,
109 bvh: Bvh,
110}
111
112impl CollisionWorld {
113 #[must_use]
115 pub fn new(meshes: Vec<TriangleMesh>) -> Self {
116 let bounds: Vec<Aabb> = meshes.iter().map(|mesh| *mesh.bounds()).collect();
117 Self {
118 bvh: Bvh::build(&bounds),
119 meshes,
120 }
121 }
122
123 #[must_use]
129 pub fn collide_capsule(
130 &self,
131 capsule: &Capsule<Vector<f32>>,
132 up: Vector<f32>,
133 velocity: Vector<f32>,
134 ) -> CollisionResult {
135 let radius = capsule.rad;
136 let motion = Motion::from_velocity(velocity, up);
137
138 let mut result = CollisionResult {
139 ground: None,
140 push: Vector::new(0.0, 0.0, 0.0),
141 };
142
143 let pad = Vector::new(radius, radius, radius);
144 let mut capsule_bounds = Aabb::EMPTY;
145 capsule_bounds.include((capsule.start - pad).into());
146 capsule_bounds.include((capsule.start + pad).into());
147 capsule_bounds.include((capsule.end - pad).into());
148 capsule_bounds.include((capsule.end + pad).into());
149
150 for mesh_index in self.bvh.overlapping(&capsule_bounds) {
151 let Some(mesh) = self.meshes.get(mesh_index) else {
152 continue;
153 };
154 let Some(hit) = mesh.collide_capsule(capsule.start, capsule.end, radius, up, motion)
155 else {
156 continue;
157 };
158
159 if let Some(ground) = hit.ground
160 && result
161 .ground
162 .is_none_or(|best| ground.distance > best.distance)
163 {
164 result.ground = Some(ground);
165 }
166 result.push += hit.push;
167 }
168
169 result
170 }
171
172 pub fn raycast(&self, ray: &Ray<Vector<f32>>, max_distance: f32) -> Option<f32> {
177 let origin: [f32; 3] = ray.origin.into();
178 let inverse_direction = <[f32; 3]>::from(ray.direction).map(f32::recip);
179
180 let mut closest: Option<f32> = None;
181 for mesh_index in self
182 .bvh
183 .ray_overlapping(origin, inverse_direction, max_distance)
184 {
185 let Some(mesh) = self.meshes.get(mesh_index) else {
186 continue;
187 };
188 if let Some(distance) = mesh.raycast(ray.origin, ray.direction, max_distance)
189 && closest.is_none_or(|best| distance < best)
190 {
191 closest = Some(distance);
192 }
193 }
194 closest
195 }
196}