1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy)]
10pub struct EdgeNormalEntry {
11 pub edge_v0: u32,
12 pub edge_v1: u32,
13 pub normal: [f32; 3],
14}
15
16#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct EdgeNormalExport {
20 pub entries: Vec<EdgeNormalEntry>,
21}
22
23#[allow(dead_code)]
25pub fn new_edge_normal_export() -> EdgeNormalExport {
26 EdgeNormalExport {
27 entries: Vec::new(),
28 }
29}
30
31#[allow(dead_code)]
33pub fn add_edge_normal(export: &mut EdgeNormalExport, entry: EdgeNormalEntry) {
34 export.entries.push(entry);
35}
36
37#[allow(dead_code)]
39pub fn edge_normal_count(export: &EdgeNormalExport) -> usize {
40 export.entries.len()
41}
42
43#[allow(dead_code)]
45pub fn normalize_en(v: [f32; 3]) -> [f32; 3] {
46 let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
47 if len < 1e-12 {
48 return [0.0, 1.0, 0.0];
49 }
50 [v[0] / len, v[1] / len, v[2] / len]
51}
52
53#[allow(dead_code)]
55pub fn normals_unit_en(export: &EdgeNormalExport) -> bool {
56 export.entries.iter().all(|e| {
57 let len2 =
58 e.normal[0] * e.normal[0] + e.normal[1] * e.normal[1] + e.normal[2] * e.normal[2];
59 (len2 - 1.0).abs() < 1e-4
60 })
61}
62
63#[allow(dead_code)]
65pub fn avg_edge_normal(export: &EdgeNormalExport) -> [f32; 3] {
66 let n = export.entries.len();
67 if n == 0 {
68 return [0.0, 1.0, 0.0];
69 }
70 let mut sum = [0.0f32; 3];
71 for e in &export.entries {
72 sum[0] += e.normal[0];
73 sum[1] += e.normal[1];
74 sum[2] += e.normal[2];
75 }
76 normalize_en([sum[0] / n as f32, sum[1] / n as f32, sum[2] / n as f32])
77}
78
79#[allow(dead_code)]
81pub fn find_edge_normal(export: &EdgeNormalExport, v0: u32, v1: u32) -> Option<[f32; 3]> {
82 export
83 .entries
84 .iter()
85 .find(|e| (e.edge_v0 == v0 && e.edge_v1 == v1) || (e.edge_v0 == v1 && e.edge_v1 == v0))
86 .map(|e| e.normal)
87}
88
89#[allow(dead_code)]
91pub fn compute_from_mesh(positions: &[[f32; 3]], indices: &[u32]) -> EdgeNormalExport {
92 let tri_count = indices.len() / 3;
93 let mut edge_accum: std::collections::HashMap<(u32, u32), ([f32; 3], u32)> =
94 std::collections::HashMap::new();
95 for t in 0..tri_count {
96 let i0 = indices[t * 3];
97 let i1 = indices[t * 3 + 1];
98 let i2 = indices[t * 3 + 2];
99 if i0 as usize >= positions.len()
100 || i1 as usize >= positions.len()
101 || i2 as usize >= positions.len()
102 {
103 continue;
104 }
105 let p0 = positions[i0 as usize];
106 let p1 = positions[i1 as usize];
107 let p2 = positions[i2 as usize];
108 let e1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
109 let e2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
110 let n = normalize_en([
111 e1[1] * e2[2] - e1[2] * e2[1],
112 e1[2] * e2[0] - e1[0] * e2[2],
113 e1[0] * e2[1] - e1[1] * e2[0],
114 ]);
115 for (a, b) in [(i0, i1), (i1, i2), (i2, i0)] {
116 let key = if a < b { (a, b) } else { (b, a) };
117 let acc = edge_accum.entry(key).or_insert(([0.0; 3], 0));
118 acc.0[0] += n[0];
119 acc.0[1] += n[1];
120 acc.0[2] += n[2];
121 acc.1 += 1;
122 }
123 }
124 let mut entries = Vec::new();
125 for ((v0, v1), (sum, cnt)) in edge_accum {
126 let avg = normalize_en([
127 sum[0] / cnt as f32,
128 sum[1] / cnt as f32,
129 sum[2] / cnt as f32,
130 ]);
131 entries.push(EdgeNormalEntry {
132 edge_v0: v0,
133 edge_v1: v1,
134 normal: avg,
135 });
136 }
137 EdgeNormalExport { entries }
138}
139
140#[allow(dead_code)]
142pub fn edge_normal_to_json(export: &EdgeNormalExport) -> String {
143 format!("{{\"entry_count\":{}}}", export.entries.len())
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 fn tri_export() -> EdgeNormalExport {
151 compute_from_mesh(
152 &[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
153 &[0, 1, 2],
154 )
155 }
156
157 #[test]
158 fn test_compute_from_mesh_edge_count() {
159 let e = tri_export();
160 assert_eq!(edge_normal_count(&e), 3);
161 }
162
163 #[test]
164 fn test_normals_unit() {
165 let e = tri_export();
166 assert!(normals_unit_en(&e));
167 }
168
169 #[test]
170 fn test_add_and_count() {
171 let mut e = new_edge_normal_export();
172 add_edge_normal(
173 &mut e,
174 EdgeNormalEntry {
175 edge_v0: 0,
176 edge_v1: 1,
177 normal: [0.0, 1.0, 0.0],
178 },
179 );
180 assert_eq!(edge_normal_count(&e), 1);
181 }
182
183 #[test]
184 fn test_find_edge_normal_found() {
185 let mut e = new_edge_normal_export();
186 add_edge_normal(
187 &mut e,
188 EdgeNormalEntry {
189 edge_v0: 0,
190 edge_v1: 1,
191 normal: [0.0, 1.0, 0.0],
192 },
193 );
194 assert!(find_edge_normal(&e, 0, 1).is_some());
195 }
196
197 #[test]
198 fn test_find_edge_normal_reversed() {
199 let mut e = new_edge_normal_export();
200 add_edge_normal(
201 &mut e,
202 EdgeNormalEntry {
203 edge_v0: 0,
204 edge_v1: 1,
205 normal: [0.0, 1.0, 0.0],
206 },
207 );
208 assert!(find_edge_normal(&e, 1, 0).is_some());
209 }
210
211 #[test]
212 fn test_avg_normal_single() {
213 let mut e = new_edge_normal_export();
214 add_edge_normal(
215 &mut e,
216 EdgeNormalEntry {
217 edge_v0: 0,
218 edge_v1: 1,
219 normal: [0.0, 1.0, 0.0],
220 },
221 );
222 let avg = avg_edge_normal(&e);
223 assert!((avg[1] - 1.0).abs() < 1e-5);
224 }
225
226 #[test]
227 fn test_normalize_en_unit_vector() {
228 let n = normalize_en([3.0, 0.0, 0.0]);
229 assert!((n[0] - 1.0).abs() < 1e-5);
230 }
231
232 #[test]
233 fn test_edge_normal_to_json() {
234 let e = tri_export();
235 let j = edge_normal_to_json(&e);
236 assert!(j.contains("entry_count"));
237 }
238
239 #[test]
240 fn test_empty_mesh_zero_edges() {
241 let e = compute_from_mesh(&[], &[]);
242 assert_eq!(edge_normal_count(&e), 0);
243 }
244}