1use fast_mesh_store::Mesh;
32
33use crate::FieldsError;
34use crate::Result;
35
36pub fn curvatures(mesh: &Mesh, out: &mut [f32]) -> Result<()> {
44 if out.len() != mesh.vertex_slots() {
45 return Err(FieldsError::InvalidInput(
46 "out must hold one entry per vertex slot",
47 ));
48 }
49 let slots = mesh.vertex_slots();
50 let mut neighbours: Vec<Vec<(u32, f64)>> = Vec::new();
54 neighbours
55 .try_reserve_exact(slots)
56 .map_err(|_| FieldsError::OutOfMemory)?;
57 neighbours.resize(slots, Vec::new());
58 let mut areas = vec![0.0f64; slots];
59 let mut normals = vec![[0.0f64; 3]; slots];
60 for t in 0..mesh.triangle_slots() as u32 {
61 let Some(tri) = mesh.triangle(t) else {
62 continue;
63 };
64 if !mesh.is_triangle_alive(t) {
65 continue;
66 }
67 let p = mesh.positions();
68 let [a, b, c] = tri.map(|v| p[v as usize]);
69 if ![a, b, c].iter().all(|p| p.iter().all(|c| c.is_finite())) {
70 continue;
71 }
72 let fa = a.map(f64::from);
73 let fb = b.map(f64::from);
74 let fc = c.map(f64::from);
75 let (ab, ac, bc) = (sub(fb, fa), sub(fc, fa), sub(fc, fb));
76 let n = cross(ab, ac);
77 let area2 = len(n);
78 if area2 <= 0.0 {
81 continue;
82 }
83 for v in tri {
84 areas[v as usize] += area2 / 6.0;
85 for k in 0..3 {
86 normals[v as usize][k] += n[k];
87 }
88 }
89 let ca = dot(ab, ac) / area2;
91 let cb = dot([-ab[0], -ab[1], -ab[2]], bc) / area2;
92 let cc = dot([-ac[0], -ac[1], -ac[2]], [-bc[0], -bc[1], -bc[2]]) / area2;
93 add(&mut neighbours, tri[0], tri[1], cc / 2.0)?;
94 add(&mut neighbours, tri[1], tri[2], ca / 2.0)?;
95 add(&mut neighbours, tri[2], tri[0], cb / 2.0)?;
96 }
97 for list in &mut neighbours {
98 list.sort_by_key(|&(v, _)| v);
99 }
100 let pos = mesh.positions();
101 for (v, o) in out.iter_mut().enumerate() {
102 let alive = mesh.is_vertex_alive(v as u32)
103 && mesh.corner_count(v as u32) > 0
104 && pos[v].iter().all(|c| c.is_finite());
105 let area = areas[v];
106 let n = normals[v];
107 let nlen = len(n);
108 if !alive || area <= 0.0 || nlen <= 0.0 {
109 *o = f32::NAN;
110 continue;
111 }
112 let x = pos[v].map(f64::from);
113 let mut lap = [0.0; 3];
114 for &(u, w) in &neighbours[v] {
115 let y = pos[u as usize].map(f64::from);
116 for k in 0..3 {
117 lap[k] += w * (y[k] - x[k]);
118 }
119 }
120 let proj = (lap[0] * n[0] + lap[1] * n[1] + lap[2] * n[2]) / nlen;
121 *o = (-proj / (2.0 * area)) as f32;
122 }
123 Ok(())
124}
125
126fn add(neighbours: &mut [Vec<(u32, f64)>], a: u32, b: u32, w: f64) -> Result<()> {
128 for (v, u) in [(a, b), (b, a)] {
129 let list = &mut neighbours[v as usize];
130 if let Some(slot) = list.iter_mut().find(|(x, _)| *x == u) {
131 slot.1 += w;
132 } else {
133 list.try_reserve(1).map_err(|_| FieldsError::OutOfMemory)?;
134 list.push((u, w));
135 }
136 }
137 Ok(())
138}
139
140fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
141 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
142}
143
144fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
145 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
146}
147
148fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
149 [
150 a[1] * b[2] - a[2] * b[1],
151 a[2] * b[0] - a[0] * b[2],
152 a[0] * b[1] - a[1] * b[0],
153 ]
154}
155
156fn len(a: [f64; 3]) -> f64 {
157 dot(a, a).sqrt()
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::test_util::*;
164
165 fn run(mesh: &Mesh) -> Vec<f32> {
166 let mut out = vec![f32::NAN; mesh.vertex_slots()];
167 curvatures(mesh, &mut out).unwrap();
168 out
169 }
170
171 fn plane(n: u32, h: f32) -> Mesh {
173 let mut positions = Vec::new();
174 for y in 0..=n {
175 for x in 0..=n {
176 positions.push([x as f32 * h, y as f32 * h, 0.0]);
177 }
178 }
179 let mut tris = Vec::new();
180 for y in 0..n {
181 for x in 0..n {
182 let a = y * (n + 1) + x;
183 tris.extend([[a, a + 1, a + n + 2], [a, a + n + 2, a + n + 1]]);
184 }
185 }
186 mesh(&positions, &tris)
187 }
188
189 fn saddle(n: u32, s: f32) -> Mesh {
193 let mut positions = Vec::new();
194 for y in 0..=n {
195 for x in 0..=n {
196 let (fx, fy) = (
197 x as f32 / n as f32 * 2.0 - 1.0,
198 y as f32 / n as f32 * 2.0 - 1.0,
199 );
200 positions.push([fx, fy, (2.0 * fx * fx - fy * fy) / s]);
201 }
202 }
203 let mut tris = Vec::new();
204 for y in 0..n {
205 for x in 0..n {
206 let a = y * (n + 1) + x;
207 tris.extend([[a, a + 1, a + n + 2], [a, a + n + 2, a + n + 1]]);
208 }
209 }
210 mesh(&positions, &tris)
211 }
212
213 #[test]
214 fn sphere_reads_one_over_r() {
215 let m = icosphere();
216 let out = run(&m);
217 assert_eq!(out.len(), m.vertex_slots());
218 for &v in &out {
219 assert!(v.is_finite(), "{out:?}");
220 assert!((v - 1.0).abs() < 0.2, "{v}");
221 }
222 let (p, t) = (m.positions().to_vec(), m.triangles().to_vec());
225 let big = mesh(
226 &p.iter()
227 .map(|q| [q[0] * 2.0, q[1] * 2.0, q[2] * 2.0])
228 .collect::<Vec<_>>(),
229 &t,
230 );
231 for (&v, &w) in out.iter().zip(run(&big).iter()) {
232 assert!((w - v / 2.0).abs() < 1e-6, "{w} vs {v}");
233 }
234 }
235
236 #[test]
237 fn plane_reads_zero() {
238 let m = plane(4, 0.25);
239 let out = run(&m);
240 for &v in &out {
241 assert!(v.is_finite(), "{out:?}");
242 assert!(v.abs() < 1e-5, "{v}");
243 }
244 }
245
246 #[test]
247 fn saddle_reads_negative() {
248 let m = saddle(32, 4.0);
251 let c = (16 * 33 + 16) as usize;
252 let out = run(&m);
253 assert!((out[c] + 0.25).abs() < 0.1, "{:?}", out[c]);
254 assert!(out.iter().all(|v| v.is_finite()));
255 assert!(out.iter().any(|&v| v < -0.1));
256 }
257
258 #[test]
259 fn cube_corners_read_positive() {
260 let m = cube();
262 for &v in &run(&m) {
263 assert!(v.is_finite(), "{v}");
264 assert!(v > 0.3, "{v}");
265 }
266 }
267
268 #[test]
269 fn flipped_winding_negates() {
270 let m = icosphere();
272 let t = m
273 .triangles()
274 .iter()
275 .map(|&[a, b, c]| [a, c, b])
276 .collect::<Vec<_>>();
277 let flipped = mesh(m.positions(), &t);
278 let (a, b) = (run(&m), run(&flipped));
279 for (&x, &y) in a.iter().zip(&b) {
280 assert!((x + y).abs() < 1e-6, "{x} vs {y}");
281 }
282 }
283
284 #[test]
285 fn dead_and_unused_slots_read_nan() {
286 let (mut positions, tris) = cube_buffers();
287 positions.push([5.0, 5.0, 5.0]);
288 let m = mesh(&positions, &tris);
289 assert_eq!(m.vertex_slots(), 9);
290 assert!(m.is_vertex_alive(8));
291 assert_eq!(m.corner_count(8), 0);
292 let mut out = vec![7.0f32; m.vertex_slots()];
293 curvatures(&m, &mut out).unwrap();
294 assert!(out[8].is_nan(), "{out:?}");
295 assert!(out[..8].iter().all(|v| v.is_finite()), "{out:?}");
296 }
297
298 #[test]
299 fn invalid_input_writes_nothing() {
300 let m = cube();
301 let mut out = vec![7.0f32; m.vertex_slots()];
302 assert!(matches!(
303 curvatures(&m, &mut out[..4]),
304 Err(FieldsError::InvalidInput(_))
305 ));
306 assert_eq!(out, vec![7.0; m.vertex_slots()]);
307 }
308}