use std::f64::consts::TAU;
use axiolid_brep::{ExactBRep, TransformError};
use axiolid_contracts::{GeomError, GeomResult, Operation};
use axiolid_core::{Interval, Mat3, Point3, Scalar, Tolerance, Transform3, Vec3};
use axiolid_curve::Circle3;
use axiolid_profile::{CircleProfile, Profile};
use crate::extrude_exact::extrude_profile_exact;
use crate::revolve_exact::revolve_profile_exact;
use crate::BACKEND_ID;
fn unsupported(input: &'static str) -> GeomError {
GeomError::UnsupportedInput {
backend: BACKEND_ID,
operation: Operation::Sweep,
input,
}
}
fn disk(radius: Scalar, inner_radius: Option<Scalar>) -> GeomResult<Profile> {
if !radius.is_finite() || radius <= 0.0 {
return Err(GeomError::InvalidInput(format!(
"swept disk radius must be positive and finite, got {radius}"
)));
}
match inner_radius {
None | Some(0.0) => Ok(Profile::Circle(CircleProfile {
radius,
thickness: None,
})),
Some(inner) if inner.is_finite() && inner > 0.0 && inner < radius => {
Ok(Profile::Circle(CircleProfile {
radius,
thickness: Some(radius - inner),
}))
}
Some(inner) => Err(GeomError::InvalidInput(format!(
"swept disk inner radius must lie in [0, {radius}), got {inner}"
))),
}
}
pub(crate) fn place(solid: &ExactBRep, transform: &Transform3) -> GeomResult<ExactBRep> {
solid.transformed(transform).map_err(|error| match error {
TransformError::Unsupported(what) => unsupported(what),
other => GeomError::InvalidInput(format!("swept disk placement: {other}")),
})
}
fn perpendicular(d: Vec3) -> Vec3 {
let a = d.abs();
let helper = if a.x <= a.y && a.x <= a.z {
Vec3::X
} else if a.y <= a.z {
Vec3::Y
} else {
Vec3::Z
};
(helper - d * helper.dot(d)).normalize()
}
pub fn swept_disk_along_line_exact(
start: Point3,
end: Point3,
radius: Scalar,
inner_radius: Option<Scalar>,
tolerance: Tolerance,
) -> GeomResult<ExactBRep> {
let profile = disk(radius, inner_radius)?;
let along = end - start;
let length = along.length();
if !length.is_finite() || length <= tolerance.linear() {
return Err(GeomError::InvalidInput(format!(
"swept disk segment must be finite and longer than the tolerance, got {length}"
)));
}
let z = along / length;
let x = perpendicular(z);
let y = z.cross(x);
let profile = match profile {
hollow @ Profile::Circle(CircleProfile {
thickness: Some(_), ..
}) => Profile::Composite(vec![hollow]),
full => full,
};
let local = extrude_profile_exact(&profile, Vec3::Z, length, tolerance)?;
place(
&local,
&Transform3::from_mat3_translation(Mat3::from_cols(x, y, z), start),
)
}
pub fn swept_disk_along_arc_exact(
arc: &Circle3,
span: Interval,
radius: Scalar,
inner_radius: Option<Scalar>,
tolerance: Tolerance,
) -> GeomResult<ExactBRep> {
let profile = disk(radius, inner_radius)?;
let bend = arc.radius;
if !bend.is_finite() || bend <= 0.0 {
return Err(GeomError::InvalidInput(format!(
"swept disk arc radius must be positive and finite, got {bend}"
)));
}
let angle = span.end - span.start;
if !angle.is_finite() || angle == 0.0 {
return Err(GeomError::InvalidInput(format!(
"swept disk arc span must be finite and non-zero, got {angle}"
)));
}
if angle.abs() > TAU + tolerance.linear() {
return Err(unsupported("swept disk along an arc beyond a full turn"));
}
if radius >= bend - tolerance.linear() {
return Err(unsupported("swept disk reaching its arc's axis"));
}
let frame = &arc.frame;
let axes = [frame.x, frame.y, frame.z];
let orthonormal = axes.iter().all(|a| a.is_finite())
&& frame.origin.is_finite()
&& (0..3).all(|i| {
(i..3).all(|j| {
let want = if i == j { 1.0 } else { 0.0 };
(axes[i].dot(axes[j]) - want).abs() <= 1e-12
})
})
&& (frame.x.cross(frame.y) - frame.z).length() <= 1e-12;
if !orthonormal {
return Err(GeomError::InvalidInput(
"swept disk arc frame must be a right-handed orthonormal frame".to_owned(),
));
}
let local = revolve_profile_exact(
&profile,
Point3::new(-bend, 0.0, 0.0),
Vec3::Y,
angle,
tolerance,
)?;
let (sin, cos) = span.start.sin_cos();
let radial = frame.x * cos + frame.y * sin;
let tangent = frame.z.cross(radial);
let rotation = Mat3::from_cols(radial, frame.z, -tangent);
let translation = frame.origin + radial * bend;
place(
&local,
&Transform3::from_mat3_translation(rotation, translation),
)
}