use axiolid_contracts::{GeomError, GeomResult};
use axiolid_core::{Interval, Scalar, Vec3};
use axiolid_curve::{Banked3, Curve2, Curve3, Elevated3};
use crate::arc_length::{plan_derivative, plan_second_derivative};
use crate::bound::{frame_stretch2, CurveDerivativeBounds, ROUNDING};
fn invalid(detail: &str) -> GeomError {
GeomError::InvalidInput(detail.to_owned())
}
#[must_use]
pub fn elevated_span(curve: &Elevated3) -> Option<Scalar> {
let span = match curve.plan.as_ref() {
Curve2::Line(_) => return Some(Scalar::INFINITY),
Curve2::Circle(c) => core::f64::consts::TAU * c.radius,
Curve2::Intrinsic(i) => i.length,
Curve2::Chain(c) => c.length()?,
_ => return None,
};
(span.is_finite() && span > 0.0).then_some(span)
}
pub(crate) fn elevated_domain(curve: &Elevated3) -> Interval {
elevated_span(curve).map_or(
Interval {
start: 0.0,
end: 0.0,
},
|end| Interval { start: 0.0, end },
)
}
pub fn elevated_derivative(curve: &Elevated3, d: Scalar) -> GeomResult<Vec3> {
if !d.is_finite() {
return Err(invalid("plan distance must be finite"));
}
let plan = plan_derivative(&curve.plan, d)?;
let grade = crate::elevation::elevation_grade(&curve.elevation, d)?;
let out = Vec3::new(plan.x, plan.y, grade);
if out.is_finite() {
Ok(out)
} else {
Err(invalid("elevated curve derivative is not finite"))
}
}
pub fn elevated_second_derivative(curve: &Elevated3, d: Scalar) -> GeomResult<Vec3> {
if !d.is_finite() {
return Err(invalid("plan distance must be finite"));
}
let plan = plan_second_derivative(&curve.plan, d)?;
let bend = crate::elevation::elevation_second(&curve.elevation, d)?;
let out = Vec3::new(plan.x, plan.y, bend);
if out.is_finite() {
Ok(out)
} else {
Err(invalid("elevated curve second derivative is not finite"))
}
}
fn plan_bounds(plan: &Curve2, lo: Scalar, hi: Scalar) -> Option<[Scalar; 3]> {
let slack = |length: Scalar| 1e-9 * length.max(1.0);
let out = match plan {
Curve2::Line(line) => {
let length = line.direction.length();
if !(length.is_finite() && length > 0.0) {
return None;
}
[1.0, 0.0, 0.0]
}
Curve2::Circle(c) => {
if !(c.radius.is_finite() && c.radius > 0.0) {
return None;
}
let stretch = frame_stretch2(&c.frame);
let k = 1.0 / c.radius;
[stretch, stretch * k, stretch * k * k]
}
Curve2::Intrinsic(i) => {
if !(lo >= 0.0 && hi <= i.length + slack(i.length)) {
return None;
}
let stretch = frame_stretch2(&i.start);
let k = crate::chain::curvature_bound(&i.curvature, lo, hi)?;
let seamed = i
.curvature
.seams_within(i.length)
.iter()
.any(|&seam| seam > lo && seam < hi);
let rate = if seamed {
Scalar::INFINITY
} else {
crate::chain::curvature_bound(&i.curvature.derivative(), lo, hi)?
};
[stretch, stretch * k, stretch * (rate + k * k)]
}
Curve2::Chain(chain) => {
let (p2, p3) = crate::chain::chain_plan_bounds(chain, lo, hi)?;
[frame_stretch2(&chain.start), p2, p3]
}
_ => return None,
};
(out[0].is_finite() && out[1].is_finite()).then_some(out)
}
#[must_use]
pub fn elevated_breaks(curve: &Elevated3, k: usize) -> Vec<Scalar> {
if k == 0 {
return Vec::new();
}
let mut out = crate::bound::continuity_breaks2(&curve.plan, 1);
out.extend(crate::elevation::elevation_seams(&curve.elevation));
if k >= 2 {
match curve.plan.as_ref() {
Curve2::Intrinsic(i) => out.extend(i.curvature.seams_within(i.length)),
Curve2::Chain(chain) => out.extend(crate::chain::chain_curvature_seams(chain)),
_ => {}
}
}
out.retain(|b| b.is_finite());
out.sort_by(Scalar::total_cmp);
out.dedup();
out
}
#[must_use]
pub fn banked_breaks(curve: &Banked3, k: usize) -> Vec<Scalar> {
if k == 0 {
return Vec::new();
}
let mut out = elevated_breaks(&curve.base, k);
out.extend(curve.pivot.seams());
out.sort_by(Scalar::total_cmp);
out.dedup();
out
}
fn inside(breaks: &[Scalar], lo: Scalar, hi: Scalar) -> bool {
breaks.iter().any(|&b| b > lo && b < hi)
}
#[must_use]
pub fn elevated_chord_bound(curve: &Elevated3, a: Scalar, b: Scalar) -> Option<Scalar> {
let (lo, hi) = (a.min(b), a.max(b));
if !(lo.is_finite() && hi.is_finite()) {
return None;
}
if lo == hi {
return Some(0.0);
}
if inside(&elevated_breaks(curve, 1), lo, hi) {
return None;
}
let h = hi - lo;
let plan = h * h * 0.125 * plan_bounds(&curve.plan, lo, hi)?[1];
let profile = crate::elevation::elevation_chord_bound(&curve.elevation, lo, hi)?;
let bound = plan.hypot(profile) * (1.0 + ROUNDING);
bound.is_finite().then_some(bound)
}
#[must_use]
pub fn banked_chord_bound(curve: &Banked3, a: Scalar, b: Scalar) -> Option<Scalar> {
let (lo, hi) = (a.min(b), a.max(b));
if !(lo.is_finite() && hi.is_finite()) {
return None;
}
if lo == hi {
return Some(0.0);
}
if lo < 0.0 || inside(&banked_breaks(curve, 1), lo, hi) {
return None;
}
let h = hi - lo;
let taylor = h * h * 0.125;
let plan = taylor * plan_bounds(&curve.base.plan, lo, hi)?[1];
let profile = crate::elevation::elevation_chord_bound(&curve.base.elevation, lo, hi)?;
let pivot = taylor * crate::banked::pivot_bounds(&curve.pivot, lo, hi)?[1];
let bound = plan.hypot(profile + pivot) * (1.0 + ROUNDING);
bound.is_finite().then_some(bound)
}
#[must_use]
pub fn elevated_derivative_bounds(
curve: &Elevated3,
a: Scalar,
b: Scalar,
) -> Option<CurveDerivativeBounds> {
let (lo, hi) = (a.min(b), a.max(b));
if !(lo.is_finite() && hi.is_finite()) || inside(&elevated_breaks(curve, 2), lo, hi) {
return None;
}
let plan = plan_bounds(&curve.plan, lo, hi)?;
let profile = crate::elevation::profile_bounds(&curve.elevation, lo, hi)?;
combined(plan, profile)
}
#[must_use]
pub fn banked_derivative_bounds(
curve: &Banked3,
a: Scalar,
b: Scalar,
) -> Option<CurveDerivativeBounds> {
let (lo, hi) = (a.min(b), a.max(b));
if !(lo.is_finite() && hi.is_finite()) || lo < 0.0 || inside(&banked_breaks(curve, 2), lo, hi) {
return None;
}
let plan = plan_bounds(&curve.base.plan, lo, hi)?;
let profile = crate::elevation::profile_bounds(&curve.base.elevation, lo, hi)?;
let pivot = crate::banked::pivot_bounds(&curve.pivot, lo, hi)?;
combined(
plan,
[
profile[0] + pivot[0],
profile[1] + pivot[1],
profile[2] + pivot[2],
],
)
}
fn combined(plan: [Scalar; 3], vertical: [Scalar; 3]) -> Option<CurveDerivativeBounds> {
let bound = |j: usize| plan[j].hypot(vertical[j]) * (1.0 + ROUNDING);
let out = CurveDerivativeBounds {
first: bound(0),
second: bound(1),
third: bound(2),
};
(out.first.is_finite() && out.second.is_finite() && out.third.is_finite()).then_some(out)
}
pub(crate) fn certifies_elevated(curve: &Elevated3) -> bool {
fn closed_form(law: &axiolid_curve::ElevationLaw) -> bool {
use axiolid_curve::ElevationLaw as L;
match law {
L::Polynomial { .. } | L::CircularArc { .. } => true,
L::Piecewise { laws, .. } => laws.iter().all(closed_form),
_ => false,
}
}
let plan = match curve.plan.as_ref() {
Curve2::Line(_) | Curve2::Circle(_) => true,
Curve2::Intrinsic(i) => {
crate::chain::curvature_bound(&i.curvature, 0.0, i.length.max(0.0)).is_some()
}
Curve2::Chain(chain) => crate::chain::chain_certifies(chain),
_ => false,
};
plan && curve.elevation.is_well_formed() && closed_form(&curve.elevation)
}
pub fn grade_corners3(curve: &Curve3, angle: Scalar) -> GeomResult<Vec<Scalar>> {
let (elevated, pivot) = match curve {
Curve3::Elevated(e) => (e, None),
Curve3::Banked(b) => (&b.base, Some(&b.pivot)),
_ => return Ok(Vec::new()),
};
let mut seams = crate::elevation::elevation_seams(&elevated.elevation);
if let Some(pivot) = pivot {
seams.extend(pivot.seams());
}
seams.sort_by(Scalar::total_cmp);
seams.dedup();
let mut out = Vec::new();
for seam in seams {
let mut before = crate::elevation::elevation_grade_before(&elevated.elevation, seam)?;
let mut after = crate::elevation::elevation_grade(&elevated.elevation, seam)?;
if let Some(pivot) = pivot {
let rate = |before: bool| {
crate::banked::pivot_jet(pivot, seam, before)
.map(|jet| jet[1])
.ok_or_else(|| invalid("banked curve: pivot law has no rate at a seam"))
};
before += rate(true)?;
after += rate(false)?;
}
if (before.atan() - after.atan()).abs() > angle {
out.push(seam);
}
}
Ok(out)
}