1use fast_mesh_store::{Mesh, Transaction, TriangleId, VertexId};
8
9use crate::topology::{
10 EdgeMap, components, cross, dot, point, proper_triangle, sub, triangle_area,
11};
12use crate::{Result, filled, push, reserve};
13
14const NONE: u32 = u32::MAX;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub struct OrientReport {
19 pub flipped_triangles: usize,
21 pub patches: usize,
23 pub non_orientable_patches: usize,
25 pub flipped_outward_components: usize,
27}
28
29struct Patch {
31 start: usize,
33 end: usize,
34 orientable: bool,
36 area: [f64; 2],
38}
39
40pub fn orient(tx: &mut Transaction) -> Result<OrientReport> {
64 let mesh = tx.mesh();
65 let edges = EdgeMap::build(mesh)?;
66 let slots = mesh.triangle_slots();
67 let mut patch_of = filled(slots, NONE)?;
68 let mut parity = filled(slots, false)?;
69 let mut order: Vec<TriangleId> = Vec::new();
71 reserve(&mut order, mesh.triangle_count())?;
72 let mut patches: Vec<Patch> = Vec::new();
73 for seed in 0..slots as TriangleId {
74 if patch_of[seed as usize] != NONE || proper_triangle(mesh, seed).is_none() {
75 continue;
76 }
77 let index = patches.len() as u32;
78 let start = order.len();
79 let mut orientable = true;
80 patch_of[seed as usize] = index;
81 order.push(seed);
82 let mut head = start;
83 while head < order.len() {
84 let u = order[head];
85 head += 1;
86 let [x, y, z] = mesh.triangles()[u as usize];
87 for (a, b) in [(x, y), (y, z), (z, x)] {
88 let &[s, t] = edges.triangles(a, b) else {
89 continue;
90 };
91 let w = if s == u { t } else { s };
92 let want = parity[u as usize] ^ runs_forward(mesh, w, a, b);
95 if patch_of[w as usize] == NONE {
96 patch_of[w as usize] = index;
97 parity[w as usize] = want;
98 order.push(w);
99 } else if parity[w as usize] != want {
100 orientable = false;
101 }
102 }
103 }
104 let mut area = [0.0; 2];
105 for &t in &order[start..] {
106 let tri = mesh.triangles()[t as usize];
107 area[usize::from(parity[t as usize])] += triangle_area(mesh, tri);
108 }
109 push(
110 &mut patches,
111 Patch {
112 start,
113 end: order.len(),
114 orientable,
115 area,
116 },
117 )?;
118 }
119
120 let mut flip = filled(slots, false)?;
121 for p in patches.iter().filter(|p| p.orientable) {
122 let odd_wins = p.area[1] > p.area[0];
124 for &t in &order[p.start..p.end] {
125 flip[t as usize] = parity[t as usize] != odd_wins;
126 }
127 }
128
129 let mut report = OrientReport {
130 patches: patches.len(),
131 non_orientable_patches: patches.iter().filter(|p| !p.orientable).count(),
132 ..OrientReport::default()
133 };
134 for c in components(mesh, &edges)? {
135 let orientable = c
138 .triangles
139 .iter()
140 .all(|&t| patches[patch_of[t as usize] as usize].orientable);
141 if c.closed && orientable && signed_volume(mesh, &c.triangles, &flip) < 0.0 {
142 for &t in &c.triangles {
143 flip[t as usize] = !flip[t as usize];
144 }
145 report.flipped_outward_components += 1;
146 }
147 }
148
149 let count = flip.iter().filter(|&&f| f).count();
150 let (mut ids, mut flipped) = (Vec::new(), Vec::new());
151 reserve(&mut ids, count)?;
152 reserve(&mut flipped, count)?;
153 for t in 0..slots as TriangleId {
154 if flip[t as usize] {
155 let [a, b, c] = mesh.triangles()[t as usize];
156 ids.push(t);
157 flipped.push([a, c, b]);
158 }
159 }
160 report.flipped_triangles = count;
161 tx.set_triangles(&ids, &flipped)?;
162 Ok(report)
163}
164
165fn runs_forward(mesh: &Mesh, t: TriangleId, a: VertexId, b: VertexId) -> bool {
167 let [x, y, z] = mesh.triangles()[t as usize];
168 (x, y) == (a, b) || (y, z) == (a, b) || (z, x) == (a, b)
169}
170
171fn signed_volume(mesh: &Mesh, triangles: &[TriangleId], flip: &[bool]) -> f64 {
175 let Some(&first) = triangles.first() else {
176 return 0.0;
177 };
178 let o = point(mesh, mesh.triangles()[first as usize][0]);
179 let mut sum = 0.0;
180 for &t in triangles {
181 let [a, mut b, mut c] = mesh.triangles()[t as usize];
182 if flip[t as usize] {
183 std::mem::swap(&mut b, &mut c);
184 }
185 let [pa, pb, pc] = [a, b, c].map(|v| sub(point(mesh, v), o));
186 sum += dot(pa, cross(pb, pc));
187 }
188 sum
189}
190
191#[cfg(test)]
192mod tests {
193 use fast_mesh_store::{Mesh, TriangleId};
194
195 use super::*;
196 use crate::analyze::{AnalyzeOptions, analyze};
197 use crate::boundary_loops;
198 use crate::test_util::*;
199
200 fn conflicts(mesh: &Mesh) -> usize {
201 analyze(mesh, &AnalyzeOptions::default())
202 .unwrap()
203 .orientation_conflicts
204 }
205
206 fn corners(mesh: &Mesh, t: TriangleId) -> [[f64; 3]; 3] {
208 mesh.triangle(t)
209 .unwrap()
210 .map(|v| mesh.positions()[v as usize].map(f64::from))
211 }
212
213 fn facing(mesh: &Mesh, t: TriangleId) -> f64 {
216 let [a, b, c] = corners(mesh, t);
217 let (u, v) = (
218 std::array::from_fn::<f64, 3, _>(|k| b[k] - a[k]),
219 std::array::from_fn::<f64, 3, _>(|k| c[k] - a[k]),
220 );
221 let n = [
222 u[1] * v[2] - u[2] * v[1],
223 u[2] * v[0] - u[0] * v[2],
224 u[0] * v[1] - u[1] * v[0],
225 ];
226 (0..3).map(|k| n[k] * (a[k] + b[k] + c[k])).sum()
227 }
228
229 fn volume(mesh: &Mesh) -> f64 {
231 let o = mesh.positions()[0].map(f64::from);
232 let mut sum = 0.0;
233 for t in 0..mesh.triangle_slots() as TriangleId {
234 if mesh.triangle(t).is_none() {
235 continue;
236 }
237 let [a, b, c] =
238 corners(mesh, t).map(|p| std::array::from_fn::<f64, 3, _>(|k| p[k] - o[k]));
239 sum += a[0] * (b[1] * c[2] - b[2] * c[1]) - a[1] * (b[0] * c[2] - b[2] * c[0])
240 + a[2] * (b[0] * c[1] - b[1] * c[0]);
241 }
242 sum / 6.0
243 }
244
245 fn scattered(n: u32, count: usize) -> Vec<TriangleId> {
247 let mut out = Vec::new();
248 let mut x: u64 = 12345;
249 while out.len() < count {
250 x = x
251 .wrapping_mul(6364136223846793005)
252 .wrapping_add(1442695040888963407);
253 let t = ((x >> 33) % u64::from(n)) as TriangleId;
254 if !out.contains(&t) {
255 out.push(t);
256 }
257 }
258 out.sort_unstable();
259 out
260 }
261
262 #[test]
263 fn hemisphere_random_flips_fixed() {
264 let clean = hemisphere(8, 24, 1.0);
265 assert_eq!(clean.triangle_count(), 360);
266 let random = scattered(360, 4);
267 let patch: Vec<TriangleId> = (0..30).collect();
268 for flips in [random, patch] {
269 let mut mesh = flip(&clean, &flips);
270 assert!(conflicts(&mesh) > 0);
271 let (r, _) = edit(&mut mesh, orient);
272 assert_eq!(
273 r,
274 OrientReport {
275 flipped_triangles: flips.len(),
276 patches: 1,
277 non_orientable_patches: 0,
278 flipped_outward_components: 0,
279 }
280 );
281 assert_eq!(conflicts(&mesh), 0);
282 for t in 0..360 {
283 assert!(facing(&mesh, t) > 0.0, "triangle {t} faces inward");
284 }
285 assert_eq!(parts(&mesh), parts(&clean));
286 let (_, empty) = edit(&mut mesh, orient);
287 assert!(empty, "a second run writes nothing");
288 }
289 }
290
291 #[test]
292 fn inward_sphere_turned_outward() {
293 let clean = uv_sphere(8, 12, 1.0);
294 let n = clean.triangle_count();
295 let all: Vec<TriangleId> = (0..n as TriangleId).collect();
296 let mut mesh = flip(&clean, &all);
297 assert!(volume(&mesh) < 0.0);
298 let (r, _) = edit(&mut mesh, orient);
299 assert_eq!(
300 r,
301 OrientReport {
302 flipped_triangles: n,
303 patches: 1,
304 non_orientable_patches: 0,
305 flipped_outward_components: 1,
306 }
307 );
308 assert!(volume(&mesh) > 0.0);
309 assert_eq!(parts(&mesh), parts(&clean));
310
311 let strays = scattered(n as u32, 5);
314 let inward: Vec<TriangleId> = all
315 .iter()
316 .copied()
317 .filter(|t| !strays.contains(t))
318 .collect();
319 let mut mesh = flip(&clean, &inward);
320 let (r, _) = edit(&mut mesh, orient);
321 assert_eq!(
322 (r.flipped_triangles, r.flipped_outward_components),
323 (n - 5, 1)
324 );
325 assert_eq!(parts(&mesh), parts(&clean));
326
327 let far = box_mesh([1e5; 3], [1e5 + 1.0; 3]);
329 let mut mesh = flip(&far, &(0..12).collect::<Vec<_>>());
330 let (r, _) = edit(&mut mesh, orient);
331 assert_eq!((r.flipped_triangles, r.flipped_outward_components), (12, 1));
332 assert_eq!(parts(&mesh), parts(&far));
333 }
334
335 #[test]
336 fn mobius_reported_unchanged() {
337 let mut mesh = mobius(12);
338 let before = parts(&mesh);
339 let (r, empty) = edit(&mut mesh, orient);
340 assert_eq!(
341 r,
342 OrientReport {
343 flipped_triangles: 0,
344 patches: 1,
345 non_orientable_patches: 1,
346 flipped_outward_components: 0,
347 }
348 );
349 assert!(empty);
350 assert_eq!(parts(&mesh), before);
351
352 let clean = combine(&[&mobius(12), &hemisphere(8, 24, 1.0)]);
354 let mut mesh = flip(&clean, &[30, 100, 101, 250]);
355 let (r, _) = edit(&mut mesh, orient);
356 assert_eq!(
357 (r.flipped_triangles, r.patches, r.non_orientable_patches),
358 (4, 2, 1)
359 );
360 assert_eq!(parts(&mesh), parts(&clean));
361 }
362
363 #[test]
364 fn klein_bottle_skips_outward_pass_unchanged() {
365 let mut mesh = klein(16, 12);
368 assert!(boundary_loops(&mesh).unwrap().is_empty());
369 let a = analyze(&mesh, &AnalyzeOptions::default()).unwrap();
370 assert_eq!(a.non_manifold_edges, 0);
371 assert_eq!(a.components.len(), 1);
372 assert!(a.components[0].closed);
373 let before = parts(&mesh);
374 let (r, empty) = edit(&mut mesh, orient);
375 assert_eq!(
376 r,
377 OrientReport {
378 flipped_triangles: 0,
379 patches: 1,
380 non_orientable_patches: 1,
381 flipped_outward_components: 0,
382 }
383 );
384 assert!(empty);
385 assert_eq!(parts(&mesh), before);
386 }
387
388 #[test]
389 fn fin_patches_independent() {
390 let clean = fin();
394 let mut mesh = flip(&clean, &[1, 2, 4, 5]);
395 let (r, _) = edit(&mut mesh, orient);
396 assert_eq!(
397 r,
398 OrientReport {
399 flipped_triangles: 2,
400 patches: 3,
401 non_orientable_patches: 0,
402 flipped_outward_components: 0,
403 }
404 );
405 assert_eq!(conflicts(&mesh), 0);
406 let expected = flip(&clean, &[2, 3, 4, 5]);
407 assert_eq!(parts(&mesh), parts(&expected));
408 }
409
410 #[test]
411 fn already_consistent_is_empty_patch() {
412 for clean in [uv_sphere(8, 12, 1.0), hemisphere(4, 8, 1.0), grid(3), fin()] {
413 let mut mesh = clean;
414 let (r, empty) = edit(&mut mesh, orient);
415 assert_eq!(r.flipped_triangles, 0);
416 assert!(empty);
417 }
418 }
419
420 #[test]
421 fn damaged_sphere_flips_fixed_and_degenerates_skipped() {
422 let mut mesh = damaged_sphere();
423 let before = parts(&mesh);
424 let (r, _) = edit(&mut mesh, orient);
425 assert_eq!(r.flipped_triangles, 5);
428 assert_eq!(
429 (r.non_orientable_patches, r.flipped_outward_components),
430 (0, 0)
431 );
432 assert_eq!(conflicts(&mesh), 0);
433 assert_eq!(mesh.triangle(166), before.1[166].into());
434 assert_eq!(mesh.triangle(167), before.1[167].into());
435 }
436
437 #[test]
438 fn deterministic() {
439 let damaged = || flip(&hemisphere(8, 24, 1.0), &scattered(360, 40));
440 let (mut a, mut b) = (damaged(), damaged());
441 let (ra, _) = edit(&mut a, orient);
442 let (rb, _) = edit(&mut b, orient);
443 assert_eq!(ra, rb);
444 assert_eq!(parts(&a), parts(&b));
445 }
446
447 #[test]
448 fn out_of_memory_is_reported_and_writes_nothing() {
449 assert!(sweep(damaged_sphere(), |tx| orient(tx).map(drop)) > 5);
450 let clean = uv_sphere(8, 12, 1.0);
451 let inward = flip(
452 &clean,
453 &(0..clean.triangle_count() as TriangleId).collect::<Vec<_>>(),
454 );
455 assert!(sweep(inward, |tx| orient(tx).map(drop)) > 5);
456 }
457}