runmat-meshing-surface 0.6.0

Surface meshing stage for RunMat meshing
Documentation
use super::*;
use super::{
    boundary::FaceCurveSegment,
    elements::append_curve_fan_face_elements,
    geometry::{point_in_polygon_2d, triangle_centroid_2d},
    sampling::{ExactCadSampleSurfaceReport, SizingSampleSurfaceReport},
};
use crate::math::{cross, dot, sub};
use runmat_geometry_core::{CadFaceEvaluationSample, CadFaceEvaluationSampleSource};
use runmat_meshing_cad::{build_cad_evaluation_model, build_cad_topology, SourceTopologyFace};
use runmat_meshing_core::{MeshSizingField, SizingSample};
use runmat_meshing_curve::{
    discretize_cad_topology_curves_with_sizing, discretize_topology_curves,
    CurveDiscretizationOptions, CurveValidationError,
};

mod cad_surfaces;
mod exact_samples;
mod fixtures;
mod loop_topology;

use fixtures::*;

#[test]
fn discretizes_source_faces_as_surface_elements() {
    let surface = discretize_topology_surfaces(
        &single_triangle_topology(),
        SurfaceDiscretizationOptions::default(),
    )
    .expect("surface should discretize");

    assert_eq!(surface.nodes.len(), 3);
    assert_eq!(surface.elements.len(), 1);
    assert_eq!(surface.elements[0].source_face_id, 7);
    assert_eq!(surface.elements[0].cad_face_id, None);
    assert_eq!(surface.elements[0].source_edge_ids, [0, 1, 2]);
    assert_eq!(surface.elements[0].parametric_node_uv, [[0.0, 0.0]; 3]);
    assert_eq!(surface.elements[0].max_projection_error_m, 0.0);
    assert!(surface.curve_boundary_validation.is_none());
    assert!(surface.loop_coverage.is_none());
    assert_eq!(surface.elements[0].region_ids, vec!["face_a".to_string()]);
    assert!((surface.elements[0].area_m2 - 0.5).abs() < 1.0e-12);
}

#[test]
fn rejects_cad_surface_vertex_outside_uv_domain() {
    let topology = single_triangle_topology();
    let mut geometry = geometry_with_face_domain_sample();
    geometry.source_geometry.cad_evaluators[0].faces[0].evaluation_samples = vec![
        CadFaceEvaluationSample {
            source: CadFaceEvaluationSampleSource::BackendQuery,
            point_m: [0.0, 0.0, 0.0],
            uv: Some([0.0, 0.0]),
            projected_point_m: Some([0.0, 0.0, 0.0]),
            unit_normal: Some([0.0, 0.0, 1.0]),
            projection_error_m: Some(0.0),
        },
        CadFaceEvaluationSample {
            source: CadFaceEvaluationSampleSource::BackendQuery,
            point_m: [0.5, 0.0, 0.0],
            uv: Some([0.5, 0.0]),
            projected_point_m: Some([0.5, 0.0, 0.0]),
            unit_normal: Some([0.0, 0.0, 1.0]),
            projection_error_m: Some(0.0),
        },
        CadFaceEvaluationSample {
            source: CadFaceEvaluationSampleSource::BackendQuery,
            point_m: [0.0, 0.5, 0.0],
            uv: Some([0.0, 0.5]),
            projected_point_m: Some([0.0, 0.5, 0.0]),
            unit_normal: Some([0.0, 0.0, 1.0]),
            projection_error_m: Some(0.0),
        },
    ];
    let cad_topology = build_cad_topology(&geometry, &topology).expect("cad topology");
    let cad_evaluation =
        build_cad_evaluation_model(&cad_topology, &topology).expect("cad evaluation");

    let err = discretize_cad_surfaces(
        &topology,
        &cad_evaluation,
        SurfaceDiscretizationOptions::default(),
    )
    .expect_err("out-of-domain source vertex should fail");

    assert_eq!(
        err,
        SurfaceDiscretizationError::CadProjectionOutsideFaceDomain {
            face_id: 7,
            node_id: 1,
        }
    );
}

#[test]
fn cad_topology_surface_inserts_sizing_face_domain_samples() {
    let topology = single_triangle_topology();
    let geometry = geometry_with_face_domain_sample();
    let cad_topology = build_cad_topology(&geometry, &topology).expect("cad topology");
    let cad_evaluation =
        build_cad_evaluation_model(&cad_topology, &topology).expect("cad evaluation");
    let cad_curves = discretize_cad_topology_curves_with_sizing(
        &topology,
        &cad_topology,
        CurveDiscretizationOptions {
            target_size_m: 1.0,
            min_segments_per_edge: 1,
            max_segments_per_edge: 1,
        },
        None,
    )
    .expect("cad curves should discretize");
    let sizing = MeshSizingField {
        samples: vec![SizingSample {
            position_m: [0.2, 0.2, 0.0],
            target_size_m: 0.25,
            reason: Some("structural.load_regions".to_string()),
        }],
        ..MeshSizingField::default()
    };

    let surface = discretize_cad_topology_surfaces_with_cad_curves_and_sizing(
        &cad_topology,
        &topology,
        &cad_evaluation,
        &cad_curves,
        SurfaceDiscretizationOptions {
            max_curve_segments_per_edge: 1,
            ..SurfaceDiscretizationOptions::default()
        },
        Some(&sizing),
    )
    .expect("sizing-aware CAD surface should discretize");

    assert_eq!(surface.sizing_sample_node_count, 1);
    assert_eq!(surface.rejected_sizing_sample_count, 0);
    assert!(surface
        .nodes
        .iter()
        .any(|node| node.coordinates_m == [0.2, 0.2, 0.0]));
    assert!(surface.elements.len() > 1);
}

#[test]
fn rejects_missing_face_vertices() {
    let mut topology = single_triangle_topology();
    topology.vertices.pop();

    let err = discretize_topology_surfaces(&topology, SurfaceDiscretizationOptions::default())
        .expect_err("missing face vertex should fail");

    assert_eq!(
        err,
        SurfaceDiscretizationError::MissingFaceVertex {
            face_id: 7,
            node_id: 2,
        }
    );
}

#[test]
fn rejects_invalid_curve_boundary_before_surface_triangulation() {
    let topology = single_triangle_topology();
    let geometry = geometry_with_face_domain_sample();
    let cad_topology = build_cad_topology(&geometry, &topology).expect("cad topology");
    let cad_evaluation =
        build_cad_evaluation_model(&cad_topology, &topology).expect("cad evaluation");
    let mut curves = discretize_topology_curves(
        &topology,
        CurveDiscretizationOptions {
            target_size_m: 0.5,
            min_segments_per_edge: 1,
            max_segments_per_edge: 8,
        },
    )
    .expect("curves should discretize");
    curves
        .nodes
        .iter_mut()
        .find(|node| node.source_edge_id == 0 && node.parameter == 0.0)
        .expect("source edge endpoint")
        .coordinates_m = [0.01, 0.0, 0.0];

    let err = discretize_cad_surfaces_with_curves(
        &topology,
        &cad_evaluation,
        &curves,
        SurfaceDiscretizationOptions::default(),
    )
    .expect_err("invalid curve boundary should fail before surface triangulation");

    assert!(matches!(
        err,
        SurfaceDiscretizationError::InvalidCurveBoundary(
            CurveValidationError::EndpointDrift { .. }
                | CurveValidationError::ElementLengthMismatch { .. }
        )
    ));
}