axiolid_curve_evaluate_contract/contract.rs
1//! Portable curve-evaluation provider contract.
2
3use axiolid_contracts::{Backend, Determinism, GeomError, GeomResult, Operation};
4use axiolid_core::{Frame3, Point3, Scalar, Vec3};
5use axiolid_curve::{Curve3, CurvePath, SeamSide};
6
7use crate::{CurveMeasure, DistanceConvention, DistanceInterval};
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.
38///
39/// # Seams (#286)
40///
41/// A curve made of pieces -- a polyline's vertex, an elevated curve's
42/// grade break, a banked curve's cant jump -- has two tangents or two
43/// rolls where its pieces meet. [`point_at`](Self::point_at),
44/// [`tangent_at`](Self::tangent_at) and [`frame_at`](Self::frame_at) read
45/// the piece the measure falls in, which on a seam is the piece that
46/// STARTS there. [`point_at_on`](Self::point_at_on),
47/// [`tangent_at_on`](Self::tangent_at_on) and
48/// [`frame_at_on`](Self::frame_at_on) name the side instead, by the
49/// station seam rule of ADR 0082 (#263): a measure within the arc-length
50/// tolerance of a seam whose frame may jump is ON it, read at the seam
51/// from the piece [`SeamSide`] names -- [`SeamSide::Outgoing`], the one
52/// starting there, or [`SeamSide::Incoming`], the one ending there. Off a
53/// seam, and at the curve's own start and end, both sides read the same
54/// piece and agree.
55///
56/// The sided methods have defaults so that adding them broke no provider:
57/// `Outgoing` delegates to the side-less method, and every other side is
58/// refused by a typed [`GeomError::UnsupportedInput`] naming
59/// [`SEAM_SIDE_UNSUPPORTED`]. A provider that does not implement sides
60/// never answers `Incoming` with the outgoing frame.
61///
62/// # Curve paths (#290)
63///
64/// A curve relation a distance runs along -- a composite curve, a trim of
65/// one, segments placed at stations of another curve -- reaches a provider
66/// as a [`CurvePath`]: spans of atomic curves laid end to end, each read
67/// forwards or backwards and carried by a rigid placement
68/// (`axiolid-curve`'s neutral value; a graph compiler flattens a relation
69/// into one). The `path_*` queries read it as ADR 0082 reads a composite
70/// station basis (#285):
71///
72/// - The distance runs end to end, each piece in its own station measure
73/// (plan distance on an elevated or banked curve, arc length on every
74/// other); every piece must measure alike
75/// ([`path_distance_convention`](Self::path_distance_convention)).
76/// Consecutive pieces must meet; a path that does not is refused by
77/// name.
78/// - Every interior joint is a seam, never smooth: a distance within the
79/// arc-length tolerance `1e-12 * max(1, s)` of a joint is ON it and is
80/// read at the joint from the piece [`SeamSide`] names, the incoming one
81/// at its end, the outgoing one at its start, each at its own point. A
82/// piece's own seams strictly inside it are read by its curve's rule; at
83/// a piece's ends it is read from inside. The plain queries read
84/// [`SeamSide::Outgoing`].
85/// - A reversed piece reads its curve at `end - u`, seam sides swapped,
86/// tangent and left lateral negated, up kept; a placed piece's frame is
87/// its curve's carried by the placement.
88/// - The frame is the station section in this trait's layout (`x`
89/// tangent, `y` up, `z` right); a frame is exact, rounding aside, only
90/// where every piece up to and including the one read is a line placed,
91/// if at all, exactly
92/// ([`path_frame_is_exact_at`](Self::path_frame_is_exact_at)).
93///
94/// A piece may leave its end open ([`PathPiece::to_end`](axiolid_curve::PathPiece::to_end),
95/// [`PathPiece::whole`](axiolid_curve::PathPiece::whole), #298): a caller
96/// with no provider cannot state the length of an offset beside a gradient
97/// curve, a clothoid or a B-spline. A provider that reads paths resolves
98/// the end to where the curve ends by its own length of it (exact where
99/// that is read from stored data, its numerical estimate otherwise), so
100/// the distance runs through the pieces as it would through the stated
101/// path holding those lengths, and
102/// [`path_joint_distances`](Self::path_joint_distances) certifies where
103/// the joints after it truly fall. A provider that reads stated paths but
104/// not open ends refuses a path with one by a typed
105/// [`GeomError::UnsupportedInput`] naming [`OPEN_PATH_END_UNSUPPORTED`],
106/// never reading the `end` the piece leaves unset.
107///
108/// A piece may also start or end at a [`PathBound`](axiolid_curve::PathBound)
109/// (#304): a native parameter of its curve, or a point on it in the
110/// curve's own coordinates ([`PathPiece::between_parameters`](axiolid_curve::PathPiece::between_parameters),
111/// [`PathPiece::bounded`](axiolid_curve::PathPiece::bounded)), since the
112/// distance to an interior parameter of an ellipse or a B-spline is a
113/// quadrature too. A provider that reads paths resolves a point to the
114/// one parameter naming it, refusing by a typed
115/// [`GeomError::UnsupportedInput`] a point off the curve
116/// ([`PATH_POINT_OFF_CURVE`]) or one several parameters name
117/// ([`PATH_POINT_AMBIGUOUS`]), and a parameter to its distance in the
118/// curve's station measure: exact where that is read from stored data,
119/// otherwise the same estimate it measures a span between parameters by,
120/// so the path reads as the stated path holding those distances and
121/// [`path_joint_distances`](Self::path_joint_distances) and
122/// [`path_joint_distances_within`](Self::path_joint_distances_within)
123/// certify the joints after it. A provider that does not read bounds refuses a path
124/// with one naming [`PATH_PIECE_BOUND_UNSUPPORTED`], never reading the
125/// `start` or `end` the piece leaves unset.
126///
127/// They have defaults so that adding them broke no provider: a provider
128/// that does not implement paths refuses every path query by a typed
129/// [`GeomError::UnsupportedInput`] naming [`CURVE_PATH_UNSUPPORTED`],
130/// reports [`DistanceConvention::Unsupported`] and claims no frame exact.
131/// It never reads a path as its first piece, or a joint from the wrong
132/// side.
133///
134/// # Joint distances (#294)
135///
136/// A caller that holds a distance authored against the true geometry --
137/// a station a model's precision puts on a joint -- needs to know where
138/// the joints lie, and a provider's own joint distances are partly
139/// numerical: the length of an ellipse or a B-spline piece, and of an
140/// offset other than a line beside a line or a circle beside a circle, is
141/// a quadrature. [`path_joint_distances`](Self::path_joint_distances)
142/// answers each interior joint with a [`DistanceInterval`] that CONTAINS
143/// its true distance and is no wider than the requested tolerance: the
144/// sum of the true lengths of the pieces before it, each in the path's
145/// convention (its own plan length for an offset beside a plan-measured
146/// curve), between the points the provider reads as the piece's ends. A
147/// piece whose length is read from stored data (a line, a circle, a
148/// polyline, an intrinsic curve or a chain, an elevated or banked curve,
149/// a constant offset of a line, or of a circle in planes normal to its
150/// axis) contributes exactly, so a path of them has point intervals.
151///
152/// The default refuses by a typed [`GeomError::UnsupportedInput`] naming
153/// [`PATH_JOINT_DISTANCES_UNSUPPORTED`]: a provider that does not bound its
154/// pieces' lengths never hands out an estimate as a certificate.
155///
156/// # Joints near a window (#305)
157///
158/// A caller placing something at a distance `around` needs only the joints
159/// that can fall within its precision `window` of it, and one piece whose
160/// length cannot be certified anywhere on the path (an offset by distances
161/// along a B-spline) refuses
162/// [`path_joint_distances`](Self::path_joint_distances) for all of them.
163/// [`path_joint_distances_within`](Self::path_joint_distances_within)
164/// answers only the joints whose certified interval meets `[around -
165/// window, around + window]`, each with the interval
166/// [`path_joint_distances`](Self::path_joint_distances) would give it, so
167/// it certifies only the pieces those joints lie after:
168///
169/// - **Before the window,** every piece is certified, since its length
170/// moves every later joint; the intervals of the sum may widen with
171/// each numerical piece, but stay within the tolerance. A piece before
172/// the window whose length cannot be certified refuses the query by
173/// name: the window's position against the joints after it is unknown.
174/// No coarser bound is substituted; one loose enough to reach every
175/// such piece would be far wider than any tolerance a placement asks.
176/// - **Within the window,** each joint that can fall in it is certified
177/// or the query refused by name: the uncertainty reaches the window.
178/// - **After the window,** no piece is read: a joint is known to lie past
179/// it once the joint before it does, or once a certified lower bound of
180/// its piece's length (its chord) carries it past.
181///
182/// A joint left out therefore truly lies outside the window, and a joint
183/// whose certified interval lies inside it is always returned. The
184/// default delegates to [`path_joint_distances`](Self::path_joint_distances)
185/// and keeps the joints whose intervals meet the window, which cannot be
186/// wrong where that query is right, so a provider that does not certify
187/// joints refuses it by the same name.
188pub trait CurveEvaluator: Backend {
189 /// Which distance this provider measures for `curve`.
190 ///
191 /// Callers MUST consult this before trusting a distance. Defaults to
192 /// [`DistanceConvention::Unsupported`] so a provider that has not
193 /// declared a convention is treated as unable rather than assumed to
194 /// mean 3D arc length.
195 fn distance_convention(&self, curve: &Curve3) -> DistanceConvention {
196 let _ = curve;
197 DistanceConvention::Unsupported
198 }
199
200 /// Reproducibility this provider guarantees.
201 ///
202 /// Defaults to [`Determinism::BestEffort`], the weakest level, so an
203 /// unaudited provider cannot silently satisfy a stronger request.
204 fn determinism(&self) -> Determinism {
205 Determinism::BestEffort
206 }
207
208 /// Position at `at` along `curve`.
209 ///
210 /// [`CurveMeasure`] carries WHICH method of measurement the caller
211 /// means, so a native parameter cannot be mistaken for a length. A
212 /// [`Distance`](CurveMeasure::Distance) is interpreted in this
213 /// provider's [`distance_convention`](Self::distance_convention) for
214 /// this curve; a [`Parameter`](CurveMeasure::Parameter) is the curve's
215 /// own parameter and is independent of that convention.
216 ///
217 /// Refuses a non-finite value, a distance on a curve whose convention
218 /// is [`Unsupported`](DistanceConvention::Unsupported), and a value
219 /// outside the curve.
220 fn point_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Point3>;
221
222 /// Unit tangent at `at` along `curve`.
223 ///
224 /// Unit length is part of the contract: a caller composing a rotation
225 /// from this must not have to renormalise, and a non-unit result
226 /// would silently scale whatever it is applied to.
227 fn tangent_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Vec3>;
228
229 /// Oriented frame at `at` along `curve`.
230 ///
231 /// `x` is the unit tangent, `y` is the reference up made perpendicular
232 /// to it (`y = z x x`, leaning back with the grade), and
233 /// `z = normalise(x x up)` points to the RIGHT of the tangent: a
234 /// right-handed orthonormal triad whose second axis is up (ADR 0063;
235 /// #242 corrected this text, which used to call `z` up). On a level
236 /// line along `+X` with `up = +Z` the frame is `x = +X`, `y = +Z`,
237 /// `z = -Y`. See the trait docs for why this is not the Frenet frame.
238 ///
239 /// Refuses when the tangent is parallel to `up`, where roll is
240 /// genuinely undetermined.
241 ///
242 /// A curve that carries its own roll ([`Curve3::Banked`]) is framed by
243 /// it: the section frame rolled about the tangent by the curve's bank
244 /// convention -- `x` the tangent, `y` the section up, `z` minus its
245 /// left lateral -- which at zero cant is the reference-up frame with
246 /// `up = +Z` (ADR 0081).
247 fn frame_at(&self, curve: &Curve3, at: CurveMeasure) -> GeomResult<Frame3>;
248
249 /// [`point_at`](Self::point_at) reading `side` of a seam `at` lies on
250 /// (see the [trait documentation](Self#seams-286)).
251 ///
252 /// On a seam both sides share the point up to the curve's own
253 /// continuity; a provider that applies the seam tolerance reads the
254 /// point at the seam, not at `at`.
255 ///
256 /// # Errors
257 ///
258 /// As [`point_at`](Self::point_at). By default, every side but
259 /// [`SeamSide::Outgoing`] by [`GeomError::UnsupportedInput`] naming
260 /// [`SEAM_SIDE_UNSUPPORTED`].
261 fn point_at_on(&self, curve: &Curve3, at: CurveMeasure, side: SeamSide) -> GeomResult<Point3> {
262 match side {
263 SeamSide::Outgoing => self.point_at(curve, at),
264 _ => Err(side_unsupported(self)),
265 }
266 }
267
268 /// [`tangent_at`](Self::tangent_at) reading `side` of a seam `at` lies
269 /// on (see the [trait documentation](Self#seams-286)): at a polyline's
270 /// vertex, the direction of the segment ending there (`Incoming`) or
271 /// starting there (`Outgoing`).
272 ///
273 /// # Errors
274 ///
275 /// As [`tangent_at`](Self::tangent_at). By default, every side but
276 /// [`SeamSide::Outgoing`] by [`GeomError::UnsupportedInput`] naming
277 /// [`SEAM_SIDE_UNSUPPORTED`].
278 fn tangent_at_on(&self, curve: &Curve3, at: CurveMeasure, side: SeamSide) -> GeomResult<Vec3> {
279 match side {
280 SeamSide::Outgoing => self.tangent_at(curve, at),
281 _ => Err(side_unsupported(self)),
282 }
283 }
284
285 /// [`frame_at`](Self::frame_at) reading `side` of a seam `at` lies on
286 /// (see the [trait documentation](Self#seams-286)), in the same
287 /// layout: `x` the side's tangent, `y` up, `z` to the right; on a
288 /// banked curve's cant jump, the side's roll.
289 ///
290 /// # Errors
291 ///
292 /// As [`frame_at`](Self::frame_at). By default, every side but
293 /// [`SeamSide::Outgoing`] by [`GeomError::UnsupportedInput`] naming
294 /// [`SEAM_SIDE_UNSUPPORTED`].
295 fn frame_at_on(&self, curve: &Curve3, at: CurveMeasure, side: SeamSide) -> GeomResult<Frame3> {
296 match side {
297 SeamSide::Outgoing => self.frame_at(curve, at),
298 _ => Err(side_unsupported(self)),
299 }
300 }
301
302 /// Which distance this provider measures along `path` (see the
303 /// [trait documentation](Self#curve-paths-290)): the convention every
304 /// piece measures in, or [`DistanceConvention::Unsupported`] where the
305 /// provider cannot measure the path (pieces measured differently, a
306 /// joint whose pieces do not meet, a piece it does not know).
307 ///
308 /// Defaults to [`DistanceConvention::Unsupported`].
309 fn path_distance_convention(&self, path: &CurvePath) -> DistanceConvention {
310 let _ = path;
311 DistanceConvention::Unsupported
312 }
313
314 /// Position at `at` along `path`, reading a joint from its outgoing
315 /// piece: [`path_point_at_on`](Self::path_point_at_on) with
316 /// [`SeamSide::Outgoing`].
317 ///
318 /// # Errors
319 ///
320 /// As [`path_point_at_on`](Self::path_point_at_on).
321 fn path_point_at(&self, path: &CurvePath, at: CurveMeasure) -> GeomResult<Point3> {
322 self.path_point_at_on(path, at, SeamSide::Outgoing)
323 }
324
325 /// Unit tangent at `at` along `path`, reading a joint from its outgoing
326 /// piece: [`path_tangent_at_on`](Self::path_tangent_at_on) with
327 /// [`SeamSide::Outgoing`].
328 ///
329 /// # Errors
330 ///
331 /// As [`path_tangent_at_on`](Self::path_tangent_at_on).
332 fn path_tangent_at(&self, path: &CurvePath, at: CurveMeasure) -> GeomResult<Vec3> {
333 self.path_tangent_at_on(path, at, SeamSide::Outgoing)
334 }
335
336 /// Oriented frame at `at` along `path`, reading a joint from its
337 /// outgoing piece: [`path_frame_at_on`](Self::path_frame_at_on) with
338 /// [`SeamSide::Outgoing`].
339 ///
340 /// # Errors
341 ///
342 /// As [`path_frame_at_on`](Self::path_frame_at_on).
343 fn path_frame_at(&self, path: &CurvePath, at: CurveMeasure) -> GeomResult<Frame3> {
344 self.path_frame_at_on(path, at, SeamSide::Outgoing)
345 }
346
347 /// Position at `at` along `path`, reading `side` of a joint or a seam
348 /// it lies on (see the [trait documentation](Self#curve-paths-290)).
349 ///
350 /// # Errors
351 ///
352 /// A measure that is not a distance in the path's convention, not
353 /// finite, or outside `[0, L]`; a path the provider cannot measure; the
354 /// pieces' own refusals. By default, everything by
355 /// [`GeomError::UnsupportedInput`] naming [`CURVE_PATH_UNSUPPORTED`].
356 fn path_point_at_on(
357 &self,
358 path: &CurvePath,
359 at: CurveMeasure,
360 side: SeamSide,
361 ) -> GeomResult<Point3> {
362 let _ = (path, at, side);
363 Err(path_unsupported(self))
364 }
365
366 /// Unit tangent at `at` along `path`, reading `side` of a joint or a
367 /// seam it lies on (see the [trait documentation](Self#curve-paths-290)).
368 ///
369 /// # Errors
370 ///
371 /// As [`path_point_at_on`](Self::path_point_at_on).
372 fn path_tangent_at_on(
373 &self,
374 path: &CurvePath,
375 at: CurveMeasure,
376 side: SeamSide,
377 ) -> GeomResult<Vec3> {
378 let _ = (path, at, side);
379 Err(path_unsupported(self))
380 }
381
382 /// Oriented frame at `at` along `path`, reading `side` of a joint or a
383 /// seam it lies on (see the [trait documentation](Self#curve-paths-290)),
384 /// in [`frame_at`](Self::frame_at)'s layout: `x` the side's tangent,
385 /// `y` up, `z` to the right; on a banked piece, its rolled section.
386 ///
387 /// # Errors
388 ///
389 /// As [`path_point_at_on`](Self::path_point_at_on), and a tangent
390 /// parallel to the reference up.
391 fn path_frame_at_on(
392 &self,
393 path: &CurvePath,
394 at: CurveMeasure,
395 side: SeamSide,
396 ) -> GeomResult<Frame3> {
397 let _ = (path, at, side);
398 Err(path_unsupported(self))
399 }
400
401 /// Whether the frame at `at` along `path`, read from `side`, is exact,
402 /// rounding aside: only where every piece up to and including the one
403 /// read is a line placed, if at all, exactly (see the
404 /// [trait documentation](Self#curve-paths-290)). `false` wherever the
405 /// provider would refuse the query.
406 ///
407 /// Defaults to `false`: a provider that does not implement paths
408 /// claims nothing exact.
409 fn path_frame_is_exact_at(&self, path: &CurvePath, at: CurveMeasure, side: SeamSide) -> bool {
410 let _ = (path, at, side);
411 false
412 }
413
414 /// Where the interior joints of `path` lie, each certified to a
415 /// [`DistanceInterval`] no wider than `tolerance` (see the
416 /// [trait documentation](Self#joint-distances-294)): one interval per
417 /// joint, in order, `pieces - 1` in all.
418 ///
419 /// # Errors
420 ///
421 /// A `tolerance` that is not finite and positive, a path the provider
422 /// cannot measure (as [`path_point_at_on`](Self::path_point_at_on)),
423 /// and a piece whose length it cannot certify to `tolerance`, by name.
424 /// By default, everything by [`GeomError::UnsupportedInput`] naming
425 /// [`PATH_JOINT_DISTANCES_UNSUPPORTED`].
426 fn path_joint_distances(
427 &self,
428 path: &CurvePath,
429 tolerance: Scalar,
430 ) -> GeomResult<Vec<DistanceInterval>> {
431 let _ = (path, tolerance);
432 Err(GeomError::UnsupportedInput {
433 backend: self.descriptor().id,
434 operation: Operation::CurveEvaluation,
435 input: PATH_JOINT_DISTANCES_UNSUPPORTED,
436 })
437 }
438
439 /// The interior joints of `path` that can fall within `window` of
440 /// `around` (see the [trait documentation](Self#joints-near-a-window-305)):
441 /// each as `(joint, interval)`, `joint` its index among the path's
442 /// joints (the joint where piece `joint` ends and piece `joint + 1`
443 /// starts, its position in
444 /// [`path_joint_distances`](Self::path_joint_distances)' answer), in
445 /// order, `interval` certified as that query certifies it, no wider
446 /// than `tolerance`. Returned: every joint whose interval meets
447 /// `[around - window, around + window]` (the edges included). Left
448 /// out: only joints certified to lie outside it. Empty when no joint
449 /// can fall within it.
450 ///
451 /// # Errors
452 ///
453 /// An `around` that is not finite or a `window` that is negative or not
454 /// finite ([`GeomError::InvalidInput`]); as
455 /// [`path_joint_distances`](Self::path_joint_distances) otherwise, but
456 /// only for the pieces before and within the window. By default,
457 /// whatever [`path_joint_distances`](Self::path_joint_distances)
458 /// refuses, so [`PATH_JOINT_DISTANCES_UNSUPPORTED`] where that is not
459 /// implemented.
460 fn path_joint_distances_within(
461 &self,
462 path: &CurvePath,
463 around: Scalar,
464 window: Scalar,
465 tolerance: Scalar,
466 ) -> GeomResult<Vec<(usize, DistanceInterval)>> {
467 let span = window_span(around, window)?;
468 Ok(self
469 .path_joint_distances(path, tolerance)?
470 .into_iter()
471 .enumerate()
472 .filter(|(_, interval)| interval.meets(&span))
473 .collect())
474 }
475}
476
477/// The window `[around - window, around + window]` of
478/// [`path_joint_distances_within`](CurveEvaluator::path_joint_distances_within),
479/// checked: `around` finite, `window` finite and not negative.
480///
481/// # Errors
482///
483/// Any other, by [`GeomError::InvalidInput`].
484pub fn window_span(around: Scalar, window: Scalar) -> GeomResult<DistanceInterval> {
485 if !around.is_finite() {
486 return Err(GeomError::InvalidInput(format!(
487 "joints near a window: the window's centre {around} is not finite"
488 )));
489 }
490 if !(window.is_finite() && window >= 0.0) {
491 return Err(GeomError::InvalidInput(format!(
492 "joints near a window: the window {window} is negative or not finite"
493 )));
494 }
495 Ok(DistanceInterval::new(around - window, around + window))
496}
497
498/// The input a provider that does not certify joint distances refuses, in
499/// the [`GeomError::UnsupportedInput`] its default
500/// [`path_joint_distances`](CurveEvaluator::path_joint_distances) returns.
501pub const PATH_JOINT_DISTANCES_UNSUPPORTED: &str =
502 "certified joint distances of a curve path: this provider does not bound the lengths of a \
503 path's pieces";
504
505/// The input a provider that reads curve paths but not open piece ends
506/// (#298) refuses a path with one for.
507pub const OPEN_PATH_END_UNSUPPORTED: &str =
508 "a curve path piece whose end is open (runs to its curve's end): this provider reads stated \
509 spans only";
510
511/// The input a provider that reads curve paths but not piece bounds given
512/// as a native parameter or a point (#304) refuses a path with one for.
513pub const PATH_PIECE_BOUND_UNSUPPORTED: &str =
514 "a curve path piece bounded by a native parameter or a point: this provider reads spans \
515 stated as distances only";
516
517/// The input a point bound of a curve path piece (#304) is refused for
518/// when it does not lie on the piece's curve within the joint tolerance
519/// `1e-9 * max(1, |p|)`: a provider never projects it onto the curve.
520pub const PATH_POINT_OFF_CURVE: &str =
521 "a curve path piece bounded by a point that does not lie on its curve within the joint \
522 tolerance";
523
524/// The input a point bound of a curve path piece (#304) is refused for
525/// when several parameters of the curve name it: a curve that passes
526/// through it more than once, or the seam of a closed curve other than a
527/// circle or an ellipse (whose angle runs round).
528pub const PATH_POINT_AMBIGUOUS: &str =
529 "a curve path piece bounded by a point that several parameters of its curve name (the curve \
530 passes through it more than once, or it is the seam of a closed curve other than a circle \
531 or an ellipse)";
532
533/// The input a provider that does not implement curve paths refuses, in
534/// the [`GeomError::UnsupportedInput`] its default `path_*` queries return.
535pub const CURVE_PATH_UNSUPPORTED: &str =
536 "a curve path (pieces of curves laid end to end): this provider evaluates single curves only";
537
538/// The default refusal of a curve path, naming `provider`.
539fn path_unsupported<E: Backend + ?Sized>(provider: &E) -> GeomError {
540 GeomError::UnsupportedInput {
541 backend: provider.descriptor().id,
542 operation: Operation::CurveEvaluation,
543 input: CURVE_PATH_UNSUPPORTED,
544 }
545}
546
547/// The input a provider that does not implement seam sides refuses, in
548/// the [`GeomError::UnsupportedInput`] its default sided methods return.
549pub const SEAM_SIDE_UNSUPPORTED: &str =
550 "a seam side other than Outgoing: this provider reads only the piece that starts at a seam";
551
552/// The default refusal of a seam side, naming `provider`.
553fn side_unsupported<E: Backend + ?Sized>(provider: &E) -> GeomError {
554 GeomError::UnsupportedInput {
555 backend: provider.descriptor().id,
556 operation: Operation::CurveEvaluation,
557 input: SEAM_SIDE_UNSUPPORTED,
558 }
559}