1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct FaceNormalExport {
11 pub normals: Vec<[f32; 3]>,
12}
13
14#[allow(dead_code)]
16pub fn compute_face_normal_fn(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] {
17 let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
18 let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
19 let n = [
20 ab[1] * ac[2] - ab[2] * ac[1],
21 ab[2] * ac[0] - ab[0] * ac[2],
22 ab[0] * ac[1] - ab[1] * ac[0],
23 ];
24 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
25 if len < 1e-12 {
26 [0.0, 0.0, 1.0]
27 } else {
28 [n[0] / len, n[1] / len, n[2] / len]
29 }
30}
31
32#[allow(dead_code)]
34pub fn export_face_normals(positions: &[[f32; 3]], indices: &[u32]) -> FaceNormalExport {
35 let face_count = indices.len() / 3;
36 let mut normals = Vec::with_capacity(face_count);
37 for f in 0..face_count {
38 let i0 = indices[f * 3] as usize;
39 let i1 = indices[f * 3 + 1] as usize;
40 let i2 = indices[f * 3 + 2] as usize;
41 normals.push(compute_face_normal_fn(
42 positions[i0],
43 positions[i1],
44 positions[i2],
45 ));
46 }
47 FaceNormalExport { normals }
48}
49
50#[allow(dead_code)]
52pub fn fn_face_count(e: &FaceNormalExport) -> usize {
53 e.normals.len()
54}
55
56#[allow(dead_code)]
58pub fn get_face_normal(e: &FaceNormalExport, idx: usize) -> Option<[f32; 3]> {
59 e.normals.get(idx).copied()
60}
61
62#[allow(dead_code)]
64pub fn avg_face_normal(e: &FaceNormalExport) -> [f32; 3] {
65 if e.normals.is_empty() {
66 return [0.0; 3];
67 }
68 let n = e.normals.len() as f32;
69 let sum = e.normals.iter().fold([0.0f32; 3], |acc, &n| {
70 [acc[0] + n[0], acc[1] + n[1], acc[2] + n[2]]
71 });
72 [sum[0] / n, sum[1] / n, sum[2] / n]
73}
74
75#[allow(dead_code)]
77pub fn validate_face_normals(e: &FaceNormalExport, tol: f32) -> bool {
78 e.normals.iter().all(|&n| {
79 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
80 (len - 1.0).abs() < tol
81 })
82}
83
84#[allow(dead_code)]
86pub fn face_normals_to_csv(e: &FaceNormalExport) -> String {
87 let mut s = "face,nx,ny,nz\n".to_string();
88 for (i, &n) in e.normals.iter().enumerate() {
89 s.push_str(&format!("{},{:.6},{:.6},{:.6}\n", i, n[0], n[1], n[2]));
90 }
91 s
92}
93
94#[allow(dead_code)]
96pub fn face_normal_to_json(e: &FaceNormalExport) -> String {
97 format!("{{\"face_count\":{}}}", fn_face_count(e))
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 fn unit_tri_up() -> ([f32; 3], [f32; 3], [f32; 3]) {
105 ([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0])
106 }
107
108 #[test]
109 fn test_compute_face_normal_up() {
110 let (a, b, c) = unit_tri_up();
111 let n = compute_face_normal_fn(a, b, c);
112 assert!((n[2] - 1.0).abs() < 1e-5);
113 }
114
115 #[test]
116 fn test_compute_face_normal_degenerate() {
117 let n = compute_face_normal_fn([0.0; 3], [0.0; 3], [0.0; 3]);
118 assert!((n[2] - 1.0).abs() < 1e-5);
120 }
121
122 #[test]
123 fn test_export_face_normals_empty() {
124 let e = export_face_normals(&[], &[]);
125 assert_eq!(fn_face_count(&e), 0);
126 }
127
128 #[test]
129 fn test_export_face_normals_single() {
130 let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
131 let idx = vec![0u32, 1, 2];
132 let e = export_face_normals(&pos, &idx);
133 assert_eq!(fn_face_count(&e), 1);
134 }
135
136 #[test]
137 fn test_get_face_normal() {
138 let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
139 let idx = vec![0u32, 1, 2];
140 let e = export_face_normals(&pos, &idx);
141 assert!(get_face_normal(&e, 0).is_some());
142 assert!(get_face_normal(&e, 99).is_none());
143 }
144
145 #[test]
146 fn test_validate_face_normals() {
147 let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
148 let idx = vec![0u32, 1, 2];
149 let e = export_face_normals(&pos, &idx);
150 assert!(validate_face_normals(&e, 1e-4));
151 }
152
153 #[test]
154 fn test_avg_face_normal_single() {
155 let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
156 let idx = vec![0u32, 1, 2];
157 let e = export_face_normals(&pos, &idx);
158 let avg = avg_face_normal(&e);
159 assert!((avg[2] - 1.0).abs() < 1e-5);
160 }
161
162 #[test]
163 fn test_face_normals_to_csv() {
164 let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
165 let idx = vec![0u32, 1, 2];
166 let e = export_face_normals(&pos, &idx);
167 let csv = face_normals_to_csv(&e);
168 assert!(csv.contains("face,nx,ny,nz"));
169 }
170
171 #[test]
172 fn test_face_normal_to_json() {
173 let e = export_face_normals(&[], &[]);
174 let j = face_normal_to_json(&e);
175 assert!(j.contains("\"face_count\":0"));
176 }
177
178 #[test]
179 fn test_normals_unit_length() {
180 let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
181 let idx = vec![0u32, 1, 2];
182 let e = export_face_normals(&pos, &idx);
183 let n = e.normals[0];
184 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
185 assert!((len - 1.0).abs() < 1e-5);
186 }
187}