use axiolid_core::{Scalar, Tolerance};
use axiolid_measure::volume_properties;
use axiolid_mesh::TriMesh;
use axiolid_primitive::Primitive;
use axiolid_reference::primitive::tessellate_primitive;
fn volume(mesh: &TriMesh, tol: Tolerance) -> Scalar {
volume_properties(mesh, tol)
.expect("a primitive must be a closed two-manifold solid")
.signed_volume
}
fn tol_for(chord: Scalar) -> Tolerance {
Tolerance::new((chord * 1e-3).max(1e-12), 1e-9).expect("tolerance")
}
#[test]
fn a_block_has_exactly_its_analytic_volume() {
let p = Primitive::Block {
x: 3.0,
y: 2.0,
z: 4.0,
};
let mesh = tessellate_primitive(&p, Tolerance::MILLIMETRE).expect("block");
let v = volume(&mesh, Tolerance::MILLIMETRE);
assert!((v - 24.0).abs() < 1e-9, "block volume {v}");
}
#[test]
fn a_pyramid_is_one_third_base_times_height() {
let p = Primitive::Pyramid {
x: 3.0,
y: 3.0,
height: 4.0,
};
let mesh = tessellate_primitive(&p, Tolerance::MILLIMETRE).expect("pyramid");
let v = volume(&mesh, Tolerance::MILLIMETRE);
assert!((v - 12.0).abs() < 1e-9, "pyramid volume {v}");
}
#[test]
fn a_cylinder_converges_on_pi_r_squared_h() {
let chord = 1e-5;
let p = Primitive::Cylinder {
radius: 2.0,
height: 5.0,
};
let mesh = tessellate_primitive(&p, Tolerance::new(chord, 1e-9).expect("t")).expect("cylinder");
let v = volume(&mesh, tol_for(chord));
let want = core::f64::consts::PI * 4.0 * 5.0;
assert!(
v < want,
"an inscribed cylinder under-estimates: {v} vs {want}"
);
assert!((want - v) / want < 1e-5, "cylinder volume {v} vs {want}");
}
#[test]
fn a_cone_is_one_third_of_its_cylinder() {
let chord = 1e-5;
let p = Primitive::Cone {
radius: 2.0,
height: 5.0,
};
let mesh = tessellate_primitive(&p, Tolerance::new(chord, 1e-9).expect("t")).expect("cone");
let v = volume(&mesh, tol_for(chord));
let want = core::f64::consts::PI * 4.0 * 5.0 / 3.0;
assert!((want - v) / want < 1e-5, "cone volume {v} vs {want}");
}
#[test]
fn a_sphere_converges_on_four_thirds_pi_r_cubed() {
let chord = 1e-5;
let p = Primitive::Sphere { radius: 2.0 };
let mesh = tessellate_primitive(&p, Tolerance::new(chord, 1e-9).expect("t")).expect("sphere");
let v = volume(&mesh, tol_for(chord));
let want = 4.0 / 3.0 * core::f64::consts::PI * 8.0;
assert!((want - v) / want < 1e-4, "sphere volume {v} vs {want}");
}
#[test]
fn a_tighter_budget_converges() {
let want = core::f64::consts::PI * 4.0 * 5.0;
let mut previous = Scalar::INFINITY;
for chord in [1e-2, 1e-3, 1e-4, 1e-5] {
let p = Primitive::Cylinder {
radius: 2.0,
height: 5.0,
};
let mesh =
tessellate_primitive(&p, Tolerance::new(chord, 1e-9).expect("t")).expect("cylinder");
let error = (want - volume(&mesh, tol_for(chord))).abs();
assert!(error < previous, "error must shrink: {error} vs {previous}");
previous = error;
}
}
#[test]
fn a_non_positive_extent_is_refused() {
let p = Primitive::Sphere { radius: 0.0 };
assert!(tessellate_primitive(&p, Tolerance::MILLIMETRE).is_err());
}