use crate::eval::model_curve_point_by_id;
use crate::eval::model_surface_point;
use crate::eval::model_surface_point_by_id;
use crate::eval::pcurve_tangent;
use crate::eval::EvaluationFailure;
use crate::features::FinitePoint3;
use crate::geometry::analytic::LineCurve;
use crate::geometry::pcurve::{CirclePcurve, LinePcurve, OffsetPcurve, PcurveGeometry};
use crate::geometry::surface_payloads::{
AxisRevolutionSurfaceConstruction, ExtrusionSurfaceConstruction,
LinearSweepSurfaceConstruction, RevolutionSurfaceConstruction, SumSurfaceConstruction,
};
use crate::geometry::{
CacheContract, Curve, CurveGeometry, HelixCurveConstruction, HelixFrame, PlacedCurve,
ProceduralCurve, ProceduralCurveDefinition, ProceduralSurface, ProceduralSurfaceDefinition,
SolvedCurveGeometry, Surface, SurfaceGeometry,
};
use crate::ids::{CurveId, ProceduralCurveId, ProceduralSurfaceId, SurfaceId};
use crate::math::{Point2, Point3, Vector3};
use crate::transform::Transform;
use crate::CadIr;
fn edge_line() -> CurveGeometry {
CurveGeometry::Solved(SolvedCurveGeometry::Line(
LineCurve::try_new(Point3::new(f64::MAX, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0))
.expect("line fixture"),
))
}
fn curve_surface_model(
curves: Vec<CurveGeometry>,
definition: impl FnOnce(&[CurveId]) -> ProceduralSurfaceDefinition,
) -> (CadIr, SurfaceId, SurfaceGeometry) {
let ids = (0..curves.len())
.map(|index| CurveId::mint(format!("test:model:curve#{index}")).expect("valid identity"))
.collect::<Vec<_>>();
let surface_id = SurfaceId::mint("test:model:surface#procedural").expect("valid identity");
let construction =
ProceduralSurfaceId::mint("test:model:procedural#surface").expect("valid identity");
let geometry = SurfaceGeometry::Procedural {
construction: construction.clone(),
cache: None,
};
let mut ir = CadIr::empty();
ir.model
.curves
.extend(ids.iter().zip(curves).map(|(id, geometry)| Curve {
parameter_range: None,
id: id.clone(),
geometry,
source_object: None,
}));
ir.model.surfaces.push(Surface {
id: surface_id.clone(),
geometry: geometry.clone(),
source_object: None,
});
ir.model
.procedural_surfaces
.push(ProceduralSurface::new(construction, definition(&ids), None));
(ir, surface_id, geometry)
}
fn surface_routes(
ir: &CadIr,
surface: &SurfaceId,
geometry: &SurfaceGeometry,
u: f64,
v: f64,
) -> [Result<FinitePoint3, EvaluationFailure<Point3>>; 3] {
let index = crate::index::ModelIndex::build(ir);
[
model_surface_point(
crate::eval::admission::EvaluationAdmission::Standard,
ir,
geometry,
u,
v,
),
model_surface_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
surface,
u,
v,
),
crate::eval::admission::EvaluationAdmission::Standard.within_work_slice(
&cadmpeg_core::decode::WorkBudget::new(1_000_000),
|admission| model_surface_point_by_id(admission, &index, surface, u, v),
),
]
}
#[test]
fn an_extrusion_whose_directrix_point_overflows_reports_the_point_its_extrusion_reaches() {
let (ir, surface, geometry) = curve_surface_model(vec![edge_line()], |curves| {
ProceduralSurfaceDefinition::Extrusion(
ExtrusionSurfaceConstruction::try_new(
curves[0].clone(),
None,
Vector3::new(0.0, 0.0, 1.0),
None,
CacheContract::from_form(None),
)
.expect("extrusion fixture"),
)
});
for route in surface_routes(&ir, &surface, &geometry, f64::MAX, 2.0) {
assert!(
matches!(
route,
Err(EvaluationFailure::NonFinite(point))
if point.x.is_nan() && point.y == 0.0 && point.z == 2.0
),
"{route:?}"
);
}
for route in surface_routes(&ir, &surface, &geometry, -f64::MAX, 2.0) {
assert_eq!(route.map(FinitePoint3::get), Ok(Point3::new(0.0, 0.0, 2.0)));
}
}
fn overflowing_placed_line() -> CurveGeometry {
CurveGeometry::Solved(SolvedCurveGeometry::Transformed(
PlacedCurve::try_new(
Box::new(SolvedCurveGeometry::Line(
LineCurve::try_new(Point3::new(f64::MAX, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0))
.expect("line fixture"),
)),
Transform::affine([
[1.0, 0.0, 0.0, f64::MAX],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
])
.expect("affine transform"),
)
.expect("placed curve fixture"),
))
}
#[test]
fn a_nurbs_curve_whose_projection_overflows_reports_the_point_it_reached() {
let curve = CurveGeometry::Solved(SolvedCurveGeometry::Nurbs(
crate::geometry::nurbs::NurbsCurve::from_lanes(
1,
vec![0.0, 0.0, 1.0, 1.0],
vec![
Point3::new(1.0e300, 0.0, 0.0),
Point3::new(-1.0e300, 0.0, 0.0),
],
Some(vec![1.0, -1.0 + 2.0_f64.powi(-40)]),
false,
)
.expect("NURBS curve fixture"),
));
assert_eq!(
crate::eval::decode::curve_point(
crate::eval::admission::EvaluationAdmission::Standard,
&curve,
0.5
),
Err(EvaluationFailure::NonFinite(Point3::new(
f64::INFINITY,
0.0,
0.0
)))
);
assert_eq!(
crate::eval::decode::curve_point(
crate::eval::admission::EvaluationAdmission::Standard,
&curve,
2.0
),
Err(EvaluationFailure::NoValue)
);
}
fn procedural_curve_model(
curves: Vec<CurveGeometry>,
definition: impl FnOnce(&[CurveId]) -> ProceduralCurveDefinition,
) -> (CadIr, CurveId) {
let ids = (0..curves.len())
.map(|index| CurveId::mint(format!("test:model:curve#{index}")).expect("valid identity"))
.collect::<Vec<_>>();
let owner = CurveId::mint("test:model:curve#procedural").expect("valid identity");
let construction =
ProceduralCurveId::mint("test:model:procedural#curve").expect("valid identity");
let mut ir = CadIr::empty();
ir.model
.curves
.extend(ids.iter().zip(curves).map(|(id, geometry)| Curve {
parameter_range: None,
id: id.clone(),
geometry,
source_object: None,
}));
ir.model.curves.push(Curve {
parameter_range: None,
id: owner.clone(),
geometry: CurveGeometry::Procedural {
construction: construction.clone(),
cache: None,
},
source_object: None,
});
ir.model
.add_procedural_curve(&owner, ProceduralCurve::new(construction, definition(&ids)))
.expect("procedural curve fixture");
(ir, owner)
}
#[test]
fn a_replica_curve_whose_placement_overflows_reports_the_point_it_reached() {
let line = CurveGeometry::Solved(SolvedCurveGeometry::Line(
LineCurve::try_new(Point3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0))
.expect("line fixture"),
));
let (ir, replica) =
procedural_curve_model(vec![line], |curves| ProceduralCurveDefinition::Replica {
source: curves[0].clone(),
transform: Transform::affine([
[1.0, 0.0, 0.0, f64::MAX],
[0.0, 1.0, 0.0, 2.0],
[0.0, 0.0, 1.0, 0.0],
])
.expect("affine transform"),
});
let index = crate::index::ModelIndex::build(&ir);
assert_eq!(
model_curve_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&replica,
f64::MAX
),
Err(EvaluationFailure::NonFinite(Point3::new(
f64::INFINITY,
2.0,
0.0
)))
);
assert_eq!(
model_curve_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&replica,
-f64::MAX
)
.map(FinitePoint3::get),
Ok(Point3::new(0.0, 2.0, 0.0))
);
}
#[test]
fn a_subset_curve_whose_span_overflows_keeps_its_finite_local_point() {
let line = CurveGeometry::Solved(SolvedCurveGeometry::Line(
LineCurve::try_new(Point3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0))
.expect("line fixture"),
));
let (ir, subset) = procedural_curve_model(vec![line], |curves| {
ProceduralCurveDefinition::Subset(
crate::geometry::curve_payloads::SubsetCurveConstruction::try_new(
curves[0].clone(),
[-f64::MAX, f64::MAX],
true,
None,
)
.expect("subset fixture"),
)
});
let index = crate::index::ModelIndex::build(&ir);
assert_eq!(
model_curve_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&subset,
1.0
)
.map(FinitePoint3::get),
Ok(Point3::new(-f64::MAX, 0.0, 0.0))
);
assert_eq!(
model_curve_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&subset,
f64::MAX
)
.map(FinitePoint3::get),
Ok(Point3::new(0.0, 0.0, 0.0))
);
assert_eq!(
model_curve_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&subset,
-1.0
),
Err(EvaluationFailure::NoValue)
);
}
fn large_helix(apex_factor: f64) -> ProceduralCurveDefinition {
ProceduralCurveDefinition::Helix(
HelixCurveConstruction::try_new(
[0.0, 1.0],
HelixFrame {
center: Point3::new(0.0, 0.0, 0.0),
major: Vector3::new(0.9 * f64::MAX, 0.0, 0.0),
minor: Vector3::new(0.0, 0.9 * f64::MAX, 0.0),
pitch: Vector3::new(0.0, 0.0, 1.0),
axis: Vector3::new(0.0, 0.0, 1.0),
},
apex_factor,
None,
)
.expect("helix fixture"),
)
}
#[test]
fn a_helix_whose_tangent_overflows_has_its_finite_point() {
let (ir, helix) = procedural_curve_model(Vec::new(), |_| large_helix(10.0));
let index = crate::index::ModelIndex::build(&ir);
assert_eq!(
model_curve_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&helix,
0.0
)
.map(FinitePoint3::get),
Ok(Point3::new(0.9 * f64::MAX, 0.0, 0.0))
);
assert_eq!(
model_curve_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&helix,
2.0
),
Err(EvaluationFailure::NoValue)
);
}
#[test]
fn an_extrusion_over_a_helix_whose_tangent_overflows_has_its_finite_point() {
let (mut ir, helix) = procedural_curve_model(Vec::new(), |_| large_helix(10.0));
let surface = SurfaceId::mint("test:model:surface#extrusion").expect("valid identity");
let construction =
ProceduralSurfaceId::mint("test:model:procedural#extrusion").expect("valid identity");
let geometry = SurfaceGeometry::Procedural {
construction: construction.clone(),
cache: None,
};
ir.model.surfaces.push(Surface {
id: surface.clone(),
geometry: geometry.clone(),
source_object: None,
});
ir.model.procedural_surfaces.push(ProceduralSurface::new(
construction,
ProceduralSurfaceDefinition::Extrusion(
ExtrusionSurfaceConstruction::try_new(
helix,
None,
Vector3::new(0.0, 0.0, 1.0),
None,
CacheContract::from_form(None),
)
.expect("extrusion fixture"),
),
None,
));
for route in surface_routes(&ir, &surface, &geometry, 0.0, 2.0) {
assert_eq!(
route.map(FinitePoint3::get),
Ok(Point3::new(0.9 * f64::MAX, 0.0, 2.0))
);
}
}
#[test]
fn a_ruled_surface_whose_rail_point_overflows_reports_the_point_its_rule_reaches() {
let second = CurveGeometry::Solved(SolvedCurveGeometry::Line(
LineCurve::try_new(Point3::new(0.0, 0.0, 1.0), Vector3::new(0.0, 1.0, 0.0))
.expect("line fixture"),
));
let (ir, surface, geometry) =
curve_surface_model(vec![overflowing_placed_line(), second], |curves| {
ProceduralSurfaceDefinition::Ruled {
first: curves[0].clone(),
second: curves[1].clone(),
cache: None,
}
});
for route in surface_routes(&ir, &surface, &geometry, 3.0, 0.5) {
assert!(
matches!(
route,
Err(EvaluationFailure::NonFinite(point))
if point.x.is_nan() && point.y == 3.0 && point.z == 0.5
),
"{route:?}"
);
}
}
#[test]
fn a_sum_surface_whose_curve_point_overflows_reports_the_point_its_sum_reaches() {
let second = CurveGeometry::Solved(SolvedCurveGeometry::Line(
LineCurve::try_new(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0))
.expect("line fixture"),
));
let (ir, surface, geometry) =
curve_surface_model(vec![overflowing_placed_line(), second], |curves| {
ProceduralSurfaceDefinition::Sum(
SumSurfaceConstruction::try_new(
curves[0].clone(),
curves[1].clone(),
Vector3::new(0.0, 0.0, 0.0),
CacheContract::from_form(None),
)
.expect("sum fixture"),
)
});
for route in surface_routes(&ir, &surface, &geometry, 3.0, 2.0) {
assert_eq!(
route,
Err(EvaluationFailure::NonFinite(Point3::new(
f64::INFINITY,
3.0,
2.0
)))
);
}
}
#[test]
fn a_linear_sweep_whose_directrix_point_overflows_reports_the_point_its_sweep_reaches() {
let (ir, surface, geometry) = curve_surface_model(vec![edge_line()], |curves| {
ProceduralSurfaceDefinition::LinearSweep(
LinearSweepSurfaceConstruction::try_new(curves[0].clone(), Vector3::new(0.0, 0.0, 1.0))
.expect("linear sweep fixture"),
)
});
for route in surface_routes(&ir, &surface, &geometry, f64::MAX, 2.0) {
assert!(
matches!(
route,
Err(EvaluationFailure::NonFinite(point))
if point.x.is_nan() && point.y == 0.0 && point.z == 2.0
),
"{route:?}"
);
}
for route in surface_routes(&ir, &surface, &geometry, 0.0, f64::NAN) {
assert_eq!(route, Err(EvaluationFailure::NoValue));
}
}
#[test]
fn a_revolution_whose_directrix_point_overflows_reports_the_point_its_revolution_reaches() {
let (ir, surface, geometry) = curve_surface_model(vec![overflowing_placed_line()], |curves| {
ProceduralSurfaceDefinition::Revolution(
RevolutionSurfaceConstruction::try_new(
curves[0].clone(),
(
FinitePoint3::new(Point3::new(0.0, 0.0, 0.0)).expect("finite origin"),
crate::units::UnitVector3::new(Vector3::new(0.0, 0.0, 1.0)).expect("unit axis"),
),
[0.0, std::f64::consts::TAU],
None,
None,
false,
CacheContract::from_form(None),
)
.expect("revolution fixture"),
)
});
for route in surface_routes(&ir, &surface, &geometry, 3.0, 0.0) {
assert!(
matches!(route, Err(EvaluationFailure::NonFinite(point)) if !point.x.is_finite()),
"{route:?}"
);
}
}
#[test]
fn an_axis_revolution_whose_directrix_point_overflows_reports_the_point_its_revolution_reaches() {
let (ir, surface, geometry) = curve_surface_model(vec![overflowing_placed_line()], |curves| {
ProceduralSurfaceDefinition::AxisRevolution(
AxisRevolutionSurfaceConstruction::try_new(
curves[0].clone(),
Point3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
)
.expect("axis revolution fixture"),
)
});
for route in surface_routes(&ir, &surface, &geometry, 0.0, 3.0) {
assert!(
matches!(route, Err(EvaluationFailure::NonFinite(point)) if !point.x.is_finite()),
"{route:?}"
);
}
}
#[test]
fn an_offset_pcurve_whose_basis_acceleration_overflows_reports_its_tangent_as_left_the_finite_range(
) {
let basis = PcurveGeometry::Circle(
CirclePcurve::try_new(
Point2::new(-f64::MAX, 0.0),
Point2::new(1.0, 0.0),
Point2::new(1.0, 1.0),
f64::MAX,
)
.expect("circle pcurve fixture"),
);
let parameter = std::f64::consts::FRAC_PI_4;
assert!(pcurve_tangent(
crate::eval::admission::EvaluationAdmission::Standard,
&basis,
parameter
)
.is_ok());
let offset = PcurveGeometry::Offset(
OffsetPcurve::try_new(1.0, Box::new(basis)).expect("offset pcurve fixture"),
);
assert!(crate::eval::decode::pcurve_uv(
crate::eval::admission::EvaluationAdmission::Standard,
&offset,
parameter
)
.is_ok());
assert!(matches!(
pcurve_tangent(crate::eval::admission::EvaluationAdmission::Standard, &offset, parameter),
Err(EvaluationFailure::NonFinite(tangent)) if tangent.u.is_nan() && tangent.v.is_nan()
));
}
#[test]
fn an_offset_pcurve_over_an_offset_basis_has_no_tangent() {
let inner = PcurveGeometry::Offset(
OffsetPcurve::try_new(
1.0,
Box::new(PcurveGeometry::Line(
LinePcurve::try_new(Point2::new(0.0, 0.0), Point2::new(1.0, 0.0))
.expect("line pcurve fixture"),
)),
)
.expect("offset pcurve fixture"),
);
let outer =
PcurveGeometry::Offset(OffsetPcurve::try_new(1.0, Box::new(inner)).expect("offset pcurve"));
assert_eq!(
crate::eval::decode::pcurve_uv(
crate::eval::admission::EvaluationAdmission::Standard,
&outer,
0.5
)
.map(crate::units::FinitePoint2::get),
Ok(Point2::new(0.5, 2.0))
);
assert_eq!(
pcurve_tangent(
crate::eval::admission::EvaluationAdmission::Standard,
&outer,
0.5
),
Err(EvaluationFailure::NoValue)
);
}
#[test]
fn an_extrusion_whose_directrix_tangent_overflows_has_its_finite_point_on_every_route() {
let directrix = CurveGeometry::Solved(SolvedCurveGeometry::Transformed(
PlacedCurve::try_new(
Box::new(SolvedCurveGeometry::Line(
LineCurve::try_new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
),
)
.expect("line fixture"),
)),
Transform::affine([
[f64::MAX, f64::MAX, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
])
.expect("affine transform"),
)
.expect("placed curve fixture"),
));
assert!(crate::eval::decode::curve_tangent(
crate::eval::admission::EvaluationAdmission::Standard,
&directrix,
0.0
)
.is_err());
let (ir, surface, geometry) = curve_surface_model(vec![directrix], |curves| {
ProceduralSurfaceDefinition::Extrusion(
ExtrusionSurfaceConstruction::try_new(
curves[0].clone(),
None,
Vector3::new(0.0, 0.0, 1.0),
None,
CacheContract::from_form(None),
)
.expect("extrusion fixture"),
)
});
for route in surface_routes(&ir, &surface, &geometry, 0.0, 2.0) {
assert_eq!(route.map(FinitePoint3::get), Ok(Point3::new(0.0, 0.0, 2.0)));
}
}
fn overflowing_acceleration_circle() -> CurveGeometry {
CurveGeometry::Solved(SolvedCurveGeometry::Transformed(
PlacedCurve::try_new(
Box::new(SolvedCurveGeometry::Circle(
crate::geometry::analytic::CircleCurve::try_new(
Point3::new(-2.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 0.0),
2.0,
)
.expect("circle fixture"),
)),
Transform::affine([
[f64::MAX, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
])
.expect("affine transform"),
)
.expect("placed curve fixture"),
))
}
fn line(origin: Point3, direction: Vector3) -> CurveGeometry {
CurveGeometry::Solved(SolvedCurveGeometry::Line(
LineCurve::try_new(origin, direction).expect("line fixture"),
))
}
fn assert_second_order_alone_overflows(
(ir, surface, geometry): (CadIr, SurfaceId, SurfaceGeometry),
(u, v): (f64, f64),
point: Point3,
[du, dv]: [Vector3; 2],
) {
assert!(crate::eval::curve_second_derivative(
crate::eval::admission::EvaluationAdmission::Standard,
&overflowing_acceleration_circle(),
0.0
)
.is_err());
for route in surface_routes(&ir, &surface, &geometry, u, v) {
assert_eq!(route.map(FinitePoint3::get), Ok(point));
}
let index = crate::index::ModelIndex::build(&ir);
let budget = cadmpeg_core::decode::WorkBudget::new(1_000_000);
let expected = Ok(crate::eval::SurfacePartials { point, du, dv });
assert_eq!(
crate::eval::model_surface_partials_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&surface,
u,
v
)
.map(crate::eval::SurfacePartials::into_raw),
expected
);
assert_eq!(
crate::eval::admission::EvaluationAdmission::Standard
.within_work_slice(&budget, |admission| {
crate::eval::model_surface_partials_by_id(admission, &index, &surface, u, v)
})
.map(crate::eval::SurfacePartials::into_raw),
expected
);
assert_eq!(
crate::eval::model_surface_second_partials_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&surface,
u,
v
)
.map(crate::eval::SurfaceSecondPartials::into_raw),
Err(EvaluationFailure::NonFinite(point))
);
}
#[test]
fn an_extrusion_whose_directrix_acceleration_overflows_keeps_its_point_and_first_partials() {
assert_second_order_alone_overflows(
curve_surface_model(vec![overflowing_acceleration_circle()], |curves| {
ProceduralSurfaceDefinition::Extrusion(
ExtrusionSurfaceConstruction::try_new(
curves[0].clone(),
None,
Vector3::new(0.0, 0.0, 1.0),
None,
CacheContract::from_form(None),
)
.expect("extrusion fixture"),
)
}),
(0.0, 2.0),
Point3::new(0.0, 0.0, 2.0),
[Vector3::new(0.0, 2.0, 0.0), Vector3::new(0.0, 0.0, 1.0)],
);
}
#[test]
fn a_linear_sweep_whose_directrix_acceleration_overflows_keeps_its_point_and_first_partials() {
assert_second_order_alone_overflows(
curve_surface_model(vec![overflowing_acceleration_circle()], |curves| {
ProceduralSurfaceDefinition::LinearSweep(
LinearSweepSurfaceConstruction::try_new(
curves[0].clone(),
Vector3::new(0.0, 0.0, 1.0),
)
.expect("linear sweep fixture"),
)
}),
(0.0, 2.0),
Point3::new(0.0, 0.0, 2.0),
[Vector3::new(0.0, 2.0, 0.0), Vector3::new(0.0, 0.0, 1.0)],
);
}
#[test]
fn a_ruled_surface_whose_rail_acceleration_overflows_keeps_its_point_and_first_partials() {
assert_second_order_alone_overflows(
curve_surface_model(
vec![
overflowing_acceleration_circle(),
line(Point3::new(0.0, 0.0, 2.0), Vector3::new(0.0, 1.0, 0.0)),
],
|curves| ProceduralSurfaceDefinition::Ruled {
first: curves[0].clone(),
second: curves[1].clone(),
cache: None,
},
),
(0.0, 0.5),
Point3::new(0.0, 0.0, 1.0),
[Vector3::new(0.0, 1.5, 0.0), Vector3::new(0.0, 0.0, 2.0)],
);
}
#[test]
fn a_sum_surface_whose_curve_acceleration_overflows_keeps_its_point_and_first_partials() {
assert_second_order_alone_overflows(
curve_surface_model(
vec![
overflowing_acceleration_circle(),
line(Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 1.0)),
],
|curves| {
ProceduralSurfaceDefinition::Sum(
SumSurfaceConstruction::try_new(
curves[0].clone(),
curves[1].clone(),
Vector3::new(0.0, 0.0, 0.0),
CacheContract::from_form(None),
)
.expect("sum fixture"),
)
},
),
(0.0, 2.0),
Point3::new(0.0, 0.0, 2.0),
[Vector3::new(0.0, 2.0, 0.0), Vector3::new(0.0, 0.0, 1.0)],
);
}
#[test]
fn a_revolution_whose_directrix_acceleration_overflows_keeps_its_point_and_first_partials() {
assert_second_order_alone_overflows(
curve_surface_model(vec![overflowing_acceleration_circle()], |curves| {
ProceduralSurfaceDefinition::Revolution(
RevolutionSurfaceConstruction::try_new(
curves[0].clone(),
(
FinitePoint3::new(Point3::new(1.0, 0.0, 0.0)).expect("finite origin"),
crate::units::UnitVector3::new(Vector3::new(0.0, 0.0, 1.0))
.expect("unit axis"),
),
[0.0, std::f64::consts::TAU],
None,
None,
false,
CacheContract::from_form(None),
)
.expect("revolution fixture"),
)
}),
(0.0, 0.0),
Point3::new(0.0, 0.0, 0.0),
[Vector3::new(0.0, 2.0, 0.0), Vector3::new(0.0, -1.0, 0.0)],
);
}
#[test]
fn an_axis_revolution_whose_directrix_acceleration_overflows_keeps_its_point_and_first_partials() {
assert_second_order_alone_overflows(
curve_surface_model(vec![overflowing_acceleration_circle()], |curves| {
ProceduralSurfaceDefinition::AxisRevolution(
AxisRevolutionSurfaceConstruction::try_new(
curves[0].clone(),
Point3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
)
.expect("axis revolution fixture"),
)
}),
(0.0, 0.0),
Point3::new(0.0, 0.0, 0.0),
[Vector3::new(0.0, -1.0, 0.0), Vector3::new(0.0, 2.0, 0.0)],
);
}
#[test]
fn a_hyperbola_whose_scaled_cosh_alone_overflows_keeps_its_point_and_second_derivative() {
let hyperbola = CurveGeometry::Solved(SolvedCurveGeometry::Hyperbola(
crate::geometry::analytic::HyperbolaCurve::try_new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 0.0),
1.0,
f64::MAX,
)
.expect("hyperbola fixture"),
));
let t = 1.0e-7_f64;
let lanes = |value: Vector3| {
assert_eq!(value.x, t.cosh());
assert!((value.y / (f64::MAX * t.sinh()) - 1.0).abs() <= 4.0 * f64::EPSILON);
assert_eq!(value.z, 0.0);
};
lanes(
crate::eval::decode::curve_point(
crate::eval::admission::EvaluationAdmission::Standard,
&hyperbola,
t,
)
.expect("point")
.get()
.vector_from(Point3::new(0.0, 0.0, 0.0)),
);
lanes(
crate::eval::curve_second_derivative(
crate::eval::admission::EvaluationAdmission::Standard,
&hyperbola,
t,
)
.expect("second derivative")
.get(),
);
assert_eq!(
crate::eval::decode::curve_tangent(
crate::eval::admission::EvaluationAdmission::Standard,
&hyperbola,
t
),
Err(EvaluationFailure::NonFinite(()))
);
}
#[test]
fn an_extrusion_over_a_helix_whose_acceleration_overflows_keeps_its_first_partials() {
let (mut ir, helix) = procedural_curve_model(Vec::new(), |_| large_helix(5.0));
let surface = SurfaceId::mint("test:model:surface#extrusion").expect("valid identity");
let construction =
ProceduralSurfaceId::mint("test:model:procedural#extrusion").expect("valid identity");
ir.model.surfaces.push(Surface {
id: surface.clone(),
geometry: SurfaceGeometry::Procedural {
construction: construction.clone(),
cache: None,
},
source_object: None,
});
ir.model.procedural_surfaces.push(ProceduralSurface::new(
construction,
ProceduralSurfaceDefinition::Extrusion(
ExtrusionSurfaceConstruction::try_new(
helix,
None,
Vector3::new(0.0, 0.0, 1.0),
None,
CacheContract::from_form(None),
)
.expect("extrusion fixture"),
),
None,
));
let index = crate::index::ModelIndex::build(&ir);
let point = Point3::new(0.9 * f64::MAX, 0.0, 2.0);
let partials = crate::eval::model_surface_partials_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&surface,
0.0,
2.0,
)
.expect("first partials")
.into_raw();
assert_eq!(partials.point, point);
let radial = 5.0 / std::f64::consts::TAU * 0.9 * f64::MAX;
assert!((partials.du.x / radial - 1.0).abs() <= 8.0 * f64::EPSILON);
assert!((partials.du.y / (0.9 * f64::MAX) - 1.0).abs() <= 8.0 * f64::EPSILON);
assert!((partials.du.z * std::f64::consts::TAU - 1.0).abs() <= 8.0 * f64::EPSILON);
assert_eq!(partials.dv, Vector3::new(0.0, 0.0, 1.0));
assert_eq!(
crate::eval::model_surface_second_partials_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&surface,
0.0,
2.0
)
.map(crate::eval::SurfaceSecondPartials::into_raw),
Err(EvaluationFailure::NonFinite(point))
);
}
#[test]
fn a_helix_whose_axis_length_overflows_has_its_point() {
let (ir, helix) = procedural_curve_model(Vec::new(), |_| {
ProceduralCurveDefinition::Helix(
HelixCurveConstruction::try_new(
[0.0, 1.0],
HelixFrame {
center: Point3::new(0.0, 0.0, 0.0),
major: Vector3::new(1.0, 0.0, 0.0),
minor: Vector3::new(0.0, 1.0, 0.0),
pitch: Vector3::new(0.0, 0.0, 1.0),
axis: Vector3::new(f64::MAX, f64::MAX, 0.0),
},
0.0,
None,
)
.expect("helix fixture"),
)
});
let index = crate::index::ModelIndex::build(&ir);
assert_eq!(
model_curve_point_by_id(
crate::eval::admission::EvaluationAdmission::Standard,
&index,
&helix,
0.0
)
.map(FinitePoint3::get),
Ok(Point3::new(1.0, 0.0, 0.0))
);
}