brep_kernel/meshing/watertight_tessellation/
orient.rs1use super::*;
2
3pub fn tessellate_brep_watertight(solid: &BrepSolid, chord_tolerance: f64) -> Result<Mesh, String> {
6 let mut face_shell_signs = Vec::new();
12 if solid.shells.len() == 1 {
13 face_shell_signs.resize(solid.shells[0].faces.len(), 1_i8);
14 } else {
15 for shell in &solid.shells {
16 let volume = crate::mass_properties::shell_signed_volume(shell)?;
17 let sign = if volume < 0.0 { -1_i8 } else { 1_i8 };
18 face_shell_signs.resize(face_shell_signs.len() + shell.faces.len(), sign);
19 }
20 }
21 let mut mesh = tessellate_brep_watertight_face_stride(solid, chord_tolerance, 1, 0)?;
24 orient_mesh_coherently(&mut mesh, chord_tolerance, &face_shell_signs)?;
32 mesh.validate()?;
33 Ok(mesh)
34}
35
36pub(super) fn tri_geometric_normal(positions: &[f64], indices: &[u32], t: usize) -> [f64; 3] {
39 let p = |i: usize| {
40 let i = i * 3;
41 [positions[i], positions[i + 1], positions[i + 2]]
42 };
43 let a = p(indices[3 * t] as usize);
44 let b = p(indices[3 * t + 1] as usize);
45 let c = p(indices[3 * t + 2] as usize);
46 let e1 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
47 let e2 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
48 [
49 e1[1] * e2[2] - e1[2] * e2[1],
50 e1[2] * e2[0] - e1[0] * e2[2],
51 e1[0] * e2[1] - e1[1] * e2[0],
52 ]
53}
54
55pub(super) fn tetra_signed_volume(positions: &[f64], indices: &[u32], t: usize) -> f64 {
59 let p = |i: usize| {
60 let i = i * 3;
61 [positions[i], positions[i + 1], positions[i + 2]]
62 };
63 let a = p(indices[3 * t] as usize);
64 let b = p(indices[3 * t + 1] as usize);
65 let c = p(indices[3 * t + 2] as usize);
66 (a[0] * (b[1] * c[2] - b[2] * c[1]) - a[1] * (b[0] * c[2] - b[2] * c[0])
67 + a[2] * (b[0] * c[1] - b[1] * c[0]))
68 / 6.0
69}
70
71pub(crate) fn orient_mesh_coherently(
85 mesh: &mut Mesh,
86 chord: f64,
87 face_shell_signs: &[i8],
88) -> Result<(), String> {
89 let tri_count = mesh.indices.len() / 3;
90 if tri_count == 0 {
91 return Ok(());
92 }
93 let vcount = mesh.positions.len() / 3;
94 if vcount == 0 {
95 return Ok(());
96 }
97 let _timer = std::env::var("BREP_TIME_ORIENT")
98 .ok()
99 .map(|_| web_time::Instant::now());
100
101 let mut lo = [f64::INFINITY; 3];
104 let mut hi = [f64::NEG_INFINITY; 3];
105 for i in 0..vcount {
106 for k in 0..3 {
107 let x = mesh.positions[3 * i + k];
108 if x < lo[k] {
109 lo[k] = x;
110 }
111 if x > hi[k] {
112 hi[k] = x;
113 }
114 }
115 }
116 let diag = {
117 let d = [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]];
118 (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
119 };
120 if !(diag > 0.0) {
121 return Ok(());
122 }
123 let mut exact: FxHashMap<[u64; 3], u32> = FxHashMap::default();
127 let mut welded = Vec::with_capacity(vcount);
128 for point in mesh.positions.chunks_exact(3) {
129 if point.iter().any(|x| !x.is_finite()) {
130 return Err("orient_mesh_coherently: nonfinite vertex".into());
131 }
132 let key = std::array::from_fn(|i| {
133 if point[i] == 0. {
134 0
135 } else {
136 point[i].to_bits()
137 }
138 });
139 let next = exact.len() as u32;
140 welded.push(*exact.entry(key).or_insert(next));
141 }
142 let mut edge_tris: FxHashMap<(u32, u32), Vec<(u32, bool)>> = FxHashMap::default();
143 edge_tris.reserve(tri_count * 3);
144 for (t, triangle) in mesh.indices.chunks_exact(3).enumerate() {
145 for i in 0..3 {
146 let a = welded[triangle[i] as usize];
147 let b = welded[triangle[(i + 1) % 3] as usize];
148 if a == b {
149 continue;
150 }
151 let (key, forward) = if a < b {
152 ((a, b), true)
153 } else {
154 ((b, a), false)
155 };
156 edge_tris.entry(key).or_default().push((t as u32, forward));
157 }
158 }
159 let mut unmatched: Vec<_> = edge_tris
162 .iter()
163 .filter(|(_, uses)| uses.len() == 1)
164 .map(|(&key, _)| key)
165 .collect();
166 unmatched.sort_unstable();
167 if !unmatched.is_empty() {
168 let quant = (chord * 1e-2).max(1e-12);
169 let mut coarse: FxHashMap<[i64; 3], u32> = FxHashMap::default();
170 let mut coarse_id = vec![None; exact.len()];
171 for (raw, point) in mesh.positions.chunks_exact(3).enumerate() {
172 let coordinates: [f64; 3] =
173 std::array::from_fn(|i| ((point[i] - lo[i]) / quant).round());
174 if coordinates
177 .iter()
178 .any(|x| !x.is_finite() || x.abs() >= i64::MAX as f64)
179 {
180 continue;
181 }
182 let key = coordinates.map(|x| x as i64);
183 let next = u32::try_from(exact.len() + coarse.len())
184 .map_err(|_| "orient_mesh_coherently: too many welded vertices")?;
185 coarse_id[welded[raw] as usize] = Some(*coarse.entry(key).or_insert(next));
186 }
187 let mut approximate: FxHashMap<(u32, u32), Vec<(u32, bool)>> = FxHashMap::default();
188 for (a, b) in unmatched {
189 let (Some(ca), Some(cb)) = (coarse_id[a as usize], coarse_id[b as usize]) else {
190 continue;
191 };
192 if ca == cb {
193 continue;
194 } let uses = edge_tris.remove(&(a, b)).unwrap();
196 let key = if ca < cb { (ca, cb) } else { (cb, ca) };
197 approximate.entry(key).or_default().extend(
198 uses.into_iter()
199 .map(|(t, forward)| (t, if ca < cb { forward } else { !forward })),
200 );
201 }
202 edge_tris.extend(approximate);
203 }
204
205 let mut adj: Vec<Vec<(u32, bool)>> = vec![Vec::new(); tri_count];
213 for (key, uses) in &edge_tris {
214 if key.0 >= exact.len() as u32 || uses.len() != 2 {
217 continue;
218 }
219 let (t0, d0) = uses[0];
220 let (t1, d1) = uses[1];
221 let same_dir = d0 == d1;
222 adj[t0 as usize].push((t1, same_dir));
223 adj[t1 as usize].push((t0, same_dir));
224 }
225 for a in adj.iter_mut() {
228 a.sort_unstable();
229 }
230
231 let mut flip = vec![false; tri_count];
235 let mut comp_of = vec![u32::MAX; tri_count];
236 let mut stack: Vec<u32> = Vec::new();
237 let mut ncomp = 0u32;
238 for seed in 0..tri_count {
239 if comp_of[seed] != u32::MAX {
240 continue;
241 }
242 comp_of[seed] = ncomp;
243 stack.push(seed as u32);
244 while let Some(t) = stack.pop() {
245 let tf = flip[t as usize];
246 for &(nb, same_dir) in &adj[t as usize] {
247 if comp_of[nb as usize] != u32::MAX {
248 continue;
249 }
250 flip[nb as usize] = tf ^ same_dir;
251 comp_of[nb as usize] = ncomp;
252 stack.push(nb);
253 }
254 }
255 ncomp += 1;
256 }
257 let ncomp = ncomp as usize;
258
259 let mut parent: Vec<u32> = (0..ncomp as u32).collect();
268 let mut prel = vec![false; ncomp]; fn find(parent: &mut [u32], prel: &mut [bool], mut c: u32) -> (u32, bool) {
270 let mut par = false;
271 let mut path = Vec::new();
273 while parent[c as usize] != c {
274 path.push(c);
275 par ^= prel[c as usize];
276 c = parent[c as usize];
277 }
278 let mut acc = par;
281 for &node in path.iter() {
282 let this = prel[node as usize];
283 parent[node as usize] = c;
284 prel[node as usize] = acc;
285 acc ^= this;
286 }
287 (c, par)
288 }
289 let mut fwd: Vec<u32> = Vec::new();
290 let mut rev: Vec<u32> = Vec::new();
291 let mut seam_keys: Vec<(u32, u32)> = edge_tris.keys().copied().collect();
294 seam_keys.sort_unstable();
295 for key in &seam_keys {
296 let uses = &edge_tris[key];
297 fwd.clear();
304 rev.clear();
305 for &(t, d) in uses {
306 if d {
307 fwd.push(t);
308 } else {
309 rev.push(t);
310 }
311 }
312 let pairs = if uses.len() == 2 {
313 1
314 } else {
315 fwd.len().min(rev.len())
316 };
317 for i in 0..pairs {
318 let ((ta, da), (tb, db)) = if uses.len() == 2 {
319 (uses[0], uses[1])
320 } else {
321 ((fwd[i], true), (rev[i], false))
322 };
323 let (ca, cb) = (comp_of[ta as usize], comp_of[tb as usize]);
324 if ca == cb {
325 continue;
326 }
327 let eff_a = da ^ flip[ta as usize];
328 let eff_b = db ^ flip[tb as usize];
329 let rel = eff_a == eff_b;
330 let (ra, pa) = find(&mut parent, &mut prel, ca);
331 let (rb, pb) = find(&mut parent, &mut prel, cb);
332 if ra != rb {
333 parent[ra as usize] = rb;
334 prel[ra as usize] = pa ^ pb ^ rel;
335 }
336 }
337 }
338 let mut crel = vec![false; ncomp];
340 let mut root_of = vec![0u32; ncomp];
341 for c in 0..ncomp as u32 {
342 let (r, p) = find(&mut parent, &mut prel, c);
343 crel[c as usize] = p;
344 root_of[c as usize] = r;
345 }
346 for t in 0..tri_count {
349 if crel[comp_of[t] as usize] {
350 flip[t] = !flip[t];
351 }
352 }
353
354 let mut shell_vol: HashMap<u32, f64> = HashMap::default();
359 let mut shell_target: HashMap<u32, i8> = HashMap::default();
360 for t in 0..tri_count {
361 let v = tetra_signed_volume(&mesh.positions, &mesh.indices, t);
362 let v = if flip[t] { -v } else { v };
363 let root = root_of[comp_of[t] as usize];
364 *shell_vol.entry(root).or_insert(0.0) += v;
365 let face_id = mesh.face_ids.get(t).copied().ok_or_else(|| {
366 "orient_mesh_coherently: triangle is missing its source face id".to_string()
367 })? as usize;
368 let target = *face_shell_signs.get(face_id).ok_or_else(|| {
369 format!("orient_mesh_coherently: source face id {face_id} is out of range")
370 })?;
371 if let Some(previous) = shell_target.insert(root, target) {
372 if previous != target {
373 return Err(
374 "orient_mesh_coherently: one welded component spans oppositely oriented shells"
375 .into(),
376 );
377 }
378 }
379 }
380 if std::env::var("BREP_DEBUG_ORIENT_SHELLS").is_ok() {
381 let mut roots: std::collections::BTreeMap<u32, usize> = Default::default();
382 for t in 0..tri_count {
383 *roots.entry(root_of[comp_of[t] as usize]).or_insert(0) += 1;
384 }
385 let mut edge_use: std::collections::BTreeMap<usize, usize> = Default::default();
386 for uses in edge_tris.values() {
387 *edge_use.entry(uses.len()).or_insert(0) += 1;
388 }
389 eprintln!(
390 "[orient-shells] {ncomp} components -> {} shells | edge-use histogram {edge_use:?}",
391 roots.len()
392 );
393 for (r, n) in roots.iter().filter(|(_, n)| **n > 1) {
394 let v = shell_vol.get(r).copied().unwrap_or(0.0);
395 let tgt = shell_target.get(r).copied().unwrap_or(1);
396 eprintln!(" shell {r}: {n} tris, vol={v:.3e}, target={tgt}");
397 }
398 }
399 for t in 0..tri_count {
400 let r = root_of[comp_of[t] as usize];
401 let volume_sign = if shell_vol.get(&r).copied().unwrap_or(0.0) < 0.0 {
402 -1_i8
403 } else {
404 1_i8
405 };
406 if volume_sign != shell_target.get(&r).copied().unwrap_or(1) {
407 flip[t] = !flip[t];
408 }
409 }
410 let mut vnref = vec![[0.0f64; 3]; vcount];
416 for t in 0..tri_count {
417 let mut g = tri_geometric_normal(&mesh.positions, &mesh.indices, t);
418 if flip[t] {
419 g = [-g[0], -g[1], -g[2]];
420 }
421 for k in 0..3 {
422 let v = mesh.indices[3 * t + k] as usize;
423 vnref[v][0] += g[0];
424 vnref[v][1] += g[1];
425 vnref[v][2] += g[2];
426 }
427 }
428 for v in 0..vcount {
429 let r = vnref[v];
430 let dot = mesh.normals[3 * v] * r[0]
431 + mesh.normals[3 * v + 1] * r[1]
432 + mesh.normals[3 * v + 2] * r[2];
433 if dot < 0.0 {
434 mesh.normals[3 * v] = -mesh.normals[3 * v];
435 mesh.normals[3 * v + 1] = -mesh.normals[3 * v + 1];
436 mesh.normals[3 * v + 2] = -mesh.normals[3 * v + 2];
437 }
438 }
439
440 for t in 0..tri_count {
442 if flip[t] {
443 mesh.indices.swap(3 * t + 1, 3 * t + 2);
444 }
445 }
446
447 if let Some(start) = _timer {
448 let nflip = flip.iter().filter(|&&f| f).count();
449 eprintln!(
450 "orient_mesh_coherently: {tri_count} tris, {vcount} verts, {ncomp} components, {nflip} flipped in {:?}",
451 start.elapsed()
452 );
453 }
454 Ok(())
455}
456
457