cadmpeg-ir 0.6.1

CAD data model, validation, diffing, and codec API for cadmpeg.
use super::super::{
    PcurveGeometry, ProceduralCurveDefinition, TolerantIntersectionConstruction,
    TolerantIntersectionParameterization,
};
use crate::{ids::SurfaceId, math::Point3};
use serde_json::json;

fn supports() -> [SurfaceId; 2] {
    ["test:model:surface#1", "test:model:surface#2"].map(|id| SurfaceId::mint(id).unwrap())
}

#[test]
fn tolerant_intersection_rejects_equal_supports_and_invalid_numeric_bounds() {
    let endpoints = [Point3::new(0.0, 0.0, 0.0); 2];
    assert!(TolerantIntersectionConstruction::try_new(supports(), endpoints, 0.0).is_ok());
    let [first, _] = supports();
    assert!(
        TolerantIntersectionConstruction::try_new([first.clone(), first], endpoints, 0.0).is_err()
    );
    for tolerance in [-1.0, f64::NAN, f64::INFINITY] {
        assert!(
            TolerantIntersectionConstruction::try_new(supports(), endpoints, tolerance).is_err()
        );
    }
    for coordinate in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
        assert!(TolerantIntersectionConstruction::try_new(
            supports(),
            [Point3::new(coordinate, 0.0, 0.0), endpoints[1]],
            0.0
        )
        .is_err());
    }
}

#[test]
fn tolerant_intersection_from_parts_refuses_only_equal_supports() {
    let endpoints = [Point3::new(1.0, -0.0, 5.0e-324), Point3::new(2.0, 3.0, 4.0)]
        .map(|point| crate::features::FinitePoint3::new(point).unwrap());
    let tolerance = crate::scalar::NonNegativeReal::new(0.25).unwrap();
    let built = TolerantIntersectionConstruction::from_parts(supports(), endpoints, tolerance)
        .expect("distinct supports");
    assert_eq!(
        Ok(built),
        TolerantIntersectionConstruction::try_new(
            supports(),
            endpoints.map(crate::features::FinitePoint3::get),
            0.25
        )
    );
    let [first, _] = supports();
    assert_eq!(
        TolerantIntersectionConstruction::from_parts([first.clone(), first], endpoints, tolerance),
        Err("tolerant intersection supports must be distinct")
    );
}

#[test]
fn a_tolerant_parameterization_hands_back_its_admitted_interval() {
    let pcurves =
        std::array::from_fn(|_| PcurveGeometry::Line(crate::geometry::pcurve::LinePcurve::U_AXIS));
    let parameterization =
        TolerantIntersectionParameterization::try_new(pcurves, [-1.0, 2.5]).expect("strict range");
    let interval = parameterization.parameter_range();
    assert_eq!(interval.endpoints(), [-1.0, 2.5]);
    assert_eq!(
        interval.endpoints(),
        parameterization.parameter_range().endpoints()
    );
}

#[test]
fn tolerant_parameterization_requires_a_finite_strict_interval() {
    let pcurves = || {
        std::array::from_fn(|_| PcurveGeometry::Line(crate::geometry::pcurve::LinePcurve::U_AXIS))
    };
    assert!(TolerantIntersectionParameterization::try_new(pcurves(), [-1.0, 1.0]).is_ok());
    for range in [
        [0.0, 0.0],
        [1.0, 0.0],
        [f64::NAN, 1.0],
        [0.0, f64::INFINITY],
    ] {
        assert!(TolerantIntersectionParameterization::try_new(pcurves(), range).is_err());
    }
}

#[test]
fn tolerant_intersection_wire_retains_flat_fields_and_rejects_invalid_admission() {
    let wire = json!({
        "kind": "tolerant_intersection",
        "construction": {
            "supports": supports(),
            "endpoints": [{"x": 0.0, "y": 0.0, "z": 0.0}, {"x": 0.0, "y": 0.0, "z": 0.0}],
            "tolerance": 0.0
        }
    });
    let value: ProceduralCurveDefinition = serde_json::from_value(wire.clone()).unwrap();
    assert_eq!(serde_json::to_value(value).unwrap(), wire);
    let mut invalid = wire.clone();
    invalid["construction"]["supports"][1] = invalid["construction"]["supports"][0].clone();
    assert!(serde_json::from_value::<ProceduralCurveDefinition>(invalid).is_err());
    let mut invalid = wire;
    invalid["construction"]["tolerance"] = json!(-1.0);
    assert!(serde_json::from_value::<ProceduralCurveDefinition>(invalid).is_err());
    let parameterization = TolerantIntersectionParameterization::try_new(
        [
            PcurveGeometry::Line(crate::geometry::pcurve::LinePcurve::U_AXIS),
            PcurveGeometry::Line(crate::geometry::pcurve::LinePcurve::U_AXIS),
        ],
        [0.0, 1.0],
    )
    .unwrap();
    let mut invalid = serde_json::to_value(parameterization).unwrap();
    invalid["parameter_range"] = json!([1.0, 1.0]);
    assert!(serde_json::from_value::<TolerantIntersectionParameterization>(invalid).is_err());
}

#[test]
fn tolerant_intersection_hands_back_its_admitted_tolerance() {
    let endpoints = [Point3::new(0.0, 0.0, 0.0); 2];
    for tolerance in [-0.0, 0.0, 1.0e-5, f64::MAX] {
        let intersection =
            TolerantIntersectionConstruction::try_new(supports(), endpoints, tolerance)
                .expect("a finite non-negative tolerance");
        let admitted = intersection.tolerance();
        assert_eq!(admitted.get().to_bits(), tolerance.to_bits());
        assert_eq!(
            admitted.get().to_bits(),
            intersection.tolerance().get().to_bits()
        );
    }
}

#[test]
fn a_tolerant_intersection_holds_its_admitted_endpoints() {
    use crate::features::FinitePoint3;

    let endpoints = [Point3::new(1.0, 2.0, 3.0), Point3::new(-4.0, 5.0, 6.0)];
    let construction =
        TolerantIntersectionConstruction::try_new(supports(), endpoints, 0.5).unwrap();
    assert_eq!(
        *construction.endpoints(),
        endpoints.map(|point| FinitePoint3::new(point).unwrap())
    );
    assert_eq!(
        serde_json::to_value(&construction).unwrap()["endpoints"],
        json!(endpoints)
    );
}