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 rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
7
8pub(crate) fn close_as_faceted_brep(
14 solid: &BrepSolid,
15 tolerance: f64,
16) -> Result<BrepSolid, String> {
17 let options = TessellationOptions {
18 slabs_per_span_u: 12,
19 steps_per_span_v: 12,
20 };
21 let mut mesh = Mesh::default();
22 let mut face_id = 0;
23 for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
24 if let Ok(face_mesh) = tessellate_face(face, options, face_id) {
25 let base = (mesh.positions.len() / 3) as u32;
26 mesh.positions.extend(face_mesh.positions);
27 mesh.normals.extend(face_mesh.normals);
28 mesh.indices
29 .extend(face_mesh.indices.into_iter().map(|index| index + base));
30 mesh.face_ids.extend(face_mesh.face_ids);
31 }
32 face_id += 1;
33 }
34 if mesh.indices.is_empty() {
35 return Err("faceted shell repair could not tessellate any faces".into());
36 }
37 triangle_soup_to_faceted_brep(&mesh.positions, Some(&mesh.indices), tolerance.max(2e-3))
38}
39
40pub fn mesh_to_faceted_brep(
49 positions: &[f64],
50 indices: Option<&[u32]>,
51 tolerance: f64,
52) -> Result<BrepSolid, String> {
53 if positions.is_empty() || positions.len() % 3 != 0 {
54 return Err("mesh_to_faceted_brep: positions must be xyz triples".into());
55 }
56 let weld_tolerance = derived_weld_tolerance(positions, tolerance);
57 triangle_soup_to_faceted_brep(positions, indices, weld_tolerance)
58}
59
60fn derived_weld_tolerance(positions: &[f64], tolerance: f64) -> f64 {
64 if tolerance > 0.0 && tolerance.is_finite() {
65 return tolerance;
66 }
67 let mut low = [f64::INFINITY; 3];
68 let mut high = [f64::NEG_INFINITY; 3];
69 for point in positions.chunks_exact(3) {
70 for axis in 0..3 {
71 low[axis] = low[axis].min(point[axis]);
72 high[axis] = high[axis].max(point[axis]);
73 }
74 }
75 let diagonal = (0..3)
76 .map(|axis| (high[axis] - low[axis]).powi(2))
77 .sum::<f64>()
78 .sqrt();
79 (diagonal * 1e-6).max(1e-12)
80}
81
82#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
88pub struct MeshRepairReport {
89 pub welded_vertices: usize,
91 pub triangles: usize,
93 pub dropped_triangles: usize,
96 pub pruned_triangles: usize,
99 pub capped_loops: usize,
101 pub cap_triangles: usize,
103}
104
105impl MeshRepairReport {
106 pub fn changed(&self) -> bool {
108 self.dropped_triangles != 0 || self.pruned_triangles != 0 || self.capped_loops != 0
109 }
110}
111
112pub fn repair_triangle_soup(
126 positions: &[f64],
127 indices: Option<&[u32]>,
128 weld_tolerance: f64,
129) -> Result<(Vec<f64>, Vec<u32>, MeshRepairReport), String> {
130 if positions.is_empty() || !positions.len().is_multiple_of(3) {
131 return Err("repair_triangle_soup: positions must be xyz triples".into());
132 }
133 let weld_tolerance = derived_weld_tolerance(positions, weld_tolerance);
134 let (points, triangles, report) = repair_soup(positions, indices, weld_tolerance)?;
135 let flat = points
136 .iter()
137 .flat_map(|point| [point.x, point.y, point.z])
138 .collect::<Vec<_>>();
139 let flat_indices = triangles
140 .iter()
141 .flatten()
142 .map(|&index| index as u32)
143 .collect::<Vec<_>>();
144 Ok((flat, flat_indices, report))
145}
146
147fn triangle_soup_to_faceted_brep(
151 positions: &[f64],
152 indices: Option<&[u32]>,
153 weld_tolerance: f64,
154) -> Result<BrepSolid, String> {
155 let (points, triangles, _) = repair_soup(positions, indices, weld_tolerance)?;
156 build_faceted_brep(&points, &triangles)
157}
158
159fn repair_soup(
162 positions: &[f64],
163 indices: Option<&[u32]>,
164 weld_tolerance: f64,
165) -> Result<(Vec<Vec3>, Vec<[usize; 3]>, MeshRepairReport), String> {
166 let mut report = MeshRepairReport::default();
167 let mut points = Vec::<Vec3>::new();
168 let mut buckets = HashMap::<[i64; 3], Vec<usize>>::default();
169 let mut mesh_to_welded = Vec::with_capacity(positions.len() / 3);
170 for position in positions.chunks_exact(3) {
171 mesh_to_welded.push(weld_vertex(
172 Vec3::new(position[0], position[1], position[2]),
173 weld_tolerance,
174 &mut points,
175 &mut buckets,
176 ));
177 }
178 let sequential;
179 let indices = match indices {
180 Some(indices) => indices,
181 None => {
182 sequential = (0..mesh_to_welded.len() as u32).collect::<Vec<_>>();
183 &sequential
184 }
185 };
186 let mut triangles = Vec::<[usize; 3]>::new();
187 let mut seen = HashSet::<[usize; 3]>::default();
188 for triangle in indices.chunks_exact(3) {
189 if triangle
190 .iter()
191 .any(|index| *index as usize >= mesh_to_welded.len())
192 {
193 return Err("mesh_to_faceted_brep: triangle index outside positions".into());
194 }
195 let value = [
196 mesh_to_welded[triangle[0] as usize],
197 mesh_to_welded[triangle[1] as usize],
198 mesh_to_welded[triangle[2] as usize],
199 ];
200 if value[0] == value[1] || value[1] == value[2] || value[2] == value[0] {
201 report.dropped_triangles += 1;
202 continue;
203 }
204 let mut key = value;
205 key.sort_unstable();
206 if seen.insert(key) {
207 triangles.push(value);
208 } else {
209 report.dropped_triangles += 1;
210 }
211 }
212 if triangles.is_empty() {
213 return Err("mesh_to_faceted_brep: no non-degenerate triangles".into());
214 }
215
216 loop {
219 let mut edge_uses = HashMap::<(usize, usize), Vec<(usize, bool)>>::default();
220 for (triangle_index, triangle) in triangles.iter().enumerate() {
221 for side in 0..3 {
222 let first = triangle[side];
223 let second = triangle[(side + 1) % 3];
224 edge_uses
225 .entry(edge_key(first, second))
226 .or_default()
227 .push((triangle_index, first < second));
228 }
229 }
230 let Some(uses) = edge_uses.values().find(|uses| uses.len() > 2) else {
231 break;
232 };
233 let keep_forward = uses.iter().find(|(_, forward)| *forward).map(|use_| use_.0);
234 let keep_reverse = uses
235 .iter()
236 .find(|(_, forward)| !*forward)
237 .map(|use_| use_.0);
238 let keep = [keep_forward, keep_reverse]
239 .into_iter()
240 .flatten()
241 .collect::<HashSet<_>>();
242 let remove = uses
243 .iter()
244 .map(|use_| use_.0)
245 .filter(|index| !keep.contains(index))
246 .collect::<HashSet<_>>();
247 if remove.is_empty() {
248 break;
249 }
250 report.pruned_triangles += remove.len();
251 triangles = triangles
252 .into_iter()
253 .enumerate()
254 .filter_map(|(index, triangle)| (!remove.contains(&index)).then_some(triangle))
255 .collect();
256 }
257
258 let mut directed_boundary = Vec::<(usize, usize)>::new();
259 let mut counts = HashMap::<(usize, usize), usize>::default();
260 for triangle in &triangles {
261 for side in 0..3 {
262 *counts
263 .entry(edge_key(triangle[side], triangle[(side + 1) % 3]))
264 .or_default() += 1;
265 }
266 }
267 for triangle in &triangles {
268 for side in 0..3 {
269 let first = triangle[side];
270 let second = triangle[(side + 1) % 3];
271 if counts[&edge_key(first, second)] == 1 {
272 directed_boundary.push((first, second));
273 }
274 }
275 }
276 while let Some((start, mut end)) = directed_boundary.pop() {
277 let mut loop_vertices = vec![start, end];
278 while end != start {
279 let Some(index) = directed_boundary
280 .iter()
281 .position(|(candidate_start, _)| *candidate_start == end)
282 else {
283 break;
284 };
285 let (_, next) = directed_boundary.swap_remove(index);
286 end = next;
287 if end != start {
288 loop_vertices.push(end);
289 }
290 if loop_vertices.len() > points.len() + 1 {
291 break;
292 }
293 }
294 if end != start || loop_vertices.len() < 3 {
295 continue;
296 }
297 let center = loop_vertices
298 .iter()
299 .fold(Vec3::default(), |sum, index| sum.add(points[*index]))
300 .scale(1.0 / loop_vertices.len() as f64);
301 let center_index = points.len();
302 points.push(center);
303 for index in 0..loop_vertices.len() {
304 let first = loop_vertices[index];
305 let second = loop_vertices[(index + 1) % loop_vertices.len()];
306 triangles.push([second, first, center_index]);
308 }
309 report.capped_loops += 1;
310 report.cap_triangles += loop_vertices.len();
311 }
312
313 report.welded_vertices = points.len();
314 report.triangles = triangles.len();
315 Ok((points, triangles, report))
316}
317
318fn build_faceted_brep(points: &[Vec3], triangles: &[[usize; 3]]) -> Result<BrepSolid, String> {
320 let vertices = points
321 .iter()
322 .enumerate()
323 .map(|(index, point)| VertexRecord {
324 id: index as u64 + 1,
325 point: *point,
326 })
327 .collect::<Vec<_>>();
328 let mut edges = Vec::<EdgeRecord>::new();
329 let mut edge_ids = HashMap::<(usize, usize), u64>::default();
330 let mut faces = Vec::<FaceRecord>::new();
331 let mut next_id = vertices.len() as u64 + 1;
332 for triangle in triangles.iter().copied() {
333 let a = points[triangle[0]];
334 let b = points[triangle[1]];
335 let c = points[triangle[2]];
336 let u = b.sub(a);
337 let u_length = u.length();
338 if u_length <= 1e-10 {
339 continue;
340 }
341 let u_direction = u.scale(1.0 / u_length);
342 let c_delta = c.sub(a);
343 let c_u = c_delta.dot(u_direction);
344 let v = c_delta.sub(u_direction.scale(c_u));
345 let v_length = v.length();
346 if v_length <= 1e-10 {
347 continue;
348 }
349 let v_direction = v.scale(1.0 / v_length);
350 let u_low = c_u.min(0.0);
355 let u_high = c_u.max(u_length);
356 let surface = make_plane(
357 a.add(u_direction.scale(u_low)),
358 u_direction,
359 v_direction,
360 u_high - u_low,
361 v_length,
362 )?;
363 let uv = [
364 Vec3::new(-u_low, 0.0, 0.0),
365 Vec3::new(u_length - u_low, 0.0, 0.0),
366 Vec3::new(c_u - u_low, v_length, 0.0),
367 ];
368 let mut coedges = Vec::with_capacity(3);
369 for side in 0..3 {
370 let first = triangle[side];
371 let second = triangle[(side + 1) % 3];
372 let key = edge_key(first, second);
373 let edge_id = if let Some(id) = edge_ids.get(&key) {
374 *id
375 } else {
376 let id = next_id;
377 next_id += 1;
378 edges.push(EdgeRecord {
379 id,
380 curve: make_line(points[key.0], points[key.1])?,
381 t0: 0.0,
382 t1: 1.0,
383 start_vertex_id: key.0 as u64 + 1,
384 end_vertex_id: key.1 as u64 + 1,
385 degenerate: false,
386 name: None,
387 });
388 edge_ids.insert(key, id);
389 id
390 };
391 coedges.push(CoedgeRecord {
392 id: next_id,
393 edge_id,
394 forward: first < second,
395 pcurve: make_line(uv[side], uv[(side + 1) % 3])?,
396 });
397 next_id += 1;
398 }
399 let loop_record = LoopRecord {
400 id: next_id,
401 coedges,
402 };
403 next_id += 1;
404 faces.push(FaceRecord {
405 id: next_id,
406 surface,
407 same_sense: true,
408 loops: vec![loop_record],
409 name: None,
410 });
411 next_id += 1;
412 }
413 let mut result = BrepSolid {
414 id: next_id + 1,
415 vertices,
416 edges,
417 shells: vec![ShellRecord { id: next_id, faces }],
418 genus: 0,
419 };
420 let vertex_count = result.vertices.len() as i64;
421 let edge_count = result.edges.len() as i64;
422 let face_count = result.shells[0].faces.len() as i64;
423 let numerator = 2 - (vertex_count - edge_count + face_count);
424 if numerator >= 0 && numerator % 2 == 0 {
425 result.genus = numerator / 2;
426 }
427 let issues = result.validate();
428 if !issues.is_empty() {
429 return Err(format!(
430 "faceted shell repair produced invalid topology: {issues:?}"
431 ));
432 }
433 for (index, face) in result.shells[0].faces.iter().enumerate() {
434 tessellate_face(face, TessellationOptions::default(), index as u32)
435 .map_err(|error| format!("faceted face {index} cannot be tessellated: {error}"))?;
436 }
437 Ok(result)
438}
439