1use std::{
2 f32::consts::PI,
3 fmt,
4 sync::{OnceLock, atomic::Ordering},
5};
6
7use portable_atomic::AtomicU64;
8
9use super::math::{Mat4, Vec3};
10
11static NEXT_MESH_ID: AtomicU64 = AtomicU64::new(1);
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum MeshError {
16 AttributeCountMismatch,
19 IndicesNotTriangles,
21 IndexOutOfRange,
23 Empty,
25}
26
27impl fmt::Display for MeshError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 f.write_str(match self {
30 Self::AttributeCountMismatch => {
31 "texture coordinates and normals need one entry for each position"
32 }
33 Self::IndicesNotTriangles => "the index count is not a multiple of three",
34 Self::IndexOutOfRange => "an index names a vertex past the last one",
35 Self::Empty => "the mesh has no triangle",
36 })
37 }
38}
39
40impl std::error::Error for MeshError {}
41
42#[derive(Clone, Copy, Debug, PartialEq)]
44pub struct MeshHit {
45 pub distance: f32,
47 pub triangle: u32,
49 pub uv: [f32; 2],
51}
52
53#[derive(Clone, Copy, Debug, PartialEq)]
55pub struct MeshContact {
56 pub point: Vec3,
58 pub distance: f32,
60 pub uv: [f32; 2],
62}
63
64pub struct Mesh {
85 id: u64,
86 positions: Vec<[f32; 3]>,
87 normals: Vec<[f32; 3]>,
88 uvs: Vec<[f32; 2]>,
89 indices: Vec<u32>,
90 texture_plane: OnceLock<Option<TexturePlane>>,
91 bounds: OnceLock<(Vec3, Vec3)>,
92}
93
94impl PartialEq for Mesh {
95 fn eq(&self, other: &Self) -> bool {
96 self.id == other.id
97 }
98}
99
100impl fmt::Debug for Mesh {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.debug_struct("Mesh")
103 .field("id", &self.id)
104 .field("vertices", &self.positions.len())
105 .field("triangles", &self.triangle_count())
106 .finish()
107 }
108}
109
110#[derive(Clone, Copy, Debug, PartialEq)]
114pub struct TexturePlane {
115 pub origin: Vec3,
117 pub u_axis: Vec3,
119 pub v_axis: Vec3,
121}
122
123impl Mesh {
124 pub fn new(
128 positions: Vec<[f32; 3]>,
129 uvs: Vec<[f32; 2]>,
130 normals: Option<Vec<[f32; 3]>>,
131 indices: Vec<u32>,
132 ) -> Result<Self, MeshError> {
133 if uvs.len() != positions.len()
134 || normals
135 .as_ref()
136 .is_some_and(|normals| normals.len() != positions.len())
137 {
138 return Err(MeshError::AttributeCountMismatch);
139 }
140 if !indices.len().is_multiple_of(3) {
141 return Err(MeshError::IndicesNotTriangles);
142 }
143 if indices.is_empty() {
144 return Err(MeshError::Empty);
145 }
146 if indices
147 .iter()
148 .any(|&index| index as usize >= positions.len())
149 {
150 return Err(MeshError::IndexOutOfRange);
151 }
152 let normals = normals.unwrap_or_else(|| smooth_normals(&positions, &indices));
153 Ok(Self::assembled(positions, uvs, normals, indices))
154 }
155
156 pub fn plane() -> Self {
160 let mut parts = MeshParts::default();
161 parts.quad(
162 Vec3::ZERO,
163 Vec3::new(1.0, 0.0, 0.0),
164 Vec3::new(0.0, 1.0, 0.0),
165 );
166 parts.into_mesh()
167 }
168
169 pub fn cuboid() -> Self {
173 let mut parts = MeshParts::default();
174 let (x, y, z) = (
175 Vec3::new(1.0, 0.0, 0.0),
176 Vec3::new(0.0, 1.0, 0.0),
177 Vec3::new(0.0, 0.0, 1.0),
178 );
179 let h = 0.5;
180 parts.quad(Vec3::new(-h, -h, -h), x, y);
181 parts.quad(Vec3::new(h, -h, h), -x, y);
182 parts.quad(Vec3::new(-h, -h, h), -z, y);
183 parts.quad(Vec3::new(h, -h, -h), z, y);
184 parts.quad(Vec3::new(-h, -h, h), x, -z);
185 parts.quad(Vec3::new(-h, h, -h), x, z);
186 parts.into_mesh()
187 }
188
189 pub fn sphere(segments: u32, rings: u32) -> Self {
194 let (segments, rings) = (segments.max(3), rings.max(2));
195 let columns = segments + 1;
196 let mut positions = Vec::with_capacity((columns * (rings + 1)) as usize);
197 let mut uvs = Vec::with_capacity(positions.capacity());
198 for ring in 0..=rings {
199 let v = ring as f32 / rings as f32;
200 let (sin_polar, cos_polar) = (v * PI).sin_cos();
201 for segment in 0..=segments {
202 let u = segment as f32 / segments as f32;
203 let (sin_around, cos_around) = ((u - 0.5) * 2.0 * PI).sin_cos();
204 positions.push([sin_polar * sin_around, -cos_polar, -sin_polar * cos_around]);
205 uvs.push([u, v]);
206 }
207 }
208 let mut indices = Vec::with_capacity((segments * rings * 6) as usize);
209 for ring in 0..rings {
210 for segment in 0..segments {
211 let top_left = ring * columns + segment;
212 let bottom_left = top_left + columns;
213 indices.extend([
214 top_left,
215 top_left + 1,
216 bottom_left,
217 bottom_left,
218 top_left + 1,
219 bottom_left + 1,
220 ]);
221 }
222 }
223 let normals = positions.clone();
224 Self::assembled(positions, uvs, normals, indices)
225 }
226
227 pub fn id(&self) -> u64 {
230 self.id
231 }
232
233 pub fn positions(&self) -> &[[f32; 3]] {
235 &self.positions
236 }
237
238 pub fn normals(&self) -> &[[f32; 3]] {
240 &self.normals
241 }
242
243 pub fn uvs(&self) -> &[[f32; 2]] {
245 &self.uvs
246 }
247
248 pub fn indices(&self) -> &[u32] {
250 &self.indices
251 }
252
253 pub fn cast(&self, origin: Vec3, direction: Vec3) -> Option<MeshHit> {
256 let mut nearest: Option<MeshHit> = None;
257 for triangle in 0..self.triangle_count() {
258 let [a, b, c] = self.corners(triangle);
259 let Some((distance, weights)) = ray_triangle(origin, direction, [a, b, c]) else {
260 continue;
261 };
262 if nearest.is_some_and(|hit| hit.distance <= distance) {
263 continue;
264 }
265 nearest = Some(MeshHit {
266 distance,
267 triangle,
268 uv: self.uv_at(triangle, weights),
269 });
270 }
271 nearest
272 }
273
274 pub fn cast_plane(&self, origin: Vec3, direction: Vec3, triangle: u32) -> Option<[f32; 2]> {
279 if triangle >= self.triangle_count() {
280 return None;
281 }
282 let [a, b, c] = self.corners(triangle);
283 let normal = (b - a).cross(c - a);
284 let along = normal.dot(direction);
285 if along.abs() <= f32::EPSILON * normal.length() * direction.length() {
286 return None;
287 }
288 let distance = normal.dot(a - origin) / along;
289 if distance <= 0.0 {
290 return None;
291 }
292 let point = origin + direction * distance;
293 let weights = barycentric(point, [a, b, c], normal)?;
294 Some(self.uv_at(triangle, weights))
295 }
296
297 pub fn point_at(&self, uv: [f32; 2]) -> Option<Vec3> {
300 const EDGE: f32 = 1.0e-4;
301 let point = Vec3::new(uv[0], uv[1], 0.0);
302 (0..self.triangle_count()).find_map(|triangle| {
303 let [ia, ib, ic] = self.triangle_indices(triangle);
304 let corner = |index: usize| {
305 let [u, v] = self.uvs[index];
306 Vec3::new(u, v, 0.0)
307 };
308 let flat = [corner(ia), corner(ib), corner(ic)];
309 let normal = (flat[1] - flat[0]).cross(flat[2] - flat[0]);
310 let [wa, wb, wc] = barycentric(point, flat, normal)?;
311 (wa >= -EDGE && wb >= -EDGE && wc >= -EDGE).then(|| {
312 let position = |index: usize| Vec3::from_array(self.positions[index]);
313 position(ia) * wa + position(ib) * wb + position(ic) * wc
314 })
315 })
316 }
317
318 pub fn nearest(&self, point: Vec3, transform: &Mat4) -> Option<MeshContact> {
321 let mut nearest: Option<MeshContact> = None;
322 for triangle in 0..self.triangle_count() {
323 let corners = self
324 .corners(triangle)
325 .map(|corner| transform.transform_point(corner));
326 let (at, weights) = nearest_on_triangle(point, corners);
327 let distance = (point - at).length();
328 if nearest.is_some_and(|known| known.distance <= distance) {
329 continue;
330 }
331 nearest = Some(MeshContact {
332 point: at,
333 distance,
334 uv: self.uv_at(triangle, weights),
335 });
336 }
337 nearest
338 }
339
340 pub fn bounds(&self) -> (Vec3, Vec3) {
343 *self.bounds.get_or_init(|| {
344 let mut low = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);
345 let mut high = -low;
346 for &position in &self.positions {
347 let point = Vec3::from_array(position);
348 (low, high) = (low.min(point), high.max(point));
349 }
350 (low, high)
351 })
352 }
353
354 pub fn bounds_in(&self, transform: &Mat4) -> (Vec3, Vec3) {
358 let (mesh_low, mesh_high) = self.bounds();
359 let mut low = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);
360 let mut high = -low;
361 for corner in 0..8 {
362 let pick = |bit: u32, low: f32, high: f32| if corner & bit == 0 { low } else { high };
363 let point = transform.transform_point(Vec3::new(
364 pick(1, mesh_low.x, mesh_high.x),
365 pick(2, mesh_low.y, mesh_high.y),
366 pick(4, mesh_low.z, mesh_high.z),
367 ));
368 (low, high) = (low.min(point), high.max(point));
369 }
370 (low, high)
371 }
372
373 pub fn texture_plane(&self) -> Option<TexturePlane> {
378 *self.texture_plane.get_or_init(|| {
379 self.flat_texture_plane()
380 .or_else(|| self.tangent_texture_plane())
381 })
382 }
383
384 fn flat_texture_plane(&self) -> Option<TexturePlane> {
385 let origin = self.point_at([0.0, 0.0])?;
386 let u_axis = self.point_at([1.0, 0.0])? - origin;
387 let v_axis = self.point_at([0.0, 1.0])? - origin;
388 let plane = TexturePlane {
389 origin,
390 u_axis,
391 v_axis,
392 };
393 let tolerance = 1.0e-3 * (u_axis.length() + v_axis.length());
394 let lies_flat = [[1.0, 1.0], [0.5, 0.5], [0.25, 0.75]].iter().all(|&uv| {
395 self.point_at(uv)
396 .is_some_and(|point| (point - plane.at(uv)).length() <= tolerance)
397 });
398 (lies_flat && u_axis.cross(v_axis).length() > tolerance * tolerance).then_some(plane)
399 }
400
401 fn tangent_texture_plane(&self) -> Option<TexturePlane> {
402 const STEP: f32 = 1.0 / 64.0;
403 let middle = self.point_at([0.5, 0.5])?;
404 let u_axis = (self.point_at([0.5 + STEP, 0.5])? - middle) * (1.0 / STEP);
405 let v_axis = (self.point_at([0.5, 0.5 + STEP])? - middle) * (1.0 / STEP);
406 (u_axis.cross(v_axis).length() > f32::EPSILON).then(|| TexturePlane {
407 origin: middle - u_axis * 0.5 - v_axis * 0.5,
408 u_axis,
409 v_axis,
410 })
411 }
412
413 fn triangle_count(&self) -> u32 {
414 (self.indices.len() / 3) as u32
415 }
416
417 fn triangle_indices(&self, triangle: u32) -> [usize; 3] {
418 let first = triangle as usize * 3;
419 [0, 1, 2].map(|corner| self.indices[first + corner] as usize)
420 }
421
422 fn corners(&self, triangle: u32) -> [Vec3; 3] {
423 self.triangle_indices(triangle)
424 .map(|index| Vec3::from_array(self.positions[index]))
425 }
426
427 fn uv_at(&self, triangle: u32, [wa, wb, wc]: [f32; 3]) -> [f32; 2] {
428 let [a, b, c] = self.triangle_indices(triangle).map(|index| self.uvs[index]);
429 [
430 a[0] * wa + b[0] * wb + c[0] * wc,
431 a[1] * wa + b[1] * wb + c[1] * wc,
432 ]
433 }
434
435 pub(super) fn assembled(
436 positions: Vec<[f32; 3]>,
437 uvs: Vec<[f32; 2]>,
438 normals: Vec<[f32; 3]>,
439 indices: Vec<u32>,
440 ) -> Self {
441 Self {
442 id: NEXT_MESH_ID.fetch_add(1, Ordering::Relaxed),
443 positions,
444 normals,
445 uvs,
446 indices,
447 texture_plane: OnceLock::new(),
448 bounds: OnceLock::new(),
449 }
450 }
451}
452
453impl TexturePlane {
454 pub fn at(&self, [u, v]: [f32; 2]) -> Vec3 {
456 self.origin + self.u_axis * u + self.v_axis * v
457 }
458}
459
460#[derive(Default)]
461struct MeshParts {
462 positions: Vec<[f32; 3]>,
463 normals: Vec<[f32; 3]>,
464 uvs: Vec<[f32; 2]>,
465 indices: Vec<u32>,
466}
467
468impl MeshParts {
469 fn quad(&mut self, top_left: Vec3, right: Vec3, down: Vec3) {
473 let base = self.positions.len() as u32;
474 let normal = down.cross(right).normalized().unwrap_or(Vec3::ZERO);
475 for (corner, uv) in [
476 (top_left, [0.0, 0.0]),
477 (top_left + right, [1.0, 0.0]),
478 (top_left + down, [0.0, 1.0]),
479 (top_left + right + down, [1.0, 1.0]),
480 ] {
481 self.positions.push(corner.to_array());
482 self.normals.push(normal.to_array());
483 self.uvs.push(uv);
484 }
485 self.indices
486 .extend([base, base + 1, base + 2, base + 2, base + 1, base + 3]);
487 }
488
489 fn into_mesh(self) -> Mesh {
490 Mesh::assembled(self.positions, self.uvs, self.normals, self.indices)
491 }
492}
493
494pub(super) fn smooth_normals(positions: &[[f32; 3]], indices: &[u32]) -> Vec<[f32; 3]> {
497 let mut sums = vec![Vec3::ZERO; positions.len()];
498 for triangle in indices.as_chunks::<3>().0 {
499 let corner = |index: u32| Vec3::from_array(positions[index as usize]);
500 let (a, b, c) = (
501 corner(triangle[0]),
502 corner(triangle[1]),
503 corner(triangle[2]),
504 );
505 let face = (b - a).cross(c - a);
506 for &index in triangle {
507 sums[index as usize] = sums[index as usize] + face;
508 }
509 }
510 sums.into_iter()
511 .map(|sum| {
512 sum.normalized()
513 .unwrap_or(Vec3::new(0.0, 0.0, -1.0))
514 .to_array()
515 })
516 .collect()
517}
518
519fn ray_triangle(origin: Vec3, direction: Vec3, [a, b, c]: [Vec3; 3]) -> Option<(f32, [f32; 3])> {
522 let (edge_b, edge_c) = (b - a, c - a);
523 let p = direction.cross(edge_c);
524 let determinant = edge_b.dot(p);
525 if determinant.abs() <= f32::EPSILON * edge_b.length() * edge_c.length() * direction.length() {
526 return None;
527 }
528 let inverse = 1.0 / determinant;
529 let offset = origin - a;
530 let wb = offset.dot(p) * inverse;
531 if !(0.0..=1.0).contains(&wb) {
532 return None;
533 }
534 let q = offset.cross(edge_b);
535 let wc = direction.dot(q) * inverse;
536 if wc < 0.0 || wb + wc > 1.0 {
537 return None;
538 }
539 let distance = edge_c.dot(q) * inverse;
540 (distance > 0.0).then_some((distance, [1.0 - wb - wc, wb, wc]))
541}
542
543fn nearest_on_triangle(point: Vec3, [a, b, c]: [Vec3; 3]) -> (Vec3, [f32; 3]) {
547 let (ab, ac) = (b - a, c - a);
548 let (d1, d2) = (ab.dot(point - a), ac.dot(point - a));
549 if d1 <= 0.0 && d2 <= 0.0 {
550 return (a, [1.0, 0.0, 0.0]);
551 }
552 let (d3, d4) = (ab.dot(point - b), ac.dot(point - b));
553 if d3 >= 0.0 && d4 <= d3 {
554 return (b, [0.0, 1.0, 0.0]);
555 }
556 let vc = d1 * d4 - d3 * d2;
557 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
558 let v = d1 / (d1 - d3);
559 return (a + ab * v, [1.0 - v, v, 0.0]);
560 }
561 let (d5, d6) = (ab.dot(point - c), ac.dot(point - c));
562 if d6 >= 0.0 && d5 <= d6 {
563 return (c, [0.0, 0.0, 1.0]);
564 }
565 let vb = d5 * d2 - d1 * d6;
566 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
567 let w = d2 / (d2 - d6);
568 return (a + ac * w, [1.0 - w, 0.0, w]);
569 }
570 let va = d3 * d6 - d5 * d4;
571 if va <= 0.0 && d4 - d3 >= 0.0 && d5 - d6 >= 0.0 {
572 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
573 return (b + (c - b) * w, [0.0, 1.0 - w, w]);
574 }
575 let total = va + vb + vc;
576 if !total.is_normal() {
577 return (a, [1.0, 0.0, 0.0]);
578 }
579 let (v, w) = (vb / total, vc / total);
580 (a + ab * v + ac * w, [1.0 - v - w, v, w])
581}
582
583fn barycentric(point: Vec3, [a, b, c]: [Vec3; 3], normal: Vec3) -> Option<[f32; 3]> {
586 let area = normal.dot(normal);
587 if !area.is_normal() {
588 return None;
589 }
590 let wb = normal.dot((point - a).cross(c - a)) / area;
591 let wc = normal.dot((b - a).cross(point - a)) / area;
592 Some([1.0 - wb - wc, wb, wc])
593}