1use ga3::Vector;
2
3use crate::{Aabb, bvh::Bvh, triangle};
4
5const BVH_THRESHOLD: usize = 16;
6const WALKABLE_NORMAL_Y: f32 = 0.5;
7const GROUND_BELOW_TOLERANCE: f32 = 0.2;
8const GROUND_REST_TOLERANCE: f32 = 0.05;
9const GROUND_OVERLAP_RATIO: f32 = 0.6;
10
11pub struct TriangleMesh {
13 triangles: Vec<[Vector<f32>; 3]>,
14 normals: Vec<Vector<f32>>,
15 bounds: Aabb,
16 bvh: Option<Bvh>,
17}
18
19pub struct TriangleHit {
21 pub grounded: bool,
23 pub ground_y: f32,
25 pub ground_normal: Vector<f32>,
27 pub push: Vector<f32>,
29}
30
31impl TriangleMesh {
32 pub fn from_vertices(positions: &[[f32; 3]], indices: &[u32]) -> Self {
37 let mut triangles = Vec::with_capacity(indices.len() / 3);
38 let mut normals = Vec::with_capacity(indices.len() / 3);
39 let mut bounds = Aabb::EMPTY;
40
41 for chunk in indices.chunks_exact(3) {
42 let &[index_a, index_b, index_c] = chunk else {
43 continue;
44 };
45 let (Some(&a), Some(&b), Some(&c)) = (
46 positions.get(index_a as usize),
47 positions.get(index_b as usize),
48 positions.get(index_c as usize),
49 ) else {
50 continue;
51 };
52
53 let corners = [Vector::from(a), Vector::from(b), Vector::from(c)];
54 let Some(normal) = triangle::face_normal(&corners) else {
55 continue;
56 };
57
58 for corner in &corners {
59 bounds.include((*corner).into());
60 }
61 triangles.push(corners);
62 normals.push(normal);
63 }
64
65 let bvh = (triangles.len() > BVH_THRESHOLD).then(|| {
66 let triangle_bounds: Vec<Aabb> = triangles.iter().map(triangle_bounds).collect();
67 Bvh::build(&triangle_bounds)
68 });
69
70 Self {
71 triangles,
72 normals,
73 bounds,
74 bvh,
75 }
76 }
77
78 pub const fn bounds(&self) -> &Aabb {
80 &self.bounds
81 }
82
83 pub fn collide_capsule(
88 &self,
89 position: Vector<f32>,
90 velocity_y: f32,
91 radius: f32,
92 height: f32,
93 ) -> Option<TriangleHit> {
94 let capsule_bounds = Aabb {
95 min: [position.x - radius, position.y, position.z - radius],
96 max: [
97 position.x + radius,
98 position.y + height,
99 position.z + radius,
100 ],
101 };
102 if !self.bounds.overlaps(&capsule_bounds) {
103 return None;
104 }
105
106 let segment_bottom = position + Vector::y(radius);
107 let segment_top = position + Vector::y(height - radius);
108
109 let feet = position.y;
110 let falling = velocity_y <= 0.0;
111
112 let mut grounded = false;
113 let mut best_ground_y = f32::NEG_INFINITY;
114 let mut best_ground_normal = Vector::y(1.0);
115 let mut push = Vector::new(0.0, 0.0, 0.0);
116
117 for index in self.overlapping_triangles(&capsule_bounds) {
118 let (Some(corners), Some(&normal)) =
119 (self.triangles.get(index), self.normals.get(index))
120 else {
121 continue;
122 };
123
124 if normal.y > WALKABLE_NORMAL_Y {
125 let Some(ground_y) = triangle::ground_height_at(corners, position.x, position.z)
126 else {
127 continue;
128 };
129 let overlap = ground_y - feet;
130 if overlap < -GROUND_BELOW_TOLERANCE || overlap > height * GROUND_OVERLAP_RATIO {
131 continue;
132 }
133 let supported = (falling && overlap > 0.0)
134 || (-GROUND_BELOW_TOLERANCE..=GROUND_REST_TOLERANCE).contains(&overlap);
135 if supported && ground_y > best_ground_y {
136 grounded = true;
137 best_ground_y = ground_y;
138 best_ground_normal = normal;
139 }
140 } else if let Some(correction) =
141 triangle::capsule_push(segment_bottom, segment_top, radius, corners)
142 {
143 push += correction;
144 }
145 }
146
147 if !grounded && push == Vector::new(0.0, 0.0, 0.0) {
148 return None;
149 }
150
151 Some(TriangleHit {
152 grounded,
153 ground_y: best_ground_y,
154 ground_normal: best_ground_normal,
155 push,
156 })
157 }
158
159 pub fn raycast(
161 &self,
162 origin: Vector<f32>,
163 direction: Vector<f32>,
164 max_distance: f32,
165 ) -> Option<f32> {
166 let origin_array: [f32; 3] = origin.into();
167 let inverse_direction = <[f32; 3]>::from(direction).map(f32::recip);
168
169 let candidates = self.bvh.as_ref().map_or_else(
170 || (0..self.triangles.len()).collect(),
171 |bvh| bvh.ray_overlapping(origin_array, inverse_direction, max_distance),
172 );
173
174 let mut closest: Option<f32> = None;
175 for index in candidates {
176 let Some(corners) = self.triangles.get(index) else {
177 continue;
178 };
179 if let Some(distance) = triangle::ray_intersection(origin, direction, corners)
180 && distance <= max_distance
181 && closest.is_none_or(|best| distance < best)
182 {
183 closest = Some(distance);
184 }
185 }
186 closest
187 }
188
189 fn overlapping_triangles(&self, query: &Aabb) -> Vec<usize> {
190 self.bvh.as_ref().map_or_else(
191 || (0..self.triangles.len()).collect(),
192 |bvh| bvh.overlapping(query),
193 )
194 }
195}
196
197fn triangle_bounds(corners: &[Vector<f32>; 3]) -> Aabb {
198 let mut bounds = Aabb::EMPTY;
199 for corner in corners {
200 bounds.include((*corner).into());
201 }
202 bounds
203}