use axiolid_contracts::{
Backend, BackendDescriptor, BackendId, Determinism, ExecutionTarget, GeomError, GeomResult,
};
use axiolid_core::{Frame3, Point3, Scalar, Vec3};
use axiolid_curve::Curve3;
use axiolid_curve_evaluate_contract::{CurveEvaluator, CurveMeasure, DistanceConvention};
use crate::arc_length::{elevated_point, elevated_tangent};
use crate::frenet::{frenet_point, frenet_tangent};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ReferenceCurveEvaluator {
up: Vec3,
}
impl ReferenceCurveEvaluator {
pub const ID: BackendId = BackendId::new("axiolid-evaluate");
#[must_use]
pub const fn new() -> Self {
Self { up: Vec3::Z }
}
#[must_use]
pub fn with_up(up: Vec3) -> Option<Self> {
let length = up.length();
if !length.is_finite() || length <= 0.0 {
return None;
}
Some(Self { up: up / length })
}
#[must_use]
pub const fn up(&self) -> Vec3 {
self.up
}
}
impl Default for ReferenceCurveEvaluator {
fn default() -> Self {
Self::new()
}
}
impl Backend for ReferenceCurveEvaluator {
fn descriptor(&self) -> BackendDescriptor {
BackendDescriptor::new(Self::ID, ExecutionTarget::PortableCpu)
}
}
fn unsupported() -> GeomError {
GeomError::Unsupported {
backend: ReferenceCurveEvaluator::ID,
operation: axiolid_contracts::Operation::CurveEvaluation,
}
}
fn invalid(detail: &str) -> GeomError {
GeomError::InvalidInput(detail.into())
}
fn finite_value(at: CurveMeasure) -> GeomResult<Scalar> {
let value = at.value();
if !value.is_finite() {
return Err(invalid("curve measure must be finite"));
}
Ok(value)
}
fn convention_for(curve: &Curve3) -> DistanceConvention {
match curve {
Curve3::Intrinsic(_) | Curve3::Line(_) | Curve3::Circle(_) => {
DistanceConvention::ArcLength3d
}
Curve3::Elevated(_) => DistanceConvention::PlanDistance,
_ => DistanceConvention::Unsupported,
}
}
fn parameter_for(curve: &Curve3, distance: Scalar) -> GeomResult<Scalar> {
if !distance.is_finite() {
return Err(invalid("distance along a curve must be finite"));
}
match curve {
Curve3::Intrinsic(_) | Curve3::Elevated(_) => Ok(distance),
Curve3::Line(l) => {
let speed = l.direction.length();
if !speed.is_finite() || speed <= 0.0 {
return Err(invalid("line has no direction, so no distance along it"));
}
Ok(distance / speed)
}
Curve3::Circle(c) => {
if !c.radius.is_finite() || c.radius <= 0.0 {
return Err(invalid("circle has no positive radius"));
}
Ok(distance / c.radius)
}
_ => Err(unsupported()),
}
}
impl CurveEvaluator for ReferenceCurveEvaluator {
fn distance_convention(&self, curve: &Curve3) -> DistanceConvention {
convention_for(curve)
}
fn determinism(&self) -> Determinism {
Determinism::Bitwise
}
fn point_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Point3> {
let CurveMeasure::Distance(distance) = at else {
return crate::curve::evaluate3(curve, finite_value(at)?);
};
match curve {
Curve3::Intrinsic(i) => frenet_point(i, distance),
Curve3::Elevated(e) => elevated_point(e, distance),
Curve3::Line(_) | Curve3::Circle(_) => {
crate::curve::evaluate3(curve, parameter_for(curve, distance)?)
}
_ => Err(unsupported()),
}
}
fn tangent_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Vec3> {
let raw = match at {
CurveMeasure::Parameter(_) => crate::curve::derivative3(curve, finite_value(at)?)?,
CurveMeasure::Distance(distance) => match curve {
Curve3::Intrinsic(i) => frenet_tangent(i, distance)?,
Curve3::Elevated(e) => elevated_tangent(e, distance)?,
Curve3::Line(_) | Curve3::Circle(_) => {
crate::curve::derivative3(curve, parameter_for(curve, distance)?)?
}
_ => return Err(unsupported()),
},
_ => return Err(unsupported()),
};
let length = raw.length();
if !length.is_finite() || length <= 0.0 {
return Err(invalid("curve has no tangent direction there"));
}
Ok(raw / length)
}
fn frame_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Frame3> {
let origin = self.point_at(curve, at)?;
let tangent = self.tangent_at(curve, at)?;
let right = tangent.cross(self.up);
let magnitude = right.length();
if !magnitude.is_finite() || magnitude <= 1e-12 {
return Err(invalid(
"tangent is parallel to the reference up direction, so roll is undefined",
));
}
let right = right / magnitude;
let up = right.cross(tangent);
Ok(Frame3 {
origin,
x: tangent,
y: up,
z: right,
})
}
}