1use crate::mesh_weld::{edge_key, weld_vertex};
2use crate::topology::{
3 BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord, ShellRecord, VertexRecord,
4};
5use crate::{make_line, make_plane, tessellate_face, Mesh, TessellationOptions, Vec3};
6use crate::{KernelRefusal, KernelStage, OrRefuse};
7use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
8
9pub(crate) fn close_as_faceted_brep(
15 solid: &BrepSolid,
16 tolerance: f64,
17) -> Result<BrepSolid, KernelRefusal> {
18 let options = TessellationOptions {
19 slabs_per_span_u: 12,
20 steps_per_span_v: 12,
21 };
22 let mut mesh = Mesh::default();
23 let mut face_id = 0;
24 for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
25 if let Ok(face_mesh) = tessellate_face(face, options, face_id) {
26 let base = (mesh.positions.len() / 3) as u32;
27 mesh.positions.extend(face_mesh.positions);
28 mesh.normals.extend(face_mesh.normals);
29 mesh.indices
30 .extend(face_mesh.indices.into_iter().map(|index| index + base));
31 mesh.face_ids.extend(face_mesh.face_ids);
32 }
33 face_id += 1;
34 }
35 if mesh.indices.is_empty() {
36 return Err(KernelRefusal::internal(KernelStage::Refine, "faceted_tessellation", "faceted shell repair could not tessellate any faces"));
37 }
38 triangle_soup_to_faceted_brep(&mesh.positions, Some(&mesh.indices), tolerance.max(2e-3))
39}
40
41pub fn mesh_to_faceted_brep(
50 positions: &[f64],
51 indices: Option<&[u32]>,
52 tolerance: f64,
53) -> Result<BrepSolid, KernelRefusal> {
54 if positions.is_empty() || positions.len() % 3 != 0 {
55 return Err(KernelRefusal::input(KernelStage::Collect, "positions", "mesh_to_faceted_brep: positions must be xyz triples"));
56 }
57 let weld_tolerance = derived_weld_tolerance(positions, tolerance);
58 triangle_soup_to_faceted_brep(positions, indices, weld_tolerance)
59}
60
61fn derived_weld_tolerance(positions: &[f64], tolerance: f64) -> f64 {
65 if tolerance > 0.0 && tolerance.is_finite() {
66 return tolerance;
67 }
68 let mut low = [f64::INFINITY; 3];
69 let mut high = [f64::NEG_INFINITY; 3];
70 for point in positions.chunks_exact(3) {
71 for axis in 0..3 {
72 low[axis] = low[axis].min(point[axis]);
73 high[axis] = high[axis].max(point[axis]);
74 }
75 }
76 let diagonal = (0..3)
77 .map(|axis| (high[axis] - low[axis]).powi(2))
78 .sum::<f64>()
79 .sqrt();
80 (diagonal * 1e-6).max(1e-12)
81}
82
83#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
89pub struct MeshRepairReport {
90 pub welded_vertices: usize,
92 pub triangles: usize,
94 pub dropped_triangles: usize,
97 pub pruned_triangles: usize,
100 pub reoriented_triangles: usize,
103 pub capped_loops: usize,
105 pub cap_triangles: usize,
107}
108
109impl MeshRepairReport {
110 pub fn changed(&self) -> bool {
112 self.dropped_triangles != 0
113 || self.pruned_triangles != 0
114 || self.reoriented_triangles != 0
115 || self.capped_loops != 0
116 }
117}
118
119pub fn repair_triangle_soup(
133 positions: &[f64],
134 indices: Option<&[u32]>,
135 weld_tolerance: f64,
136) -> Result<(Vec<f64>, Vec<u32>, MeshRepairReport), KernelRefusal> {
137 if positions.is_empty() || !positions.len().is_multiple_of(3) {
138 return Err(KernelRefusal::input(KernelStage::Collect, "positions", "repair_triangle_soup: positions must be xyz triples"));
139 }
140 let weld_tolerance = derived_weld_tolerance(positions, weld_tolerance);
141 let (points, triangles, report) = repair_soup(positions, indices, weld_tolerance)?;
142 let flat = points
143 .iter()
144 .flat_map(|point| [point.x, point.y, point.z])
145 .collect::<Vec<_>>();
146 let flat_indices = triangles
147 .iter()
148 .flatten()
149 .map(|&index| index as u32)
150 .collect::<Vec<_>>();
151 Ok((flat, flat_indices, report))
152}
153
154fn triangle_soup_to_faceted_brep(
158 positions: &[f64],
159 indices: Option<&[u32]>,
160 weld_tolerance: f64,
161) -> Result<BrepSolid, KernelRefusal> {
162 let (points, triangles, _) = repair_soup(positions, indices, weld_tolerance)?;
163 build_faceted_brep(&points, &triangles)
164}
165
166fn repair_soup(
169 positions: &[f64],
170 indices: Option<&[u32]>,
171 weld_tolerance: f64,
172) -> Result<(Vec<Vec3>, Vec<[usize; 3]>, MeshRepairReport), KernelRefusal> {
173 let mut report = MeshRepairReport::default();
174 let mut points = Vec::<Vec3>::new();
175 let mut buckets = HashMap::<[i64; 3], Vec<usize>>::default();
176 let mut mesh_to_welded = Vec::with_capacity(positions.len() / 3);
177 for position in positions.chunks_exact(3) {
178 mesh_to_welded.push(weld_vertex(
179 Vec3::new(position[0], position[1], position[2]),
180 weld_tolerance,
181 &mut points,
182 &mut buckets,
183 ));
184 }
185 let sequential;
186 let indices = match indices {
187 Some(indices) => indices,
188 None => {
189 sequential = (0..mesh_to_welded.len() as u32).collect::<Vec<_>>();
190 &sequential
191 }
192 };
193 let mut triangles = Vec::<[usize; 3]>::new();
194 let mut seen = HashSet::<[usize; 3]>::default();
195 for triangle in indices.chunks_exact(3) {
196 if triangle
197 .iter()
198 .any(|index| *index as usize >= mesh_to_welded.len())
199 {
200 return Err(KernelRefusal::input(KernelStage::Collect, "indices", "mesh_to_faceted_brep: triangle index outside positions"));
201 }
202 let value = [
203 mesh_to_welded[triangle[0] as usize],
204 mesh_to_welded[triangle[1] as usize],
205 mesh_to_welded[triangle[2] as usize],
206 ];
207 if value[0] == value[1] || value[1] == value[2] || value[2] == value[0] {
208 report.dropped_triangles += 1;
209 continue;
210 }
211 let mut key = value;
212 key.sort_unstable();
213 if seen.insert(key) {
214 triangles.push(value);
215 } else {
216 report.dropped_triangles += 1;
217 }
218 }
219 if triangles.is_empty() {
220 return Err(KernelRefusal::input(KernelStage::Collect, "triangles", "mesh_to_faceted_brep: no non-degenerate triangles"));
221 }
222
223 loop {
226 let mut edge_uses = HashMap::<(usize, usize), Vec<(usize, bool)>>::default();
227 for (triangle_index, triangle) in triangles.iter().enumerate() {
228 for side in 0..3 {
229 let first = triangle[side];
230 let second = triangle[(side + 1) % 3];
231 edge_uses
232 .entry(edge_key(first, second))
233 .or_default()
234 .push((triangle_index, first < second));
235 }
236 }
237 let Some(uses) = edge_uses.values().find(|uses| uses.len() > 2) else {
238 break;
239 };
240 let keep_forward = uses.iter().find(|(_, forward)| *forward).map(|use_| use_.0);
241 let keep_reverse = uses
242 .iter()
243 .find(|(_, forward)| !*forward)
244 .map(|use_| use_.0);
245 let keep = [keep_forward, keep_reverse]
246 .into_iter()
247 .flatten()
248 .collect::<HashSet<_>>();
249 let remove = uses
250 .iter()
251 .map(|use_| use_.0)
252 .filter(|index| !keep.contains(index))
253 .collect::<HashSet<_>>();
254 if remove.is_empty() {
255 break;
256 }
257 report.pruned_triangles += remove.len();
258 triangles = triangles
259 .into_iter()
260 .enumerate()
261 .filter_map(|(index, triangle)| (!remove.contains(&index)).then_some(triangle))
262 .collect();
263 }
264
265 report.reoriented_triangles = orient_consistently(&points, &mut triangles)?;
266
267 let mut directed_boundary = Vec::<(usize, usize)>::new();
268 let mut counts = HashMap::<(usize, usize), usize>::default();
269 for triangle in &triangles {
270 for side in 0..3 {
271 *counts
272 .entry(edge_key(triangle[side], triangle[(side + 1) % 3]))
273 .or_default() += 1;
274 }
275 }
276 for triangle in &triangles {
277 for side in 0..3 {
278 let first = triangle[side];
279 let second = triangle[(side + 1) % 3];
280 if counts[&edge_key(first, second)] == 1 {
281 directed_boundary.push((first, second));
282 }
283 }
284 }
285 while let Some((start, mut end)) = directed_boundary.pop() {
286 let mut loop_vertices = vec![start, end];
287 while end != start {
288 let Some(index) = directed_boundary
289 .iter()
290 .position(|(candidate_start, _)| *candidate_start == end)
291 else {
292 break;
293 };
294 let (_, next) = directed_boundary.swap_remove(index);
295 end = next;
296 if end != start {
297 loop_vertices.push(end);
298 }
299 if loop_vertices.len() > points.len() + 1 {
300 break;
301 }
302 }
303 if end != start || loop_vertices.len() < 3 {
304 continue;
305 }
306 let center = loop_vertices
307 .iter()
308 .fold(Vec3::default(), |sum, index| sum.add(points[*index]))
309 .scale(1.0 / loop_vertices.len() as f64);
310 let center_index = points.len();
311 points.push(center);
312 for index in 0..loop_vertices.len() {
313 let first = loop_vertices[index];
314 let second = loop_vertices[(index + 1) % loop_vertices.len()];
315 triangles.push([second, first, center_index]);
317 }
318 report.capped_loops += 1;
319 report.cap_triangles += loop_vertices.len();
320 }
321
322 report.welded_vertices = points.len();
323 report.triangles = triangles.len();
324 Ok((points, triangles, report))
325}
326
327fn orient_consistently(points: &[Vec3], triangles: &mut [[usize; 3]]) -> Result<usize, KernelRefusal> {
346 let mut edge_uses = HashMap::<(usize, usize), Vec<usize>>::default();
347 for (triangle_index, triangle) in triangles.iter().enumerate() {
348 for side in 0..3 {
349 edge_uses
350 .entry(edge_key(triangle[side], triangle[(side + 1) % 3]))
351 .or_default()
352 .push(triangle_index);
353 }
354 }
355 let runs_forward = |triangle: &[usize; 3], first: usize, second: usize| {
356 (0..3).any(|side| triangle[side] == first && triangle[(side + 1) % 3] == second)
357 };
358
359 let mut flip = vec![None::<bool>; triangles.len()];
360 let mut flipped = 0;
361 for seed in 0..triangles.len() {
362 if flip[seed].is_some() {
363 continue;
364 }
365 flip[seed] = Some(false);
366 let mut patch = vec![seed];
367 let mut queue = std::collections::VecDeque::from([seed]);
368 while let Some(current) = queue.pop_front() {
369 let current_flip = flip[current] == Some(true);
370 for side in 0..3 {
371 let first = triangles[current][side];
372 let second = triangles[current][(side + 1) % 3];
373 let uses = &edge_uses[&edge_key(first, second)];
374 if uses.len() != 2 {
375 continue;
376 }
377 let neighbour = if uses[0] == current { uses[1] } else { uses[0] };
378 let neighbour_same_way = runs_forward(&triangles[neighbour], first, second);
381 let wanted = neighbour_same_way != current_flip;
382 match flip[neighbour] {
383 None => {
384 flip[neighbour] = Some(wanted);
385 patch.push(neighbour);
386 queue.push_back(neighbour);
387 }
388 Some(assigned) if assigned != wanted => {
389 return Err(KernelRefusal::unsupported(KernelStage::Sew, "non_orientable_shell", format!(
390 "mesh_to_faceted_brep: the shell through triangle {seed} cannot be \
391 consistently oriented (non-orientable at the edge ({}, {}, {})–({}, {}, {}))",
392 points[first].x, points[first].y, points[first].z,
393 points[second].x, points[second].y, points[second].z
394 )));
395 }
396 Some(_) => {}
397 }
398 }
399 }
400 if patch.iter().all(|&index| flip[index] == Some(false)) {
401 continue;
402 }
403 for &index in &patch {
404 if flip[index] == Some(true) {
405 triangles[index].swap(1, 2);
406 }
407 }
408 let centroid = patch
409 .iter()
410 .flat_map(|&index| triangles[index])
411 .fold(Vec3::default(), |sum, corner| sum.add(points[corner]))
412 .scale(1.0 / (3 * patch.len()) as f64);
413 let volume = patch
414 .iter()
415 .map(|&index| {
416 let [a, b, c] = triangles[index].map(|corner| points[corner].sub(centroid));
417 a.dot(b.cross(c))
418 })
419 .sum::<f64>();
420 if volume < 0.0 {
421 for &index in &patch {
422 triangles[index].swap(1, 2);
423 }
424 flipped += patch.iter().filter(|&&index| flip[index] == Some(false)).count();
425 } else {
426 flipped += patch.iter().filter(|&&index| flip[index] == Some(true)).count();
427 }
428 }
429 Ok(flipped)
430}
431
432fn build_faceted_brep(points: &[Vec3], triangles: &[[usize; 3]]) -> Result<BrepSolid, KernelRefusal> {
434 let vertices = points
435 .iter()
436 .enumerate()
437 .map(|(index, point)| VertexRecord {
438 id: index as u64 + 1,
439 point: *point,
440 })
441 .collect::<Vec<_>>();
442 let mut edges = Vec::<EdgeRecord>::new();
443 let mut edge_ids = HashMap::<(usize, usize), u64>::default();
444 let mut faces = Vec::<FaceRecord>::new();
445 let mut next_id = vertices.len() as u64 + 1;
446 for triangle in triangles.iter().copied() {
447 let a = points[triangle[0]];
448 let b = points[triangle[1]];
449 let c = points[triangle[2]];
450 let u = b.sub(a);
451 let u_length = u.length();
452 if u_length <= 1e-10 {
453 continue;
454 }
455 let u_direction = u.scale(1.0 / u_length);
456 let c_delta = c.sub(a);
457 let c_u = c_delta.dot(u_direction);
458 let v = c_delta.sub(u_direction.scale(c_u));
459 let v_length = v.length();
460 if v_length <= 1e-10 {
461 continue;
462 }
463 let v_direction = v.scale(1.0 / v_length);
464 let u_low = c_u.min(0.0);
469 let u_high = c_u.max(u_length);
470 let surface = make_plane(
471 a.add(u_direction.scale(u_low)),
472 u_direction,
473 v_direction,
474 u_high - u_low,
475 v_length,
476 ).or_refuse(KernelStage::Sew, "make_plane")?;
477 let uv = [
478 Vec3::new(-u_low, 0.0, 0.0),
479 Vec3::new(u_length - u_low, 0.0, 0.0),
480 Vec3::new(c_u - u_low, v_length, 0.0),
481 ];
482 let mut coedges = Vec::with_capacity(3);
483 for side in 0..3 {
484 let first = triangle[side];
485 let second = triangle[(side + 1) % 3];
486 let key = edge_key(first, second);
487 let edge_id = if let Some(id) = edge_ids.get(&key) {
488 *id
489 } else {
490 let id = next_id;
491 next_id += 1;
492 edges.push(EdgeRecord {
493 id,
494 curve: make_line(points[key.0], points[key.1]).or_refuse(KernelStage::Sew, "make_line")?,
495 t0: 0.0,
496 t1: 1.0,
497 start_vertex_id: key.0 as u64 + 1,
498 end_vertex_id: key.1 as u64 + 1,
499 degenerate: false,
500 name: None,
501 });
502 edge_ids.insert(key, id);
503 id
504 };
505 coedges.push(CoedgeRecord {
506 id: next_id,
507 edge_id,
508 forward: first < second,
509 pcurve: make_line(uv[side], uv[(side + 1) % 3]).or_refuse(KernelStage::Sew, "make_line")?,
510 });
511 next_id += 1;
512 }
513 let loop_record = LoopRecord {
514 id: next_id,
515 coedges,
516 };
517 next_id += 1;
518 faces.push(FaceRecord {
519 id: next_id,
520 surface,
521 same_sense: true,
522 loops: vec![loop_record],
523 name: None,
524 });
525 next_id += 1;
526 }
527 let mut result = BrepSolid {
528 mass_properties_cache: Default::default(),
529 id: next_id + 1,
530 vertices,
531 edges,
532 shells: vec![ShellRecord { id: next_id, faces }],
533 genus: 0,
534 kind: crate::BodyKind::Solid,
535 };
536 let vertex_count = result.vertices.len() as i64;
537 let edge_count = result.edges.len() as i64;
538 let face_count = result.shells[0].faces.len() as i64;
539 let numerator = 2 - (vertex_count - edge_count + face_count);
540 if numerator >= 0 && numerator % 2 == 0 {
541 result.genus = numerator / 2;
542 }
543 let issues = result.validate();
544 if !issues.is_empty() {
545 return Err(KernelRefusal::internal(KernelStage::Validate, "faceted_validate", format!(
546 "faceted shell repair produced invalid topology: {issues:?}"
547 )));
548 }
549 for (index, face) in result.shells[0].faces.iter().enumerate() {
550 tessellate_face(face, TessellationOptions::default(), index as u32)
551 .map_err(|error| KernelRefusal::internal(KernelStage::Validate, "faceted_tessellate", format!("faceted face {index} cannot be tessellated: {error}")))?;
552 }
553 Ok(result)
554}
555