use axiolid_contracts::{GeomError, GeomResult};
use axiolid_core::{Point2, Point3, Scalar, Vec3};
use axiolid_curve::{Curve2, Curve3};
use super::{
breaks_inside, continuity_breaks2, continuity_breaks3, curve_derivative_bounds2,
curve_derivative_bounds3, inflate, intrinsic_derivative_bounds2, taylor, CurveDerivativeBounds,
ROUNDING,
};
use crate::curve::{derivative2, derivative3, evaluate2, evaluate3};
fn unit(w: Vec3) -> GeomResult<Vec3> {
let size = w.length();
if !(size.is_finite() && size > 0.0) {
return Err(GeomError::Degenerate(
"an offset curve whose basis tangent vanishes, or lies along its reference \
direction: the offset direction is undefined there"
.into(),
));
}
Ok(w / size)
}
fn reference(direction: Vec3) -> Option<Vec3> {
let size = direction.length();
(size.is_finite() && size > 0.0).then(|| direction / size)
}
pub fn offset_point2(curve: &Curve2, distance: Scalar, t: Scalar) -> GeomResult<Point2> {
let p = evaluate2(curve, t)?;
let d = derivative2(curve, t)?;
let left = unit(Vec3::new(-d.y, d.x, 0.0))?;
Ok(Point2::new(
p.x + distance * left.x,
p.y + distance * left.y,
))
}
pub fn offset_point3(
curve: &Curve3,
distance: Scalar,
reference_direction: Vec3,
t: Scalar,
) -> GeomResult<Point3> {
let v = reference(reference_direction).ok_or_else(|| {
GeomError::Degenerate("an offset curve's reference direction is zero".into())
})?;
let p = evaluate3(curve, t)?;
let d = derivative3(curve, t)?;
Ok(p + distance * unit(v.cross(d))?)
}
fn offset_chord(
h: Scalar,
bounds: CurveDerivativeBounds,
middle: Scalar,
distance: Scalar,
) -> Option<Scalar> {
let h = h.abs();
let r = middle * (1.0 - ROUNDING) - inflate(0.5 * h * bounds.second);
if r.is_nan() || r <= 0.0 {
return None;
}
let (x, y) = (bounds.second / r, bounds.third / r);
let second = bounds.second + distance.abs() * (y + 3.0 * x * x);
let out = inflate(taylor(h, inflate(second)));
out.is_finite().then_some(out)
}
#[must_use]
pub fn offset_chord_bound2(
curve: &Curve2,
distance: Scalar,
a: Scalar,
b: Scalar,
) -> Option<Scalar> {
if !(a.is_finite() && b.is_finite() && distance.is_finite()) {
return None;
}
if a == b {
return Some(0.0);
}
if breaks_inside(&continuity_breaks2(curve, 2), a, b) {
return None;
}
let bounds = match curve {
Curve2::Polyline(_) => return Some(0.0),
Curve2::Intrinsic(intrinsic) => intrinsic_derivative_bounds2(intrinsic, a, b)?,
_ => curve_derivative_bounds2(curve, a, b)?,
};
let middle = derivative2(curve, 0.5 * (a + b)).ok()?.length();
offset_chord(b - a, bounds, middle, distance)
}
#[must_use]
pub fn offset_chord_bound3(
curve: &Curve3,
distance: Scalar,
reference_direction: Vec3,
a: Scalar,
b: Scalar,
) -> Option<Scalar> {
if !(a.is_finite() && b.is_finite() && distance.is_finite()) {
return None;
}
let v = reference(reference_direction)?;
if a == b {
return Some(0.0);
}
if breaks_inside(&continuity_breaks3(curve, 2), a, b) {
return None;
}
let bounds = match curve {
Curve3::Polyline(_) => return Some(0.0),
_ => curve_derivative_bounds3(curve, a, b)?,
};
let middle = v.cross(derivative3(curve, 0.5 * (a + b)).ok()?).length();
offset_chord(b - a, bounds, middle, distance)
}