use axiolid_core::{Point2, Point3, Scalar, Tolerance, Vec3};
use axiolid_surface::Surface;
use core::f64::consts::TAU;
pub(crate) fn same_support(a: &Surface, b: &Surface, tolerance: Tolerance) -> bool {
let eps = tolerance.linear().max(1e-9);
let parallel = |x: Vec3, y: Vec3| x.normalize().cross(y.normalize()).length() <= 1e-9;
let same_way = |x: Vec3, y: Vec3| parallel(x, y) && x.dot(y) > 0.0;
let on_axis = |o1: Point3, o2: Point3, z: Vec3| (o2 - o1).cross(z.normalize()).length() <= eps;
let close = |x: Scalar, y: Scalar| (x - y).abs() <= eps;
match (a, b) {
(Surface::Plane(p), Surface::Plane(q)) => {
parallel(p.frame.z, q.frame.z)
&& (q.frame.origin - p.frame.origin)
.dot(p.frame.z.normalize())
.abs()
<= eps
}
(Surface::Cylinder(p), Surface::Cylinder(q)) => {
close(p.radius, q.radius)
&& parallel(p.frame.z, q.frame.z)
&& on_axis(p.frame.origin, q.frame.origin, p.frame.z)
}
(Surface::EllipticalCylinder(p), Surface::EllipticalCylinder(q)) => {
if !(parallel(p.frame.z, q.frame.z)
&& on_axis(p.frame.origin, q.frame.origin, p.frame.z))
{
return false;
}
(close(p.semi_axis_x, q.semi_axis_x)
&& close(p.semi_axis_y, q.semi_axis_y)
&& parallel(p.frame.x, q.frame.x))
|| (close(p.semi_axis_x, q.semi_axis_y)
&& close(p.semi_axis_y, q.semi_axis_x)
&& parallel(p.frame.x, q.frame.y))
}
(Surface::Cone(p), Surface::Cone(q)) => {
let (tp, tq) = (p.semi_angle.tan(), q.semi_angle.tan());
if tp == 0.0 || tq == 0.0 {
return false;
}
let apex = |c: &axiolid_surface::Cone, t: Scalar| {
c.frame.origin - c.frame.z.normalize() * (c.radius / t)
};
(apex(p, tp) - apex(q, tq)).length() <= eps
&& same_way(p.frame.z * tp.signum(), q.frame.z * tq.signum())
&& (tp.abs() - tq.abs()).abs() <= 1e-12 * (1.0 + tp.abs())
}
(Surface::Sphere(p), Surface::Sphere(q)) => {
close(p.radius, q.radius) && (p.frame.origin - q.frame.origin).length() <= eps
}
(Surface::Torus(p), Surface::Torus(q)) => {
close(p.major_radius, q.major_radius)
&& close(p.minor_radius, q.minor_radius)
&& (p.frame.origin - q.frame.origin).length() <= eps
&& parallel(p.frame.z, q.frame.z)
}
_ => a == b,
}
}
pub(crate) fn periods(surface: &Surface) -> (bool, bool) {
match surface {
Surface::Cylinder(_)
| Surface::EllipticalCylinder(_)
| Surface::Cone(_)
| Surface::Sphere(_) => (true, false),
Surface::Torus(_) => (true, true),
_ => (false, false),
}
}
pub(crate) fn window(surface: &Surface, lo: Point2, hi: Point2) -> (Point2, Point2) {
let (pu, pv) = periods(surface);
let pad = 1e-6 * (1.0 + (hi - lo).length());
let (mut a, mut b) = (lo - Point2::splat(pad), hi + Point2::splat(pad));
let offset = 0.123_456_789_012_345_67;
if pu && hi.x - lo.x >= TAU - 1e-9 {
(a.x, b.x) = (lo.x + offset, lo.x + offset + TAU);
}
if pv && hi.y - lo.y >= TAU - 1e-9 {
(a.y, b.y) = (lo.y + offset, lo.y + offset + TAU);
}
if matches!(surface, Surface::Sphere(_)) {
a.y = a.y.max(-core::f64::consts::FRAC_PI_2);
b.y = b.y.min(core::f64::consts::FRAC_PI_2);
}
(a, b)
}
pub(crate) fn cleaned(surface: &Surface) -> Surface {
let clean_vec = |v: Vec3, scale: Scalar| {
let floor = 8.0 * Scalar::EPSILON * scale;
let c = |x: Scalar| if x.abs() <= floor { 0.0 } else { x };
Vec3::new(c(v.x), c(v.y), c(v.z))
};
let frame = |f: axiolid_core::Frame3, size: Scalar| {
let scale = f.origin.abs().max_element().max(size);
axiolid_core::Frame3 {
origin: clean_vec(f.origin, scale),
x: clean_vec(f.x, f.x.abs().max_element()),
y: clean_vec(f.y, f.y.abs().max_element()),
z: clean_vec(f.z, f.z.abs().max_element()),
}
};
match surface {
Surface::Plane(p) => Surface::Plane(axiolid_surface::Plane {
frame: frame(p.frame, 1.0),
}),
Surface::Cylinder(c) => Surface::Cylinder(axiolid_surface::Cylinder {
frame: frame(c.frame, c.radius),
..*c
}),
Surface::EllipticalCylinder(c) => {
Surface::EllipticalCylinder(axiolid_surface::EllipticalCylinder {
frame: frame(c.frame, c.semi_axis_x.max(c.semi_axis_y)),
..*c
})
}
Surface::Cone(c) => Surface::Cone(axiolid_surface::Cone {
frame: frame(c.frame, c.radius.abs()),
..*c
}),
Surface::Sphere(s) => Surface::Sphere(axiolid_surface::Sphere {
frame: frame(s.frame, s.radius),
..*s
}),
Surface::Torus(t) => Surface::Torus(axiolid_surface::Torus {
frame: frame(t.frame, t.major_radius + t.minor_radius),
..*t
}),
other => other.clone(),
}
}