#[non_exhaustive]pub enum CurvatureLaw {
Constant {
curvature: Scalar,
},
Polynomial {
coefficients: Vec<Scalar>,
},
Sinusoid {
mean: Scalar,
amplitude: Scalar,
angular_frequency: Scalar,
phase: Scalar,
},
Composite {
polynomial: Vec<Scalar>,
harmonics: Vec<Harmonic>,
},
Piecewise {
breaks: Vec<Scalar>,
laws: Vec<CurvatureLaw>,
},
}Expand description
Curvature as a closed-form function of arc length.
Sign follows the usual plane convention: positive curvature turns the
tangent counter-clockwise. s is arc length measured from the curve start,
so a law is meaningful on [0, length] of the curve that carries it.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Constant
k(s) = curvature.
Zero is a straight line; any other value is a circular arc of radius
1 / curvature.
Polynomial
k(s) = coefficients[0] + coefficients[1] * s + coefficients[2] * s^2 + ...
Degree 1 is the clothoid (Euler spiral), whose curvature is linear in arc length; that is the transition spiral used between straight and circular track so lateral acceleration ramps linearly instead of stepping. Higher degrees cover the Bloss and cubic-parabola families.
An empty coefficient list is the zero polynomial: a straight line.
Sinusoid
k(s) = mean + amplitude * sin(angular_frequency * s + phase).
The sinusoidal transition family (Klein, cosine ramps). Kept distinct
from Polynomial because it is exactly representable this way and a
truncated series would not be.
Fields
Composite
k(s) = sum c_i s^i + sum A_j sin(w_j s + p_j).
A polynomial and any number of harmonic terms at once. Neither
Polynomial nor Sinusoid can hold a law with both a secular trend and
an oscillation, so a curve of that shape previously had to be refused or
approximated; this variant stores it exactly.
The shape is flat and additive rather than a recursive Sum(Vec<Self>).
A recursive sum would let the same function be written in unboundedly
many ways, would make is_constant a search over arbitrary trees, and
would admit nested sums that mean nothing extra. Flattening keeps one
canonical slot per kind of term, keeps the family closed under
differentiation and integration, and keeps the structural predicates a
finite check over two lists.
Empty harmonics is exactly the polynomial law; an empty polynomial with
empty harmonics is the zero law. Both are legal, so a caller assembling
terms never has to special-case the empty stage.
Fields
Piecewise
Pieces laid end to end along arc length, each with its own law.
breaks holds the INTERIOR seam positions in arc length from the
curve start, so laws.len() == breaks.len() + 1 and piece i spans
breaks[i - 1] .. breaks[i], the first starting at 0 and the last
ending at the carrying curve’s length.
Each piece’s law is written in its OWN arc length, restarting at zero at its seam, so a piece does not depend on where it sits and moving one never rewrites its coefficients.
This variant exists because a piecewise profile genuinely cannot be
decomposed into several Intrinsic2 values. Every piece after the
first would need an absolute start frame whose origin is the position
at the seam, and that position is the non-elementary integral this
crate refuses to compute. Holding the pieces in ONE curve keeps a
single absolute frame at the start and anchors the interior purely by
arc length, so no interior position is ever required.
Unlike a summed law, pieces are disjoint and ordered: the seams are
observable data, not a redundant re-encoding of one function. That is
why nesting is meaningful here and was not for Composite – a piece
may itself be piecewise, expressing refinement.
Mismatched lengths stay representable, as everywhere else in this
crate; the operations report None/false rather than guessing.
Implementations§
Source§impl CurvatureLaw
impl CurvatureLaw
Sourcepub fn clothoid(start: Scalar, end: Scalar, length: Scalar) -> Self
pub fn clothoid(start: Scalar, end: Scalar, length: Scalar) -> Self
A clothoid whose curvature runs from start to end over length.
This is the transition-spiral constructor: the rate is derived rather than asked for, because the two endpoint curvatures and the length are what an alignment actually specifies.
A non-finite or zero length cannot define a rate, so the result is a
constant start law – the honest degenerate answer, not a division by
zero smuggled into a coefficient.
Sourcepub fn sine_corrected_transition(
start: Scalar,
end: Scalar,
length: Scalar,
) -> Self
pub fn sine_corrected_transition( start: Scalar, end: Scalar, length: Scalar, ) -> Self
A transition whose linear ramp carries one full sine correction over
its length: k(s) = start + (d/L) s - (d / 2pi) sin(2 pi s / L), where
d = end - start.
The sine term removes the curvature-rate step a plain clothoid has at
each end, so the rate starts and ends at zero instead of jumping. The
mean rate is still d / L, so the total turning is unchanged from the
clothoid’s (start + end) / 2 * L.
As with clothoid, a non-finite or zero length cannot define a rate,
so the result degrades to a constant start law.
Sourcepub fn piecewise(breaks: Vec<Scalar>, laws: Vec<CurvatureLaw>) -> Self
pub fn piecewise(breaks: Vec<Scalar>, laws: Vec<CurvatureLaw>) -> Self
Pieces laid end to end, each carrying its own law.
The seams are interior positions in ascending arc length; the caller
supplies one more law than seam. A mismatch is storable and reported
by is_well_formed, not rejected here.
Sourcepub fn is_well_formed(&self) -> bool
pub fn is_well_formed(&self) -> bool
Whether the stored shape is internally consistent.
Only Piecewise can be malformed: it carries two lists whose lengths
must agree and seams that must ascend. Every other variant is
well-formed by construction, so this is true for them.
Structural, like the other predicates: it inspects stored data and never evaluates the law. Non-finite or descending seams are reported rather than silently sorted, because reordering would change which piece owns which arc length.
Sourcepub fn is_straight(&self) -> bool
pub fn is_straight(&self) -> bool
Whether the law is identically zero, i.e. a straight line.
Exact: this is a structural test on the stored coefficients, not a sampled one.
Sourcepub fn is_constant(&self) -> bool
pub fn is_constant(&self) -> bool
Whether the law is constant in arc length.
Sourcepub fn constant_value(&self) -> Option<Scalar>
pub fn constant_value(&self) -> Option<Scalar>
The law’s value, when it does not vary with arc length.
Returns None for a law that varies, so a caller that needs a
single number cannot silently read one off a varying law. The
variants that is_constant accepts are exactly the ones answered
here: a frozen harmonic contributes A * sin(p), which is why a
zero-FREQUENCY term is constant without being zero.
Sourcepub fn seams_within(&self, span: Scalar) -> Vec<Scalar> ⓘ
pub fn seams_within(&self, span: Scalar) -> Vec<Scalar> ⓘ
Interior seam positions strictly inside (0, span), ascending.
A piecewise law is only piecewise-smooth: its value can jump at a seam. Gauss-Legendre quadrature assumes the integrand is smooth across a panel, so a panel straddling a seam loses most of its accuracy – measured at 3.0e-3 on a joined curve, against 1e-9 once panels break at the seam. An integrator must therefore split its panels here rather than spreading them uniformly.
Nested piecewise laws report their inner seams too, in absolute arc length from this law’s origin, because a piece may itself be piecewise and its seams are just as discontinuous.
Sourcepub fn derivative(&self) -> Self
pub fn derivative(&self) -> Self
The derivative law dk/ds, in closed form.
Exact symbolic differentiation. The sharpness of a clothoid is the constant this returns.
Sourcepub fn shifted(&self, a: Scalar) -> Option<Self>
pub fn shifted(&self, a: Scalar) -> Option<Self>
The same function re-written in a coordinate that starts at a.
Returns the law g with g(u) = self(a + u). This is what a TRIM
needs: restricting a curve to [a, b] does not approximate the
shape, it re-anchors the same law, so the trimmed curve is exactly
the original one on that span (ADR 0062).
The family is closed under this operation, which is why trimming is exact rather than a refit:
- a polynomial shifts by the binomial expansion of
(a + u)^i; - a sinusoid shifts purely in PHASE,
p -> p + w * a, because the amplitude and frequency do not depend on where the window starts; - a piecewise law drops the pieces that end before
a, rebases the one containinga, and keeps the rest with their seams moved back.
Returns None when a is not finite, or when a piecewise law is
malformed, since the shifted law would then be a guess.
Sourcepub fn reversed_orientation(&self) -> Self
pub fn reversed_orientation(&self) -> Self
The mirrored law -k(s): the same curve reflected.
Trait Implementations§
Source§impl Clone for CurvatureLaw
impl Clone for CurvatureLaw
Source§fn clone(&self) -> CurvatureLaw
fn clone(&self) -> CurvatureLaw
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more