use axiolid_contracts::{BackendId, GeomError, GeomResult, Operation};
use axiolid_core::Scalar;
use axiolid_curve::{Curve2, Curve3};
use super::certify::{atomic_seams, CertifiedSeam};
use super::composite::StationCurve;
use super::{station_seams2, station_seams3};
use crate::arc_parameter::ARC_LENGTH_TOLERANCE;
#[cfg(doc)]
use super::CompositeBasis;
pub const WINDOW_CERTIFICATION: Scalar = 1e-3;
pub const SEAM_WINDOW_AMBIGUOUS: &str =
"a station whose seam-snapping window holds two seams of its basis: its seam side does not \
say which one it lies on";
pub const SEAM_WINDOW_STRADDLED: &str =
"a station whose seam-snapping window has a seam on its edge: the seam's certified position \
straddles it, so whether the station lies within the window cannot be told";
fn refused(input: &'static str) -> GeomError {
GeomError::UnsupportedInput {
backend: BackendId::new("axiolid-evaluate"),
operation: Operation::CurveEvaluation,
input,
}
}
pub(crate) fn checked_window(window: Scalar) -> GeomResult<Scalar> {
if window.is_finite() && window >= 0.0 {
Ok(window)
} else {
Err(GeomError::InvalidInput(format!(
"station: the seam-snapping window {window} is negative or not finite"
)))
}
}
pub(crate) fn certification(window: Scalar) -> Scalar {
window * WINDOW_CERTIFICATION
}
pub(crate) fn snap(
seams: &[CertifiedSeam],
distance: Scalar,
window: Scalar,
) -> GeomResult<Scalar> {
let (lo, hi) = (distance - window, distance + window);
let mut inside: Option<Scalar> = None;
let mut ambiguous = false;
let mut straddled = false;
for seam in seams {
let interval = seam.interval;
if interval.upper < lo || interval.lower > hi {
continue;
}
if interval.lower >= lo && interval.upper <= hi {
match inside {
Some(first)
if (first - seam.distance).abs()
> ARC_LENGTH_TOLERANCE * first.abs().max(1.0) =>
{
ambiguous = true;
}
_ => inside = Some(seam.distance),
}
} else {
straddled = true;
}
}
if ambiguous {
return Err(refused(SEAM_WINDOW_AMBIGUOUS));
}
if straddled {
return Err(refused(SEAM_WINDOW_STRADDLED));
}
Ok(inside.unwrap_or(distance))
}
pub fn snapped_distance2(curve: &Curve2, distance: Scalar, window: Scalar) -> GeomResult<Scalar> {
if checked_window(window)? == 0.0 || !distance.is_finite() {
return Ok(distance);
}
let seams = atomic_seams(
StationCurve::Two(curve),
station_seams2(curve)?,
certification(window),
)?;
snap(&seams, distance, window)
}
pub fn snapped_distance3(curve: &Curve3, distance: Scalar, window: Scalar) -> GeomResult<Scalar> {
if checked_window(window)? == 0.0 || !distance.is_finite() {
return Ok(distance);
}
let seams = atomic_seams(
StationCurve::Three(curve),
station_seams3(curve)?,
certification(window),
)?;
snap(&seams, distance, window)
}