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}