use axiolid_construct::revolve_exact::revolve_profile_exact;
use axiolid_contracts::GeomError;
use axiolid_core::{Point3, Tolerance, Vec3};
use axiolid_profile::{Profile, RectangleProfile};
use axiolid_surface::Surface;
fn rect(x: f64, y: f64) -> Profile {
Profile::Rectangle(RectangleProfile {
x,
y,
thickness: None,
outer_radius: None,
inner_radius: None,
})
}
const TAU: f64 = std::f64::consts::TAU;
#[test]
fn the_revolved_volume_matches_pappus() {
let profile = rect(2.0, 3.0);
let axis_origin = Point3::new(5.0, 0.0, 0.0);
let brep = revolve_profile_exact(&profile, axis_origin, Vec3::Y, TAU, Tolerance::METRE)
.expect("a rectangle clear of the axis revolves exactly");
let pappus = TAU * 5.0 * 6.0;
let annulus = core::f64::consts::PI * (36.0 - 16.0) * 3.0;
assert!(
(pappus - annulus).abs() < 1e-9,
"the two independent formulas must agree: {pappus} vs {annulus}"
);
let radii: Vec<f64> = brep
.surfaces()
.iter()
.filter_map(|s| match s {
Surface::Cylinder(cylinder) => Some(cylinder.radius),
_ => None,
})
.collect();
assert_eq!(radii.len(), 2, "an annular tube has two cylindrical walls");
let mut sorted = radii.clone();
sorted.sort_by(f64::total_cmp);
assert!((sorted[0] - 4.0).abs() < 1e-12, "inner radius: {sorted:?}");
assert!((sorted[1] - 6.0).abs() < 1e-12, "outer radius: {sorted:?}");
}
#[test]
fn a_partial_turn_is_refused() {
let error = revolve_profile_exact(
&rect(2.0, 3.0),
Point3::new(5.0, 0.0, 0.0),
Vec3::Y,
TAU / 4.0,
Tolerance::METRE,
)
.expect_err("a quarter turn is not an annular tube");
assert!(
matches!(
error,
GeomError::UnsupportedInput {
input: "partial-turn exact revolution",
..
}
),
"the refusal must name the gap, got {error:?}"
);
}
#[test]
fn a_profile_crossing_the_axis_is_refused() {
let error = revolve_profile_exact(
&rect(4.0, 2.0),
Point3::ZERO,
Vec3::Y,
TAU,
Tolerance::METRE,
)
.expect_err("the profile straddles the axis");
assert!(
matches!(
error,
GeomError::UnsupportedInput {
input: "exact revolution of a profile touching or crossing the axis",
..
}
),
"got {error:?}"
);
}
#[test]
fn a_straight_sweep_equals_the_extrusion() {
use axiolid_construct::extrude::extrude_profile_exact;
use axiolid_construct::revolve_exact::fixed_reference_sweep_exact;
let profile = rect(2.0, 3.0);
let path = [Point3::ZERO, Point3::new(0.0, 0.0, 4.0)];
let swept = fixed_reference_sweep_exact(&profile, &path, Vec3::X, Tolerance::METRE)
.expect("a straight path sweeps exactly");
let extruded = extrude_profile_exact(&profile, Vec3::Z, 4.0, Tolerance::METRE)
.expect("the same solid as an extrusion");
assert_eq!(
swept.topology().faces().len(),
extruded.topology().faces().len(),
"a straight sweep must be the extrusion, face for face"
);
assert_eq!(swept.surfaces().len(), extruded.surfaces().len());
}
#[test]
fn a_curved_sweep_path_is_refused() {
use axiolid_construct::revolve_exact::fixed_reference_sweep_exact;
let path = [
Point3::ZERO,
Point3::new(1.0, 0.0, 2.0),
Point3::new(0.0, 0.0, 4.0),
];
let error = fixed_reference_sweep_exact(&rect(2.0, 3.0), &path, Vec3::X, Tolerance::METRE)
.expect_err("a bent path is not a linear extrusion");
assert!(
matches!(
error,
GeomError::UnsupportedInput {
input: "exact sweep along a curved directrix",
..
}
),
"got {error:?}"
);
}