1use std::collections::BTreeMap;
2
3use runmat_meshing_cad::{
4 project_to_face, CadEvaluationModel, CadFace, CadLoop, CadTopologyModel, SourceTopologyEdge,
5 SourceTopologyFace, SourceTopologyModel,
6};
7use runmat_meshing_core::MeshSizingField;
8use runmat_meshing_curve::{
9 validate_curve_discretization, CadCurveDiscretization, CadCurveEdgeProvenance,
10 CurveDiscretization, CurveValidationOptions,
11};
12
13mod boundary;
14mod coverage;
15mod elements;
16mod geometry;
17mod sampling;
18mod subdivision;
19mod triangulation;
20mod types;
21
22use boundary::{
23 curve_nodes_by_source_edge, curve_segments_for_source_edges, face_curve_segment_loops,
24 oriented_face_curve_segments,
25};
26use coverage::{SurfaceCadCurveBoundaryProvenanceAccumulator, SurfaceLoopCoverageAccumulator};
27use elements::append_curve_driven_face_elements;
28use subdivision::{append_centroid_subdivision, triangle_centroid, CentroidSubdivision};
29pub use types::{
30 SurfaceCadCurveBoundaryEdgeProvenance, SurfaceCadCurveBoundaryProvenanceReport,
31 SurfaceDiscretization, SurfaceDiscretizationError, SurfaceDiscretizationOptions,
32 SurfaceElement, SurfaceLoopCoverageReport, SurfaceNode,
33};
34
35pub const MODULE_PURPOSE: &str = "face-domain triangulation from recovered curve boundaries";
36pub const INTERNAL_SOURCE_EDGE_ID: u32 = u32::MAX;
37
38pub fn discretize_topology_surfaces(
39 topology: &SourceTopologyModel,
40 _options: SurfaceDiscretizationOptions,
41) -> Result<SurfaceDiscretization, SurfaceDiscretizationError> {
42 let nodes = topology
43 .vertices
44 .iter()
45 .map(|vertex| SurfaceNode {
46 node_id: vertex.vertex_id,
47 source_vertex_id: vertex.vertex_id,
48 coordinates_m: vertex.coordinates_m,
49 })
50 .collect::<Vec<_>>();
51
52 let mut elements = Vec::<SurfaceElement>::with_capacity(topology.faces.len());
53 for face in &topology.faces {
54 validate_face_vertices(topology, face)?;
55 elements.push(SurfaceElement {
56 element_id: elements.len() as u32,
57 source_face_id: face.face_id,
58 cad_face_id: None,
59 source_edge_ids: face.edge_ids,
60 node_ids: face.node_ids,
61 parametric_node_uv: [[0.0, 0.0]; 3],
62 max_projection_error_m: 0.0,
63 region_ids: face.region_ids.clone(),
64 material_region_ids: face.material_region_ids.clone(),
65 area_m2: face.area_m2,
66 unit_normal: face.unit_normal,
67 });
68 }
69
70 Ok(SurfaceDiscretization {
71 nodes,
72 elements,
73 curve_boundary_validation: None,
74 loop_coverage: None,
75 cad_curve_boundary_provenance: None,
76 exact_cad_sample_node_count: 0,
77 rejected_exact_cad_sample_count: 0,
78 sizing_sample_node_count: 0,
79 rejected_sizing_sample_count: 0,
80 })
81}
82
83pub fn discretize_cad_surfaces(
84 topology: &SourceTopologyModel,
85 cad_evaluation: &CadEvaluationModel,
86 options: SurfaceDiscretizationOptions,
87) -> Result<SurfaceDiscretization, SurfaceDiscretizationError> {
88 let mut nodes = topology
89 .vertices
90 .iter()
91 .map(|vertex| SurfaceNode {
92 node_id: vertex.vertex_id,
93 source_vertex_id: vertex.vertex_id,
94 coordinates_m: vertex.coordinates_m,
95 })
96 .collect::<Vec<_>>();
97 let frames_by_source_face = cad_evaluation
98 .face_frames
99 .iter()
100 .map(|frame| (frame.source_face_id, frame))
101 .collect::<BTreeMap<_, _>>();
102
103 let element_capacity = if options.centroid_subdivision {
104 topology.faces.len() * 3
105 } else {
106 topology.faces.len()
107 };
108 let mut elements = Vec::<SurfaceElement>::with_capacity(element_capacity);
109 for face in &topology.faces {
110 validate_face_vertices(topology, face)?;
111 let frame = frames_by_source_face.get(&face.face_id).ok_or(
112 SurfaceDiscretizationError::MissingCadFaceFrame {
113 source_face_id: face.face_id,
114 },
115 )?;
116 let mut parametric_node_uv = [[0.0_f64, 0.0_f64]; 3];
117 let mut max_projection_error_m = 0.0_f64;
118 let mut corner_points = [[0.0_f64, 0.0_f64, 0.0_f64]; 3];
119 for (index, node_id) in face.node_ids.into_iter().enumerate() {
120 let point = topology
121 .vertices
122 .get(node_id as usize)
123 .filter(|vertex| vertex.vertex_id == node_id)
124 .map(|vertex| vertex.coordinates_m)
125 .ok_or(SurfaceDiscretizationError::MissingFaceVertex {
126 face_id: face.face_id,
127 node_id,
128 })?;
129 corner_points[index] = point;
130 let projection = project_to_face(frame, point);
131 if !projection.uv_in_bounds {
132 return Err(SurfaceDiscretizationError::CadProjectionOutsideFaceDomain {
133 face_id: face.face_id,
134 node_id,
135 });
136 }
137 parametric_node_uv[index] = projection.uv;
138 max_projection_error_m = max_projection_error_m.max(projection.distance_m);
139 }
140
141 if options.centroid_subdivision {
142 let centroid = triangle_centroid(corner_points);
143 let centroid_projection = project_to_face(frame, centroid);
144 if !centroid_projection.uv_in_bounds {
145 return Err(SurfaceDiscretizationError::CadProjectionOutsideFaceDomain {
146 face_id: face.face_id,
147 node_id: u32::MAX,
148 });
149 }
150 max_projection_error_m = max_projection_error_m.max(centroid_projection.distance_m);
151 append_centroid_subdivision(
152 face,
153 frame,
154 CentroidSubdivision {
155 corner_uv: parametric_node_uv,
156 centroid_m: centroid,
157 centroid_uv: centroid_projection.uv,
158 corner_projection_error_m: max_projection_error_m,
159 },
160 &mut nodes,
161 &mut elements,
162 );
163 } else {
164 elements.push(SurfaceElement {
165 element_id: elements.len() as u32,
166 source_face_id: face.face_id,
167 cad_face_id: Some(frame.face_id.clone()),
168 source_edge_ids: face.edge_ids,
169 node_ids: face.node_ids,
170 parametric_node_uv,
171 max_projection_error_m,
172 region_ids: face.region_ids.clone(),
173 material_region_ids: face.material_region_ids.clone(),
174 area_m2: face.area_m2,
175 unit_normal: frame.unit_normal,
176 });
177 }
178 }
179
180 Ok(SurfaceDiscretization {
181 nodes,
182 elements,
183 curve_boundary_validation: None,
184 loop_coverage: None,
185 cad_curve_boundary_provenance: None,
186 exact_cad_sample_node_count: 0,
187 rejected_exact_cad_sample_count: 0,
188 sizing_sample_node_count: 0,
189 rejected_sizing_sample_count: 0,
190 })
191}
192
193pub fn discretize_cad_surfaces_with_curves(
194 topology: &SourceTopologyModel,
195 cad_evaluation: &CadEvaluationModel,
196 curves: &CurveDiscretization,
197 options: SurfaceDiscretizationOptions,
198) -> Result<SurfaceDiscretization, SurfaceDiscretizationError> {
199 let curve_boundary_validation =
200 validate_curve_discretization(topology, curves, cad_curve_validation_options())
201 .map_err(SurfaceDiscretizationError::InvalidCurveBoundary)?;
202 let mut nodes = topology
203 .vertices
204 .iter()
205 .map(|vertex| SurfaceNode {
206 node_id: vertex.vertex_id,
207 source_vertex_id: vertex.vertex_id,
208 coordinates_m: vertex.coordinates_m,
209 })
210 .collect::<Vec<_>>();
211 let frames_by_source_face = cad_evaluation
212 .face_frames
213 .iter()
214 .map(|frame| (frame.source_face_id, frame))
215 .collect::<BTreeMap<_, _>>();
216 let topology_edges = topology
217 .edges
218 .iter()
219 .map(|edge| (edge.edge_id, edge))
220 .collect::<BTreeMap<_, _>>();
221 let curve_nodes_by_edge = curve_nodes_by_source_edge(curves);
222 let mut curve_node_to_surface_node = BTreeMap::<u32, u32>::new();
223
224 let mut elements = Vec::<SurfaceElement>::new();
225 let mut loop_coverage = SurfaceLoopCoverageAccumulator::new(topology.faces.len());
226 let mut exact_cad_sample_node_count = 0_usize;
227 let mut rejected_exact_cad_sample_count = 0_usize;
228 let mut sizing_sample_node_count = 0_usize;
229 let mut rejected_sizing_sample_count = 0_usize;
230 for face in &topology.faces {
231 validate_face_vertices(topology, face)?;
232 let frame = frames_by_source_face.get(&face.face_id).ok_or(
233 SurfaceDiscretizationError::MissingCadFaceFrame {
234 source_face_id: face.face_id,
235 },
236 )?;
237 let segments = oriented_face_curve_segments(
238 &topology_edges,
239 &curve_nodes_by_edge,
240 face,
241 options.max_curve_segments_per_edge.max(1),
242 &mut nodes,
243 &mut curve_node_to_surface_node,
244 )?;
245 let segment_loops = face_curve_segment_loops(face.face_id, &segments)?;
246 loop_coverage.record_face(face, &segment_loops);
247 let (sample_report, sizing_report) = append_curve_driven_face_elements(
248 face,
249 frame,
250 &segment_loops,
251 None,
252 &mut nodes,
253 &mut elements,
254 );
255 exact_cad_sample_node_count += sample_report.accepted_count;
256 rejected_exact_cad_sample_count += sample_report.rejected_count;
257 sizing_sample_node_count += sizing_report.accepted_count;
258 rejected_sizing_sample_count += sizing_report.rejected_count;
259 }
260
261 Ok(SurfaceDiscretization {
262 nodes,
263 elements,
264 curve_boundary_validation: Some(curve_boundary_validation),
265 loop_coverage: Some(loop_coverage.finish()),
266 cad_curve_boundary_provenance: None,
267 exact_cad_sample_node_count,
268 rejected_exact_cad_sample_count,
269 sizing_sample_node_count,
270 rejected_sizing_sample_count,
271 })
272}
273
274pub fn discretize_cad_topology_surfaces_with_curves(
275 cad_topology: &CadTopologyModel,
276 topology: &SourceTopologyModel,
277 cad_evaluation: &CadEvaluationModel,
278 curves: &CurveDiscretization,
279 options: SurfaceDiscretizationOptions,
280) -> Result<SurfaceDiscretization, SurfaceDiscretizationError> {
281 discretize_cad_topology_surfaces_with_curve_inputs(
282 cad_topology,
283 topology,
284 cad_evaluation,
285 curves,
286 None,
287 options,
288 None,
289 )
290}
291
292pub fn discretize_cad_topology_surfaces_with_cad_curves(
293 cad_topology: &CadTopologyModel,
294 topology: &SourceTopologyModel,
295 cad_evaluation: &CadEvaluationModel,
296 cad_curves: &CadCurveDiscretization,
297 options: SurfaceDiscretizationOptions,
298) -> Result<SurfaceDiscretization, SurfaceDiscretizationError> {
299 discretize_cad_topology_surfaces_with_cad_curves_and_sizing(
300 cad_topology,
301 topology,
302 cad_evaluation,
303 cad_curves,
304 options,
305 None,
306 )
307}
308
309pub fn discretize_cad_topology_surfaces_with_cad_curves_and_sizing(
310 cad_topology: &CadTopologyModel,
311 topology: &SourceTopologyModel,
312 cad_evaluation: &CadEvaluationModel,
313 cad_curves: &CadCurveDiscretization,
314 options: SurfaceDiscretizationOptions,
315 sizing: Option<&MeshSizingField>,
316) -> Result<SurfaceDiscretization, SurfaceDiscretizationError> {
317 discretize_cad_topology_surfaces_with_curve_inputs(
318 cad_topology,
319 topology,
320 cad_evaluation,
321 &cad_curves.curves,
322 Some(&cad_curves.edge_provenance),
323 options,
324 sizing,
325 )
326}
327
328fn discretize_cad_topology_surfaces_with_curve_inputs(
329 cad_topology: &CadTopologyModel,
330 topology: &SourceTopologyModel,
331 cad_evaluation: &CadEvaluationModel,
332 curves: &CurveDiscretization,
333 cad_curve_edge_provenance: Option<&[CadCurveEdgeProvenance]>,
334 options: SurfaceDiscretizationOptions,
335 sizing: Option<&MeshSizingField>,
336) -> Result<SurfaceDiscretization, SurfaceDiscretizationError> {
337 let curve_boundary_validation =
338 validate_curve_discretization(topology, curves, cad_curve_validation_options())
339 .map_err(SurfaceDiscretizationError::InvalidCurveBoundary)?;
340 let mut nodes = topology
341 .vertices
342 .iter()
343 .map(|vertex| SurfaceNode {
344 node_id: vertex.vertex_id,
345 source_vertex_id: vertex.vertex_id,
346 coordinates_m: vertex.coordinates_m,
347 })
348 .collect::<Vec<_>>();
349 let frames_by_cad_face = cad_evaluation
350 .face_frames
351 .iter()
352 .map(|frame| (frame.face_id.clone(), frame))
353 .collect::<BTreeMap<_, _>>();
354 let topology_edges = topology
355 .edges
356 .iter()
357 .map(|edge| (edge.edge_id, edge))
358 .collect::<BTreeMap<_, _>>();
359 let curve_nodes_by_edge = curve_nodes_by_source_edge(curves);
360 let mut curve_node_to_surface_node = BTreeMap::<u32, u32>::new();
361 let cad_curve_provenance_by_source_edge = cad_curve_edge_provenance.map(|edge_provenance| {
362 edge_provenance
363 .iter()
364 .map(|provenance| (provenance.source_edge_id, provenance))
365 .collect::<BTreeMap<_, _>>()
366 });
367 let cad_loops_by_id = cad_topology
368 .loops
369 .iter()
370 .map(|cad_loop| (cad_loop.entity_id.id.as_str(), cad_loop))
371 .collect::<BTreeMap<_, _>>();
372
373 let mut elements = Vec::<SurfaceElement>::new();
374 let mut loop_coverage = SurfaceLoopCoverageAccumulator::new(cad_topology.faces.len());
375 let mut cad_curve_boundary_provenance = cad_curve_provenance_by_source_edge
376 .as_ref()
377 .map(|_| SurfaceCadCurveBoundaryProvenanceAccumulator::new());
378 let mut exact_cad_sample_node_count = 0_usize;
379 let mut rejected_exact_cad_sample_count = 0_usize;
380 let mut sizing_sample_node_count = 0_usize;
381 let mut rejected_sizing_sample_count = 0_usize;
382 for cad_face in &cad_topology.faces {
383 let face = cad_face_surface_seed(cad_face, &cad_loops_by_id, &topology_edges)?;
384 validate_face_vertices(topology, &face)?;
385 let frame = frames_by_cad_face.get(&cad_face.entity_id.id).ok_or(
386 SurfaceDiscretizationError::MissingCadFaceFrame {
387 source_face_id: face.face_id,
388 },
389 )?;
390 let source_edge_ids = cad_face_loop_source_edge_ids(cad_face, &cad_loops_by_id)?;
391 let segments = curve_segments_for_source_edges(
392 &topology_edges,
393 &curve_nodes_by_edge,
394 face.face_id,
395 &source_edge_ids,
396 options.max_curve_segments_per_edge.max(1),
397 &mut nodes,
398 &mut curve_node_to_surface_node,
399 )?;
400 let segment_loops = face_curve_segment_loops(face.face_id, &segments)?;
401 loop_coverage.record_face_edges(&source_edge_ids, &segment_loops);
402 if let (Some(accumulator), Some(provenance_by_source_edge)) = (
403 cad_curve_boundary_provenance.as_mut(),
404 cad_curve_provenance_by_source_edge.as_ref(),
405 ) {
406 accumulator.record_segments(&segment_loops, provenance_by_source_edge)?;
407 }
408 let (sample_report, sizing_report) = append_curve_driven_face_elements(
409 &face,
410 frame,
411 &segment_loops,
412 sizing,
413 &mut nodes,
414 &mut elements,
415 );
416 exact_cad_sample_node_count += sample_report.accepted_count;
417 rejected_exact_cad_sample_count += sample_report.rejected_count;
418 sizing_sample_node_count += sizing_report.accepted_count;
419 rejected_sizing_sample_count += sizing_report.rejected_count;
420 }
421
422 Ok(SurfaceDiscretization {
423 nodes,
424 elements,
425 curve_boundary_validation: Some(curve_boundary_validation),
426 loop_coverage: Some(loop_coverage.finish()),
427 cad_curve_boundary_provenance: cad_curve_boundary_provenance
428 .map(SurfaceCadCurveBoundaryProvenanceAccumulator::finish),
429 exact_cad_sample_node_count,
430 rejected_exact_cad_sample_count,
431 sizing_sample_node_count,
432 rejected_sizing_sample_count,
433 })
434}
435
436fn cad_face_surface_seed(
437 cad_face: &CadFace,
438 cad_loops_by_id: &BTreeMap<&str, &CadLoop>,
439 topology_edges: &BTreeMap<u32, &SourceTopologyEdge>,
440) -> Result<SourceTopologyFace, SurfaceDiscretizationError> {
441 let face_id = cad_face.source_face_ids.first().copied().ok_or_else(|| {
442 SurfaceDiscretizationError::CadFaceWithoutSourceFaces {
443 cad_face_id: cad_face.entity_id.id.clone(),
444 }
445 })?;
446 let source_edge_ids = cad_face_loop_source_edge_ids(cad_face, cad_loops_by_id)?;
447 if source_edge_ids.len() < 3 {
448 return Err(SurfaceDiscretizationError::EmptyFaceLoop { face_id });
449 }
450 let mut node_ids = Vec::<u32>::new();
451 for source_edge_id in &source_edge_ids {
452 let edge = topology_edges.get(source_edge_id).ok_or(
453 SurfaceDiscretizationError::MissingFaceEdge {
454 face_id,
455 edge_id: *source_edge_id,
456 },
457 )?;
458 for node_id in edge.node_ids {
459 if !node_ids.contains(&node_id) {
460 node_ids.push(node_id);
461 }
462 }
463 }
464 if node_ids.len() < 3 {
465 return Err(SurfaceDiscretizationError::EmptyFaceLoop { face_id });
466 }
467 Ok(SourceTopologyFace {
468 face_id,
469 source_triangle_id: face_id,
470 node_ids: [node_ids[0], node_ids[1], node_ids[2]],
471 edge_ids: [source_edge_ids[0], source_edge_ids[1], source_edge_ids[2]],
472 region_ids: cad_face.region_ids.clone(),
473 material_region_ids: cad_face.material_region_ids.clone(),
474 area_m2: cad_face.area_m2,
475 unit_normal: cad_face.unit_normal,
476 })
477}
478
479fn cad_curve_validation_options() -> CurveValidationOptions {
480 CurveValidationOptions {
481 require_source_edge_projection: false,
482 ..CurveValidationOptions::default()
483 }
484}
485
486fn cad_face_loop_source_edge_ids(
487 cad_face: &CadFace,
488 cad_loops_by_id: &BTreeMap<&str, &CadLoop>,
489) -> Result<Vec<u32>, SurfaceDiscretizationError> {
490 if cad_face.loop_ids.is_empty() {
491 return Err(SurfaceDiscretizationError::CadFaceWithoutLoops {
492 cad_face_id: cad_face.entity_id.id.clone(),
493 });
494 }
495 let mut source_edge_ids = Vec::<u32>::new();
496 for loop_id in &cad_face.loop_ids {
497 let cad_loop = cad_loops_by_id.get(loop_id.as_str()).ok_or_else(|| {
498 SurfaceDiscretizationError::MissingCadFaceLoop {
499 cad_face_id: cad_face.entity_id.id.clone(),
500 loop_id: loop_id.clone(),
501 }
502 })?;
503 if cad_loop.face_id != cad_face.entity_id.id {
504 return Err(SurfaceDiscretizationError::CadLoopFaceMismatch {
505 cad_face_id: cad_face.entity_id.id.clone(),
506 loop_id: loop_id.clone(),
507 actual_face_id: cad_loop.face_id.clone(),
508 });
509 }
510 source_edge_ids.extend(
511 cad_loop
512 .edge_ids
513 .iter()
514 .map(|loop_edge_id| {
515 loop_edge_id
516 .strip_prefix("cad_edge_")
517 .and_then(|edge_id| edge_id.parse::<u32>().ok())
518 .ok_or_else(|| SurfaceDiscretizationError::InvalidCadLoopEdgeId {
519 cad_face_id: cad_face.entity_id.id.clone(),
520 loop_edge_id: loop_edge_id.clone(),
521 })
522 })
523 .collect::<Result<Vec<_>, _>>()?,
524 );
525 }
526 Ok(source_edge_ids)
527}
528
529#[derive(Debug, Clone, Copy, PartialEq)]
530struct FaceTriangulationPoint {
531 node_id: u32,
532 uv: [f64; 2],
533}
534
535fn validate_face_vertices(
536 topology: &SourceTopologyModel,
537 face: &SourceTopologyFace,
538) -> Result<(), SurfaceDiscretizationError> {
539 for node_id in face.node_ids {
540 if topology
541 .vertices
542 .get(node_id as usize)
543 .is_none_or(|vertex| vertex.vertex_id != node_id)
544 {
545 return Err(SurfaceDiscretizationError::MissingFaceVertex {
546 face_id: face.face_id,
547 node_id,
548 });
549 }
550 }
551 Ok(())
552}
553
554#[cfg(test)]
555mod tests;