1#![cfg_attr(not(feature = "std"), no_std)]
28#![cfg_attr(docsrs, feature(doc_cfg))]
29
30use core::fmt;
31
32mod base;
33pub mod intersect;
34
35pub use base::{Hit, Ray};
36
37use nalgebra::{RealField, Vector3};
38use num_traits::Float;
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct Triangle<T: Float + RealField = f32> {
43 m: nalgebra::Matrix3<T>,
44 v: nalgebra::Vector3<T>,
45}
46
47impl<T: Float + RealField> Triangle<T> {
48 #[inline]
52 pub fn new(
53 v1: impl Into<Vector3<T>>,
54 v2: impl Into<Vector3<T>>,
55 v3: impl Into<Vector3<T>>,
56 ) -> Result<Self, TriangleError> {
57 let m = intersect::compute_baldwin_weber_transform(v1.into(), v2.into(), v3.into())?;
58 Ok(Self {
59 m: m.fixed_view::<3, 3>(0, 0).into(),
60 v: m.column(3).into(),
61 })
62 }
63
64 #[inline]
68 pub fn intersect(&self, ray: Ray<T>, t_min: T, t_max: T) -> Option<Hit<T>> {
69 intersect::intersect_baldwin_weber(&self.m, &self.v, ray, t_min, t_max)
70 }
71
72 #[inline]
74 pub fn reconstruct_vertices(&self) -> [Vector3<T>; 3] {
75 let inv_m = self
76 .m
77 .try_inverse()
78 .expect("Cannot invert the triangle matrix");
79
80 let v1 = inv_m * (-self.v);
81 let v2 = v1 + inv_m.column(0);
82 let v3 = v1 + inv_m.column(1);
83
84 [v1, v2, v3]
85 }
86
87 #[inline]
88 pub fn m(&self) -> nalgebra::Matrix3<T> {
89 self.m
90 }
91
92 #[inline]
93 pub fn v(&self) -> nalgebra::Vector3<T> {
94 self.v
95 }
96}
97
98#[derive(Clone, Copy, Debug, PartialEq)]
99pub enum TriangleError {
100 Degenerate,
101}
102
103impl fmt::Display for TriangleError {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 match self {
106 TriangleError::Degenerate => write!(f, "Triangle is degenerate (zero surface area)"),
107 }
108 }
109}
110
111#[cfg(feature = "std")]
112impl std::error::Error for TriangleError {}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 fn approx<T: Float>(a: T, b: T, tol: T) -> bool {
119 (a - b).abs() <= tol
120 }
121
122 #[test]
123 fn degenerate_triangle_rejected() {
124 let zeros = Vector3::new(0.0, 0.0, 0.0);
125 assert_eq!(
126 Triangle::new(zeros, zeros, zeros),
127 Err(TriangleError::Degenerate)
128 );
129 }
130
131 #[test]
132 fn basic_hit_matches_expected_barycentrics() {
133 let tri = Triangle::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]).unwrap();
135
136 let ray = Ray::new([0.25, 0.25, 1.0], [0.0, 0.0, -1.0]);
137 let hit = tri.intersect(ray, 0.0, 10.0).unwrap();
138 assert!(approx(hit.t, 1.0, 1e-12));
139 assert!(approx(hit.u, 0.25, 1e-12));
140 assert!(approx(hit.v, 0.25, 1e-12));
141 assert!(approx(hit.w(), 0.5, 1e-12));
142 }
143
144 #[test]
145 fn miss_outside_triangle() {
146 let tri = Triangle::new(
147 Vector3::new(0.0, 0.0, 0.0),
148 Vector3::new(1.0, 0.0, 0.0),
149 Vector3::new(0.0, 1.0, 0.0),
150 )
151 .unwrap();
152
153 let ray = Ray::new(Vector3::new(0.8, 0.8, 1.0), Vector3::new(0.0, 0.0, -1.0));
154 assert_eq!(tri.intersect(ray, 0.0, 10.0), None);
155 }
156
157 #[test]
158 fn agrees_with_moller_trumbore() {
159 let v0 = Vector3::new(-1.0, 0.0, 0.5);
160 let v1 = Vector3::new(2.0, 0.3, -0.2);
161 let v2 = Vector3::new(0.2, 1.7, 0.1);
162 let tri = Triangle::new(v0, v1, v2).unwrap();
163
164 let rays = [
165 Ray::new(Vector3::new(0.1, 0.2, 2.0), Vector3::new(0.0, 0.0, -1.0)),
166 Ray::new(
167 Vector3::new(10.0, 10.0, 10.0),
168 Vector3::new(-1.0, -1.0, -1.0),
169 ),
170 Ray::new(Vector3::new(0.0, -2.0, 0.0), Vector3::new(0.2, 1.0, 0.1)),
171 Ray::new(Vector3::new(0.3, 0.4, 0.5), Vector3::new(1.0, 0.1, -0.2)),
172 ];
173
174 for ray in rays {
175 let a = tri.intersect(ray, 0.0, 1e9);
176 let b = intersect::intersect_moller_trumbore(v0, v1, v2, ray, 0.0, 1e9);
177 match (a, b) {
178 (None, None) => {}
179 (Some(ha), Some(hb)) => {
180 assert!(approx(ha.t, hb.t, 1e-12), "t mismatch: {ha:?} vs {hb:?}");
181 assert!(approx(ha.u, hb.u, 1e-12), "b1 mismatch: {ha:?} vs {hb:?}");
182 assert!(approx(ha.v, hb.v, 1e-12), "b2 mismatch: {ha:?} vs {hb:?}");
183 }
184 _ => panic!("disagreement: precomputed={a:?}, mt={b:?}"),
185 }
186 }
187 }
188
189 #[test]
190 fn reconstruct_vertices_matches_input() {
191 let v0 = Vector3::new(-1.2, 0.5, 3.1);
192 let v1 = Vector3::new(2.4, -1.1, 0.8);
193 let v2 = Vector3::new(0.3, 4.2, -1.5);
194
195 let tri = Triangle::new(v0, v1, v2).unwrap();
196 let reconstructed = tri.reconstruct_vertices();
197
198 assert!(approx(reconstructed[0].x, v0.x, 1e-12));
199 assert!(approx(reconstructed[0].y, v0.y, 1e-12));
200 assert!(approx(reconstructed[0].z, v0.z, 1e-12));
201
202 assert!(approx(reconstructed[1].x, v1.x, 1e-12));
203 assert!(approx(reconstructed[1].y, v1.y, 1e-12));
204 assert!(approx(reconstructed[1].z, v1.z, 1e-12));
205
206 assert!(approx(reconstructed[2].x, v2.x, 1e-12));
207 assert!(approx(reconstructed[2].y, v2.y, 1e-12));
208 assert!(approx(reconstructed[2].z, v2.z, 1e-12));
209 }
210}