1#![allow(dead_code)]
7
8#[allow(dead_code)]
10#[derive(Debug, Clone, Copy)]
11pub struct Triangle2D {
12 pub a: [f32; 2],
13 pub b: [f32; 2],
14 pub c: [f32; 2],
15}
16
17#[allow(dead_code)]
18impl Triangle2D {
19 pub fn new(a: [f32; 2], b: [f32; 2], c: [f32; 2]) -> Self {
20 Self { a, b, c }
21 }
22
23 pub fn barycentric(&self, p: [f32; 2]) -> Option<[f32; 3]> {
26 let v0 = [self.b[0] - self.a[0], self.b[1] - self.a[1]];
27 let v1 = [self.c[0] - self.a[0], self.c[1] - self.a[1]];
28 let v2 = [p[0] - self.a[0], p[1] - self.a[1]];
29 let d00 = dot2(v0, v0);
30 let d01 = dot2(v0, v1);
31 let d11 = dot2(v1, v1);
32 let d20 = dot2(v2, v0);
33 let d21 = dot2(v2, v1);
34 let denom = d00 * d11 - d01 * d01;
35 if denom.abs() < 1e-10 {
36 return None;
37 }
38 let v = (d11 * d20 - d01 * d21) / denom;
39 let w = (d00 * d21 - d01 * d20) / denom;
40 let u = 1.0 - v - w;
41 Some([u, v, w])
42 }
43
44 pub fn contains(&self, p: [f32; 2]) -> bool {
46 match self.barycentric(p) {
47 None => false,
48 Some([u, v, w]) => u >= 0.0 && v >= 0.0 && w >= 0.0,
49 }
50 }
51
52 pub fn interpolate(&self, values: [f32; 3], bary: [f32; 3]) -> f32 {
54 values[0] * bary[0] + values[1] * bary[1] + values[2] * bary[2]
55 }
56
57 pub fn signed_area(&self) -> f32 {
59 let v0 = [self.b[0] - self.a[0], self.b[1] - self.a[1]];
60 let v1 = [self.c[0] - self.a[0], self.c[1] - self.a[1]];
61 0.5 * (v0[0] * v1[1] - v0[1] * v1[0])
62 }
63
64 pub fn area(&self) -> f32 {
65 self.signed_area().abs()
66 }
67
68 pub fn centroid(&self) -> [f32; 2] {
70 [
71 (self.a[0] + self.b[0] + self.c[0]) / 3.0,
72 (self.a[1] + self.b[1] + self.c[1]) / 3.0,
73 ]
74 }
75}
76
77fn dot2(a: [f32; 2], b: [f32; 2]) -> f32 {
78 a[0] * b[0] + a[1] * b[1]
79}
80
81#[allow(dead_code)]
83#[derive(Debug, Clone)]
84pub struct TriGrid {
85 pub origin: [f32; 2],
86 pub cell_size: f32,
87 pub cols: usize,
88 pub rows: usize,
89}
90
91#[allow(dead_code)]
92impl TriGrid {
93 pub fn new(origin: [f32; 2], cell_size: f32, cols: usize, rows: usize) -> Self {
94 Self {
95 origin,
96 cell_size,
97 cols,
98 rows,
99 }
100 }
101
102 pub fn cell_triangles(&self, col: usize, row: usize) -> [Triangle2D; 2] {
104 let s = self.cell_size;
105 let ox = self.origin[0] + col as f32 * s;
106 let oy = self.origin[1] + row as f32 * s;
107 let a = [ox, oy];
108 let b = [ox + s, oy];
109 let c = [ox + s, oy + s];
110 let d = [ox, oy + s];
111 [Triangle2D::new(a, b, d), Triangle2D::new(b, c, d)]
112 }
113
114 pub fn find_triangle(&self, p: [f32; 2]) -> Option<(usize, usize, usize)> {
116 let s = self.cell_size;
117 let col = ((p[0] - self.origin[0]) / s).floor() as i32;
118 let row = ((p[1] - self.origin[1]) / s).floor() as i32;
119 if col < 0 || row < 0 || col as usize >= self.cols || row as usize >= self.rows {
120 return None;
121 }
122 let tris = self.cell_triangles(col as usize, row as usize);
123 for (i, tri) in tris.iter().enumerate() {
124 if tri.contains(p) {
125 return Some((col as usize, row as usize, i));
126 }
127 }
128 None
129 }
130
131 pub fn triangle_count(&self) -> usize {
133 self.cols * self.rows * 2
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 fn unit_tri() -> Triangle2D {
142 Triangle2D::new([0.0, 0.0], [1.0, 0.0], [0.0, 1.0])
143 }
144
145 #[test]
146 fn centroid_inside() {
147 let t = unit_tri();
148 let c = t.centroid();
149 assert!(t.contains(c));
150 }
151
152 #[test]
153 fn barycentric_vertex_a() {
154 let t = unit_tri();
155 let [u, v, w] = t.barycentric([0.0, 0.0]).expect("should succeed");
156 assert!((u - 1.0).abs() < 1e-5);
157 assert!(v.abs() < 1e-5);
158 assert!(w.abs() < 1e-5);
159 }
160
161 #[test]
162 fn point_outside_not_contained() {
163 let t = unit_tri();
164 assert!(!t.contains([1.0, 1.0]));
165 }
166
167 #[test]
168 fn area_unit_tri() {
169 let t = unit_tri();
170 assert!((t.area() - 0.5).abs() < 1e-5);
171 }
172
173 #[test]
174 fn interpolate_at_vertex_a() {
175 let t = unit_tri();
176 let bary = t.barycentric([0.0, 0.0]).expect("should succeed");
177 let val = t.interpolate([3.0, 0.0, 0.0], bary);
178 assert!((val - 3.0).abs() < 1e-4);
179 }
180
181 #[test]
182 fn grid_triangle_count() {
183 let g = TriGrid::new([0.0, 0.0], 1.0, 4, 3);
184 assert_eq!(g.triangle_count(), 24);
185 }
186
187 #[test]
188 fn find_triangle_origin() {
189 let g = TriGrid::new([0.0, 0.0], 1.0, 4, 4);
190 assert!(g.find_triangle([0.1, 0.1]).is_some());
191 }
192
193 #[test]
194 fn find_triangle_outside_returns_none() {
195 let g = TriGrid::new([0.0, 0.0], 1.0, 4, 4);
196 assert!(g.find_triangle([-0.1, 0.5]).is_none());
197 }
198
199 #[test]
200 fn degenerate_barycentric_is_none() {
201 let t = Triangle2D::new([0.0, 0.0], [1.0, 0.0], [2.0, 0.0]);
202 assert!(t.barycentric([0.5, 0.0]).is_none());
203 }
204
205 #[test]
206 fn signed_area_ccw_positive() {
207 let t = unit_tri();
208 assert!(t.signed_area() > 0.0);
209 }
210}