use super::super::{
HelixCircleProfile, HelixCurveConstruction, HelixFrame, HelixLineProfile,
HelixPathConstruction, HelixSurfaceConstruction, HelixSurfaceProfile,
ProceduralCurveDefinition,
};
use crate::math::{Point3, Vector3};
use serde_json::json;
fn curve(radius: f64) -> HelixCurveConstruction {
HelixCurveConstruction::try_new(
[0.0, 1.0],
HelixFrame {
center: Point3::new(0.0, 0.0, 0.0),
major: Vector3::new(radius, 0.0, 0.0),
minor: Vector3::new(0.0, radius, 0.0),
pitch: Vector3::new(0.0, 0.0, 1.0),
axis: Vector3::new(0.0, 0.0, 1.0),
},
0.0,
None,
)
.unwrap()
}
#[test]
fn numerical_audit_helix_curve_rejects_overflowing_unequal_radii() {
let maximum = f64::MAX;
assert!(HelixCurveConstruction::try_new(
[0.0, 1.0],
HelixFrame {
center: Point3::new(0.0, 0.0, 0.0),
major: Vector3::new(maximum, maximum, 0.0),
minor: Vector3::new(maximum, maximum, maximum),
pitch: Vector3::new(0.0, 0.0, 1.0),
axis: Vector3::new(0.0, 0.0, 1.0),
},
0.0,
None,
)
.is_err());
}
#[test]
fn numerical_audit_helix_line_admits_finite_nonzero_small_directions() {
for magnitude in [1e-200, f64::from_bits(1)] {
assert!(HelixLineProfile::try_new(Vector3::new(magnitude, 0.0, 0.0)).is_ok());
}
}
#[test]
fn helix_curve_admission_rejects_the_validator_numeric_states() {
let valid = curve(1.0);
let range = *valid.angle_range();
let center = *valid.center().as_raw();
let major = *valid.major();
let minor = *valid.minor();
let pitch = *valid.pitch();
let apex = valid.apex_factor();
let axis = *valid.axis();
for range in [[1.0, 0.0], [f64::NAN, 1.0], [0.0, f64::INFINITY]] {
assert!(HelixCurveConstruction::try_new(
range,
HelixFrame {
center,
major: major.get(),
minor: minor.get(),
pitch: pitch.get(),
axis: axis.get()
},
apex.get(),
None
)
.is_err());
}
assert!(HelixCurveConstruction::try_new(
[0.0, 0.0],
HelixFrame {
center,
major: major.get(),
minor: minor.get(),
pitch: pitch.get(),
axis: axis.get()
},
apex.get(),
None
)
.is_ok());
for vector in [
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(f64::EPSILON, 0.0, 0.0),
] {
assert!(HelixCurveConstruction::try_new(
range.get(),
HelixFrame {
center,
major: vector,
minor: minor.get(),
pitch: pitch.get(),
axis: axis.get()
},
apex.get(),
None
)
.is_err());
assert!(HelixCurveConstruction::try_new(
range.get(),
HelixFrame {
center,
major: major.get(),
minor: minor.get(),
pitch: pitch.get(),
axis: vector
},
apex.get(),
None
)
.is_err());
}
assert!(HelixCurveConstruction::try_new(
range.get(),
HelixFrame {
center,
major: major.get(),
minor: Vector3::new(0.0, 2.0, 0.0),
pitch: pitch.get(),
axis: axis.get()
},
apex.get(),
None
)
.is_err());
let wire = serde_json::to_value(ProceduralCurveDefinition::Helix(valid)).unwrap();
assert_eq!(wire["kind"], "helix");
assert_eq!(
serde_json::from_value::<ProceduralCurveDefinition>(wire.clone()).unwrap(),
ProceduralCurveDefinition::Helix(valid)
);
for (field, invalid) in [
("angle_range", json!([1.0, 0.0])),
("axis", json!({"x": 0.0, "y": 0.0, "z": 0.0})),
] {
let mut bad = wire.clone();
bad[field] = invalid;
assert!(serde_json::from_value::<ProceduralCurveDefinition>(bad).is_err());
}
}
#[test]
fn helix_curve_scaling_is_atomic_and_reversal_preserves_admission() {
let mut value = curve(1.0);
let old = value;
for scale in [0.0, f64::INFINITY, f64::NAN] {
assert!(value.try_scale_lengths(scale).is_err());
assert_eq!(value, old);
}
value.try_reverse_parameterization().unwrap();
assert_eq!(value.angle_range().get(), [-1.0, 0.0]);
value.try_reverse_parameterization().unwrap();
assert_eq!(value, old);
value.try_scale_lengths(2.0).unwrap();
assert_eq!(*value.major(), Vector3::new(2.0, 0.0, 0.0));
let mut huge = curve(1.0e200);
let old = huge;
assert!(huge.try_scale_lengths(1.0e200).is_err());
assert_eq!(huge, old);
}
#[test]
fn helix_reversal_refuses_a_zero_radius_at_the_new_start_atomically() {
let mut value = HelixCurveConstruction::try_new(
[0.0, std::f64::consts::TAU],
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(0.0, 0.0, 1.0),
},
-1.0,
None,
)
.unwrap();
let original = value;
assert!(value.try_reverse_parameterization().is_err());
assert_eq!(value, original);
}
#[test]
fn helix_reversal_accepts_an_overflowing_finite_angle_span() {
let mut value = HelixCurveConstruction::try_new(
[-f64::MAX, f64::MAX],
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, 0.0),
axis: Vector3::new(0.0, 0.0, 1.0),
},
0.0,
None,
)
.unwrap();
value.try_reverse_parameterization().unwrap();
assert_eq!(value.angle_range().get(), [-f64::MAX, f64::MAX]);
}
#[test]
fn helix_surface_and_curve_keep_distinct_radius_tolerances() {
const RADIUS_DIFFERENCE: f64 = 5.0e-7;
let center = Point3::new(0.0, 0.0, 0.0);
let major = Vector3::new(1000.0, 0.0, 0.0);
let minor = Vector3::new(0.0, 1000.0 + RADIUS_DIFFERENCE, 0.0);
let pitch = Vector3::new(0.0, 0.0, 1.0);
let axis = Vector3::new(0.0, 0.0, 1.0);
assert!(HelixPathConstruction::try_new(
[0.0, 1.0],
HelixFrame {
center,
major,
minor,
pitch,
axis
},
0.0
)
.is_ok());
assert!(HelixCurveConstruction::try_new(
[0.0, 1.0],
HelixFrame {
center,
major,
minor,
pitch,
axis
},
0.0,
None
)
.is_err());
assert!(HelixPathConstruction::try_new(
[0.0, 1.0],
HelixFrame {
center,
major,
minor: Vector3::new(0.0, 1001.0, 0.0),
pitch,
axis
},
0.0
)
.is_err());
}
#[test]
fn helix_surface_preserves_finite_directed_ranges_and_signed_profiles() {
let path = HelixPathConstruction::try_new(
[1.0, -1.0],
HelixFrame {
center: Point3::new(0.0, 0.0, 0.0),
major: Vector3::new(2.0, 0.0, 0.0),
minor: Vector3::new(0.0, 2.0, 0.0),
pitch: Vector3::new(0.0, 0.0, 1.0),
axis: Vector3::new(0.0, 0.0, 0.0),
},
0.0,
)
.unwrap();
let profile = HelixSurfaceProfile::Circle(HelixCircleProfile::try_new(-1.0, -2.0).unwrap());
let surface =
HelixSurfaceConstruction::try_new([1.0, -1.0], [2.0, -2.0], path, profile).unwrap();
let wire = serde_json::to_value(surface).unwrap();
assert_eq!(
wire["profile"],
json!({"kind": "circle", "length": -1.0, "radius": -2.0})
);
assert_eq!(
serde_json::from_value::<HelixSurfaceConstruction>(wire.clone()).unwrap(),
surface
);
let mut bad = wire;
bad["profile"]["radius"] = json!(0.0);
assert!(serde_json::from_value::<HelixSurfaceConstruction>(bad).is_err());
assert!(HelixCircleProfile::try_new(1.0, 0.0).is_err());
assert!(HelixCircleProfile::try_new(f64::INFINITY, 1.0).is_err());
assert!(HelixLineProfile::try_new(Vector3::new(0.0, 0.0, 0.0)).is_err());
assert!(HelixLineProfile::try_new(Vector3::new(2.0, 0.0, 0.0)).is_ok());
}
#[test]
fn numerical_followup_circular_helix_admits_finite_radial_scales() {
use crate::geometry::{HelixFrame, HelixPathConstruction};
use crate::math::{Point3, Vector3};
for radius in [1.0, 1e200, 1e-200] {
let frame = HelixFrame {
center: Point3::new(0., 0., 0.),
major: Vector3::new(radius, 0., 0.),
minor: Vector3::new(0., radius, 0.),
pitch: Vector3::new(0., 0., 1.),
axis: Vector3::new(0., 0., 1.),
};
assert!(HelixPathConstruction::try_new([0., 1.], frame, 0.).is_ok());
}
}
#[test]
fn helix_path_unit_axis_keeps_the_raw_construction_and_wire() {
use crate::units::UnitVector3;
let frame = HelixFrame {
center: Point3::new(1.0, 2.0, 3.0),
major: Vector3::new(2.0, 0.0, 0.0),
minor: Vector3::new(0.0, 2.0, 0.0),
pitch: Vector3::new(0.0, 0.0, 1.0),
axis: Vector3::new(0.0, 0.0, 1.0),
};
let raw = HelixPathConstruction::try_new([0.0, 1.0], frame, 0.5).unwrap();
let typed = HelixPathConstruction::try_with_unit_axis(
[0.0, 1.0],
HelixFrame {
center: frame.center,
major: frame.major,
minor: frame.minor,
pitch: frame.pitch,
axis: UnitVector3::Z_AXIS,
},
0.5,
)
.unwrap();
assert_eq!(raw, typed);
assert_eq!(
serde_json::to_vec(&raw).unwrap(),
serde_json::to_vec(&typed).unwrap()
);
}
#[test]
fn numerical_followup_helix_path_requires_two_equal_nonzero_radii() {
for radius in [1e-200, 1e-10, 1.0, 1e200] {
for ratio in [0.0, 0.5, 1.0] {
let result = HelixPathConstruction::try_new(
[0., 1.],
HelixFrame {
center: Point3::new(0., 0., 0.),
major: Vector3::new(radius, 0., 0.),
minor: Vector3::new(0., ratio * radius, 0.),
pitch: Vector3::new(0., 0., 1.),
axis: Vector3::new(0., 0., 1.),
},
0.,
);
assert_eq!(result.is_ok(), ratio == 1.);
}
}
}
#[test]
fn helix_curve_finite_center_returns_the_admitted_center() {
use crate::features::FinitePoint3;
for center in [
Point3::new(0.0, 0.0, 0.0),
Point3::new(-0.0, 1.5, -2.0e300),
Point3::new(f64::MAX, 5.0e-324, -f64::MAX),
] {
let helix = HelixCurveConstruction::try_new(
[0.0, 1.0],
HelixFrame {
center,
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(0.0, 0.0, 1.0),
},
0.0,
None,
)
.unwrap();
let finite = *helix.center();
assert_eq!(
[finite.x, finite.y, finite.z].map(f64::to_bits),
[center.x, center.y, center.z].map(f64::to_bits)
);
assert_eq!(FinitePoint3::new(center), Some(finite));
}
}
#[test]
fn a_helix_line_profile_holds_its_admitted_direction() {
let direction = Vector3::new(0.0, -2.0, 0.5);
let profile = HelixLineProfile::try_new(direction).unwrap();
assert_eq!(
profile.direction(),
crate::features::FiniteVector3::new(direction).unwrap()
);
assert!(HelixLineProfile::try_new(Vector3::new(0.0, 0.0, 0.0)).is_err());
assert!(HelixLineProfile::try_new(Vector3::new(f64::NAN, 1.0, 0.0)).is_err());
}