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