axiolid_curve_evaluate_contract/
contract.rs

1//! Portable curve-evaluation provider contract.
2
3use axiolid_contracts::{Backend, Determinism, GeomResult};
4use axiolid_core::{Frame3, Point3, Vec3};
5use axiolid_curve::Curve3;
6
7use crate::{CurveMeasure, DistanceConvention};
8
9/// Curve evaluation provider.
10///
11/// Implementing this trait is the capability declaration: it lets a
12/// consumer name "evaluate a curve at a distance" without depending on a
13/// particular engine. A provider that cannot evaluate curves must not
14/// implement it.
15///
16/// # Why a frame and not just a tangent
17///
18/// A tangent fixes direction but not roll, so placing an object needs a
19/// full oriented frame. The convention has to be decided ONCE, here,
20/// because a wrong one tilts the placed object rather than failing
21/// loudly -- and every consumer re-deriving it would fragment slightly.
22///
23/// # Why the frame is not Frenet
24///
25/// The Frenet normal is the wrong tool for placement even though it is
26/// the classical one. On an alignment with a crest followed by a sag it
27/// points DOWN on the crest and UP in the sag, flipping at the
28/// inflection, and on any straight run the curvature is zero and the
29/// normal is undefined entirely. An object placed by it would be upright,
30/// then inverted, then unplaceable.
31///
32/// [`frame_at`](Self::frame_at) therefore uses a REFERENCE-UP frame:
33/// `right = normalise(tangent x up)` and `up' = right x tangent`, which
34/// is continuous through inflections and defined on straights. It is
35/// undefined only when the tangent is parallel to the reference
36/// direction -- a truly vertical curve -- where a provider must refuse
37/// rather than return an arbitrary roll.
38pub trait CurveEvaluator: Backend {
39    /// Which distance this provider measures for `curve`.
40    ///
41    /// Callers MUST consult this before trusting a distance. Defaults to
42    /// [`DistanceConvention::Unsupported`] so a provider that has not
43    /// declared a convention is treated as unable rather than assumed to
44    /// mean 3D arc length.
45    fn distance_convention(&self, curve: &Curve3) -> DistanceConvention {
46        let _ = curve;
47        DistanceConvention::Unsupported
48    }
49
50    /// Reproducibility this provider guarantees.
51    ///
52    /// Defaults to [`Determinism::BestEffort`], the weakest level, so an
53    /// unaudited provider cannot silently satisfy a stronger request.
54    fn determinism(&self) -> Determinism {
55        Determinism::BestEffort
56    }
57
58    /// Position at `at` along `curve`.
59    ///
60    /// [`CurveMeasure`] carries WHICH method of measurement the caller
61    /// means, so a native parameter cannot be mistaken for a length. A
62    /// [`Distance`](CurveMeasure::Distance) is interpreted in this
63    /// provider's [`distance_convention`](Self::distance_convention) for
64    /// this curve; a [`Parameter`](CurveMeasure::Parameter) is the curve's
65    /// own parameter and is independent of that convention.
66    ///
67    /// Refuses a non-finite value, a distance on a curve whose convention
68    /// is [`Unsupported`](DistanceConvention::Unsupported), and a value
69    /// outside the curve.
70    fn point_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Point3>;
71
72    /// Unit tangent at `at` along `curve`.
73    ///
74    /// Unit length is part of the contract: a caller composing a rotation
75    /// from this must not have to renormalise, and a non-unit result
76    /// would silently scale whatever it is applied to.
77    fn tangent_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Vec3>;
78
79    /// Oriented frame at `at` along `curve`.
80    ///
81    /// `x` is the unit tangent, `z` is the reference-up direction, and
82    /// `y = z x x` completes a right-handed orthonormal triad. See the
83    /// trait docs for why this is not the Frenet frame.
84    ///
85    /// Refuses when the tangent is parallel to `up`, where roll is
86    /// genuinely undetermined.
87    fn frame_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Frame3>;
88}