Skip to main content

runmat_meshing_surface/param_tri/
mod.rs

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;