use axiolid_brep::ExactBRep;
use axiolid_construct::boolean_exact::{boolean_arc_prisms_exact, ArcPrism};
use axiolid_construct::extrude::extrude_profile_exact;
use axiolid_core::{BooleanOperator, Point2, Tolerance, Vec3};
use axiolid_measure::{boundary_clearance, boundary_distance, Clearance};
use axiolid_overlay::ArcRing;
use axiolid_profile::{CircleProfile, Profile};
fn tol() -> Tolerance {
Tolerance::METRE
}
fn cylinder(radius: f64, height: f64) -> ExactBRep {
extrude_profile_exact(
&Profile::Circle(CircleProfile {
radius,
thickness: None,
}),
Vec3::Z,
height,
tol(),
)
.expect("a cylinder")
}
fn block(corners: &[Point2], bottom: f64, top: f64) -> ExactBRep {
let prism = |scale: f64| {
let centre = corners.iter().fold(Point2::ZERO, |sum, p| sum + *p) / corners.len() as f64;
ArcPrism {
section: ArcRing::from_points(
&corners
.iter()
.map(|p| centre + (*p - centre) * scale)
.collect::<Vec<_>>(),
),
bottom,
top,
}
};
boolean_arc_prisms_exact(
&prism(1.0),
&prism(2.0),
BooleanOperator::Intersection,
tol(),
)
.expect("a block")
}
fn contains(bounds: &axiolid_measure::DistanceBounds, expected: f64) {
assert!(
bounds.lower <= expected + 1e-12 && expected <= bounds.upper + 1e-12,
"[{}, {}] must contain {expected}",
bounds.lower,
bounds.upper
);
assert!(((bounds.point_a - bounds.point_b).length() - bounds.upper).abs() < 1e-12);
}
#[test]
fn a_cylinder_wall_and_a_turned_block_close_on_the_wall_interior() {
let n = Point2::new(1.0, 1.0) / 2.0_f64.sqrt();
let t = Point2::new(-n.y, n.x);
let corner = |a: f64, b: f64| n * a + t * b;
let turned = block(
&[
corner(3.0, -1.0),
corner(5.0, -1.0),
corner(5.0, 1.0),
corner(3.0, 1.0),
],
1.0,
2.0,
);
let bounds = boundary_distance(&cylinder(1.0, 4.0), &turned, 1e-9, tol()).expect("bounded");
contains(&bounds, 2.0);
assert!(
bounds.upper - bounds.lower <= 1e-9,
"interval [{}, {}] did not close",
bounds.lower,
bounds.upper
);
assert!(bounds.point_a.z > 1.0 - 1e-4 && bounds.point_a.z < 2.0 + 1e-4);
assert!((bounds.point_a.x - n.x).abs() < 1e-4 && (bounds.point_a.y - n.y).abs() < 1e-4);
}
#[test]
fn a_block_over_a_disc_cap_closes_on_the_cap_interior() {
let small = block(
&[
Point2::new(-0.2, -0.2),
Point2::new(0.2, -0.2),
Point2::new(0.2, 0.2),
Point2::new(-0.2, 0.2),
],
2.5,
3.0,
);
let bounds = boundary_distance(&cylinder(1.0, 2.0), &small, 1e-9, tol()).expect("bounded");
contains(&bounds, 0.5);
assert!(bounds.upper - bounds.lower <= 1e-9);
}
#[test]
fn clearance_is_decided_only_when_the_interval_clears_the_limit() {
let near = block(
&[
Point2::new(3.0, -1.0),
Point2::new(4.0, -1.0),
Point2::new(4.0, 1.0),
Point2::new(3.0, 1.0),
],
1.0,
2.0,
);
let wall = cylinder(1.0, 4.0);
let (_, below) = boundary_clearance(&wall, &near, 2.001, tol()).expect("decided");
assert_eq!(below, Clearance::Below);
let (_, above) = boundary_clearance(&wall, &near, 1.999, tol()).expect("decided");
assert_eq!(above, Clearance::Above);
let (bounds, at) = boundary_clearance(&wall, &near, 2.0, tol()).expect("bounded");
assert_eq!(at, Clearance::Indeterminate, "{bounds:?}");
contains(&bounds, 2.0);
}
#[test]
fn touching_boundaries_measure_zero() {
let a = block(
&[
Point2::new(0.0, 0.0),
Point2::new(1.0, 0.0),
Point2::new(1.0, 1.0),
Point2::new(0.0, 1.0),
],
0.0,
1.0,
);
let b = block(
&[
Point2::new(1.0, 0.0),
Point2::new(2.0, 0.0),
Point2::new(2.0, 1.0),
Point2::new(1.0, 1.0),
],
0.0,
1.0,
);
let bounds = boundary_distance(&a, &b, 1e-12, tol()).expect("bounded");
assert_eq!(bounds.lower, 0.0);
assert!(bounds.upper <= 1e-12, "{bounds:?}");
}
#[test]
fn two_parallel_columns_are_their_axis_gap_less_both_radii() {
let column = |cx: f64, r: f64| {
let ring = ArcRing::circle(Point2::new(cx, 1.0), r);
let prism = |scale: f64| ArcPrism {
section: ArcRing::circle(Point2::new(cx, 1.0), r * scale),
bottom: 0.0,
top: 3.0,
};
let _ = ring;
boolean_arc_prisms_exact(
&prism(1.0),
&prism(2.0),
BooleanOperator::Intersection,
tol(),
)
.expect("a column")
};
let bounds =
boundary_distance(&column(0.0, 0.5), &column(3.0, 0.25), 1e-6, tol()).expect("bounded");
contains(&bounds, 3.0 - 0.75);
assert!(bounds.upper - bounds.lower <= 1e-6, "{bounds:?}");
}
#[test]
fn a_torus_under_a_slab_closes_on_its_crown() {
use axiolid_construct::revolve_exact::revolve_profile_exact;
use axiolid_core::Point3;
let torus = revolve_profile_exact(
&Profile::Circle(CircleProfile {
radius: 0.5,
thickness: None,
}),
Point3::new(-2.0, 0.0, 0.0),
Vec3::Y,
std::f64::consts::TAU,
tol(),
)
.expect("a torus");
let slab = {
let prism = |scale: f64| ArcPrism {
section: ArcRing::from_points(&[
Point2::new(-2.0 - 5.0 * scale, 1.375 - 0.125 * scale),
Point2::new(-2.0 + 5.0 * scale, 1.375 - 0.125 * scale),
Point2::new(-2.0 + 5.0 * scale, 1.375 + 0.125 * scale),
Point2::new(-2.0 - 5.0 * scale, 1.375 + 0.125 * scale),
]),
bottom: -3.0,
top: 3.0,
};
boolean_arc_prisms_exact(
&prism(1.0),
&prism(2.0),
BooleanOperator::Intersection,
tol(),
)
.expect("a slab")
};
let bounds = boundary_distance(&torus, &slab, 1e-9, tol()).expect("bounded");
contains(&bounds, 0.75);
assert!(bounds.upper - bounds.lower <= 1e-9, "{bounds:?}");
}
#[test]
fn a_block_over_a_hole_measures_to_the_rim_not_the_opening() {
use axiolid_core::{Frame2, Interval, Vec2};
use axiolid_curve::{Circle2, Curve2, Line2};
use axiolid_profile::{Contour, ContourProfile, ProfileSegment};
let quarter = std::f64::consts::FRAC_PI_2;
let hole = Contour::new(
(0..4)
.map(|index| ProfileSegment {
curve: Curve2::Circle(Circle2 {
frame: Frame2 {
origin: Point2::ZERO,
x: Vec2::X,
y: Vec2::Y,
},
radius: 1.0,
}),
domain: Interval::new(quarter * index as f64, quarter * (index + 1) as f64),
same_sense: true,
})
.collect(),
);
let corners = [
Point2::new(-2.0, -2.0),
Point2::new(2.0, -2.0),
Point2::new(2.0, 2.0),
Point2::new(-2.0, 2.0),
];
let outer = Contour::new(
(0..4)
.map(|i| ProfileSegment {
curve: Curve2::Line(Line2 {
origin: corners[i],
direction: corners[(i + 1) % 4] - corners[i],
}),
domain: Interval::UNIT,
same_sense: true,
})
.collect(),
);
let plate = extrude_profile_exact(
&Profile::Contour(ContourProfile {
outer,
holes: vec![hole],
}),
Vec3::Z,
1.0,
tol(),
)
.expect("a plate with a hole");
let small = block(
&[
Point2::new(-0.2, -0.2),
Point2::new(0.2, -0.2),
Point2::new(0.2, 0.2),
Point2::new(-0.2, 0.2),
],
1.5,
2.0,
);
let across = 1.0 - 0.2 * 2.0_f64.sqrt();
let expected = (across * across + 0.25).sqrt();
let bounds = boundary_distance(&plate, &small, 1e-6, tol()).expect("bounded");
contains(&bounds, expected);
assert!(bounds.upper - bounds.lower <= 1e-6, "{bounds:?}");
}
#[test]
fn a_far_block_is_found_on_a_wall_whose_centre_faces_away() {
let c = 10.0 / 2.0_f64.sqrt();
let far = block(
&[
Point2::new(c - 0.1, c - 0.1),
Point2::new(c + 0.1, c - 0.1),
Point2::new(c + 0.1, c + 0.1),
Point2::new(c - 0.1, c + 0.1),
],
0.75,
1.25,
);
let expected = (10.0 - 0.1 * 2.0_f64.sqrt()) - 1.0;
let bounds = boundary_distance(&cylinder(1.0, 2.0), &far, 1e-6, tol()).expect("bounded");
contains(&bounds, expected);
assert!(bounds.upper - bounds.lower <= 1e-6, "{bounds:?}");
}