1use std::borrow::Cow;
90
91use axiolid_contracts::{BackendId, GeomError, GeomResult, Operation};
92use axiolid_core::{Frame2, Point2, Point3, Scalar, Vec2, Vec3};
93use axiolid_curve::{
94 ChainPiece2, CurvatureLaw, Curve2, Curve3, ElevationLaw, Intrinsic3, Line2, Line3, OffsetFrame,
95 OffsetLaw,
96};
97pub use axiolid_curve_evaluate_contract::DistanceInterval;
98
99use super::composite::{StationCurve, StationPiece};
100use super::offset::{OffsetTable, StationOffset};
101use super::{station_length2, station_length3, StationSeam, KEEPS_UP_TOLERANCE};
102use crate::arc_parameter::{
103 parameter_at_arc_length2, parameter_at_arc_length3, ARC_LENGTH_TOLERANCE,
104};
105use crate::bound::{
106 continuity_breaks2, continuity_breaks3, curve_derivative_bounds2, curve_derivative_bounds3,
107 curve_fourth_bound2, curve_fourth_bound3, CurveDerivativeBounds,
108};
109use crate::curve::{
110 derivative2, derivative3, domain2, domain3, evaluate2, evaluate3, second_derivative2,
111 second_derivative3,
112};
113use beside::chord_offset_length;
114use panels::{adapt, finish, product, Compensated, Jet, Measured, PANEL_SHARE, VELOCITY_ROUNDING};
115
116mod beside;
117mod intrinsic;
118mod panels;
119mod reading;
120
121pub(crate) use intrinsic::{reads_intrinsic, IntrinsicReader};
122pub(crate) use reading::{PieceReader, AXIS_ROUNDING};
123
124pub const UNCERTIFIED_LENGTH: &str =
126 "a certified length of a station piece no bound here reaches: an offset of a banked curve, a \
127 3D intrinsic curve with torsion or out of a level plane, a chain's parametric piece or an \
128 elevated curve with an intrinsic profile in a leaning frame; a 3D offset direction that is \
129 not vertical beside a plan-measured or intrinsic base; a base placed in a frame that tilts \
130 +Z; a skewed frame; or a tolerance below the points' rounding";
131
132pub const POINT_ROUNDING: Scalar = 1e-14;
137
138const VALUE_ROUNDING: Scalar = 1e-14;
140
141pub const MAX_CERTIFIED_PANELS: usize = 1 << 16;
143
144const COS_OF_ATAN: [Scalar; 4] = [1.0, 0.39, 1.0, 1.94];
146
147const SIN_OF_ATAN: [Scalar; 4] = [1.0, 1.0, 0.86, 3.0];
149
150fn uncertified() -> GeomError {
151 GeomError::UnsupportedInput {
152 backend: BackendId::new("axiolid-evaluate"),
153 operation: Operation::CurveEvaluation,
154 input: UNCERTIFIED_LENGTH,
155 }
156}
157
158fn budget() -> GeomError {
159 GeomError::BudgetExceeded {
160 resource: "certified length panels",
161 }
162}
163
164fn slack(distance: Scalar) -> Scalar {
165 ARC_LENGTH_TOLERANCE * distance.abs().max(1.0)
166}
167
168pub(crate) fn checked_tolerance(tolerance: Scalar) -> GeomResult<Scalar> {
174 if tolerance.is_finite() && tolerance > 0.0 {
175 Ok(tolerance)
176 } else {
177 Err(GeomError::InvalidInput(format!(
178 "station: a certified distance needs a finite, positive tolerance, not {tolerance}"
179 )))
180 }
181}
182
183#[derive(Debug, Clone, Copy)]
188pub(crate) struct Running {
189 lower: Scalar,
190 upper: Scalar,
191 terms: usize,
192 exact: bool,
193}
194
195impl Running {
196 pub(crate) const ZERO: Self = Self {
197 lower: 0.0,
198 upper: 0.0,
199 terms: 0,
200 exact: true,
201 };
202
203 pub(crate) fn plus(self, term: DistanceInterval) -> Self {
205 Self {
206 lower: self.lower + term.lower,
207 upper: self.upper + term.upper,
208 terms: self.terms + 1,
209 exact: self.exact && term.is_point(),
210 }
211 }
212
213 pub(crate) fn interval(self) -> DistanceInterval {
215 if self.exact {
216 return DistanceInterval::new(self.lower, self.upper);
217 }
218 let rounding = 2.0 * (self.terms as Scalar + 1.0) * Scalar::EPSILON;
219 DistanceInterval::new(
220 self.lower - rounding * self.lower.abs(),
221 self.upper + rounding * self.upper.abs(),
222 )
223 }
224}
225
226pub(crate) fn piece_length(
237 piece: &StationPiece<'_>,
238 table: Option<&OffsetTable>,
239 tolerance: Scalar,
240) -> GeomResult<DistanceInterval> {
241 match piece.curve {
242 StationCurve::Offset(offset) => {
243 if offset.measure_is_exact() {
244 return Ok(DistanceInterval::point(piece.length()));
245 }
246 let built;
247 let table = match table {
248 Some(table) => table,
249 None => {
250 built = offset.table()?;
251 &built
252 }
253 };
254 let a = offset.base_measure(piece.start, table)?;
255 let b = offset.base_measure(piece.end, table)?;
256 offset_length(&offset, a.min(b), a.max(b), tolerance)
257 }
258 curve if curve.measure_is_exact() => Ok(DistanceInterval::point(piece.length())),
259 curve => curve_span(curve, piece.start, piece.end, tolerance),
260 }
261}
262
263pub(crate) fn curve_span(
271 curve: StationCurve<'_>,
272 m0: Scalar,
273 m1: Scalar,
274 tolerance: Scalar,
275) -> GeomResult<DistanceInterval> {
276 if curve.measure_is_exact() {
277 return Ok(DistanceInterval::point(m1 - m0));
278 }
279 let native = Native::of(curve, m0, m1)?;
280 let (t0, t1) = (native.parameter(m0)?, native.parameter(m1)?);
281 let mut breaks = native.breaks(2);
283 breaks.extend(native.breaks(1));
284 native.measured(|measured| measured.chord_turning(t0.min(t1), t0.max(t1), &breaks, tolerance))
285}
286
287fn offset_length(
291 offset: &StationOffset<'_>,
292 va: Scalar,
293 vb: Scalar,
294 tolerance: Scalar,
295) -> GeomResult<DistanceInterval> {
296 if vb <= va {
297 return Ok(DistanceInterval::point(0.0));
298 }
299 if let Some(model) = PlanModel::of(offset)? {
300 return model.length(va, vb, tolerance);
301 }
302 chord_offset_length(offset, va, vb, tolerance)
303}
304
305#[derive(Debug, Clone)]
310enum Native<'c> {
311 Two(Cow<'c, Curve2>),
312 Three(Cow<'c, Curve3>),
313}
314
315impl<'c> Native<'c> {
316 fn of(curve: StationCurve<'c>, m0: Scalar, m1: Scalar) -> GeomResult<Self> {
318 let mid = 0.5 * (m0 + m1);
319 match curve {
320 StationCurve::Two(Curve2::Polyline(polyline)) => {
321 let (origin, unit) = segment(&polyline.points, polyline.closed, mid, |p| {
322 Vec3::new(p.x, p.y, 0.0)
323 })?;
324 Ok(Self::Two(Cow::Owned(Curve2::Line(Line2 {
325 origin: Point2::new(origin.x, origin.y),
326 direction: Vec2::new(unit.x, unit.y),
327 }))))
328 }
329 StationCurve::Three(Curve3::Polyline(polyline)) => {
330 let (origin, unit) = segment(&polyline.points, polyline.closed, mid, |p| p)?;
331 Ok(Self::Three(Cow::Owned(Curve3::Line(Line3 {
332 origin,
333 direction: unit,
334 }))))
335 }
336 StationCurve::Two(
337 curve @ (Curve2::Line(_)
338 | Curve2::Circle(_)
339 | Curve2::Ellipse(_)
340 | Curve2::BSpline(_)),
341 ) => Ok(Self::Two(Cow::Borrowed(curve))),
342 StationCurve::Three(
343 curve @ (Curve3::Line(_)
344 | Curve3::Circle(_)
345 | Curve3::Ellipse(_)
346 | Curve3::BSpline(_)),
347 ) => Ok(Self::Three(Cow::Borrowed(curve))),
348 _ => Err(uncertified()),
349 }
350 }
351
352 fn point(&self, t: Scalar) -> GeomResult<Point3> {
353 match self {
354 Self::Two(curve) => {
355 let p = evaluate2(curve, t)?;
356 Ok(Point3::new(p.x, p.y, 0.0))
357 }
358 Self::Three(curve) => evaluate3(curve, t),
359 }
360 }
361
362 fn derivative(&self, t: Scalar) -> GeomResult<Vec3> {
363 match self {
364 Self::Two(curve) => {
365 let d = derivative2(curve, t)?;
366 Ok(Vec3::new(d.x, d.y, 0.0))
367 }
368 Self::Three(curve) => derivative3(curve, t),
369 }
370 }
371
372 fn second_derivative(&self, t: Scalar) -> GeomResult<Vec3> {
373 match self {
374 Self::Two(curve) => {
375 let d = second_derivative2(curve, t)?;
376 Ok(Vec3::new(d.x, d.y, 0.0))
377 }
378 Self::Three(curve) => second_derivative3(curve, t),
379 }
380 }
381
382 fn extent(&self) -> Scalar {
386 let flat = |v: Vec2| v.abs().max_element();
387 let out = match self {
388 Self::Two(curve) => match curve.as_ref() {
389 Curve2::Line(line) => flat(line.origin),
390 Curve2::Circle(c) => {
391 flat(c.frame.origin) + c.radius * (flat(c.frame.x) + flat(c.frame.y))
392 }
393 Curve2::Ellipse(e) => {
394 flat(e.frame.origin)
395 + e.semi_axis_x * flat(e.frame.x)
396 + e.semi_axis_y * flat(e.frame.y)
397 }
398 Curve2::BSpline(spline) => spline
399 .control_points
400 .iter()
401 .map(|p| flat(*p))
402 .fold(0.0, Scalar::max),
403 _ => Scalar::INFINITY,
404 },
405 Self::Three(curve) => match curve.as_ref() {
406 Curve3::Line(line) => line.origin.abs().max_element(),
407 Curve3::Circle(c) => {
408 c.frame.origin.abs().max_element()
409 + c.radius * (c.frame.x.abs().max_element() + c.frame.y.abs().max_element())
410 }
411 Curve3::Ellipse(e) => {
412 e.frame.origin.abs().max_element()
413 + e.semi_axis_x * e.frame.x.abs().max_element()
414 + e.semi_axis_y * e.frame.y.abs().max_element()
415 }
416 Curve3::BSpline(spline) => spline
417 .control_points
418 .iter()
419 .map(|p| p.abs().max_element())
420 .fold(0.0, Scalar::max),
421 _ => Scalar::INFINITY,
422 },
423 };
424 out.max(1.0)
425 }
426
427 fn bounds(&self, a: Scalar, b: Scalar) -> GeomResult<CurveDerivativeBounds> {
429 match self {
430 Self::Two(curve) => curve_derivative_bounds2(curve, a, b),
431 Self::Three(curve) => curve_derivative_bounds3(curve, a, b),
432 }
433 .filter(|d| d.first.is_finite() && d.second.is_finite() && d.third.is_finite())
434 .ok_or_else(uncertified)
435 }
436
437 fn fourth(&self, a: Scalar, b: Scalar) -> Option<Scalar> {
439 match self {
440 Self::Two(curve) => curve_fourth_bound2(curve, a, b),
441 Self::Three(curve) => curve_fourth_bound3(curve, a, b),
442 }
443 .filter(|d| d.is_finite())
444 }
445
446 fn measured<R>(&self, run: impl FnOnce(&Measured<'_>) -> GeomResult<R>) -> GeomResult<R> {
449 let point = |t: Scalar| Ok((self.point(t)?, 0.0));
450 let velocity = |t: Scalar| {
451 let d = self.derivative(t)?;
452 Ok((d, VELOCITY_ROUNDING * d.length()))
453 };
454 let bounds = |a: Scalar, b: Scalar| {
455 let d = self.bounds(a, b)?;
456 Ok((d.second, Some(d.third)))
457 };
458 run(&Measured {
459 point: &point,
460 velocity: &velocity,
461 bounds: &bounds,
462 scale: self.extent(),
463 })
464 }
465
466 fn breaks(&self, k: usize) -> Vec<Scalar> {
468 match self {
469 Self::Two(curve) => continuity_breaks2(curve, k),
470 Self::Three(curve) => continuity_breaks3(curve, k),
471 }
472 }
473
474 fn rate(&self) -> Option<Scalar> {
477 match self {
478 Self::Two(curve) => match curve.as_ref() {
479 Curve2::Line(line) => Some(line.direction.length()),
480 Curve2::Circle(circle) => Some(circle.radius),
481 _ => None,
482 },
483 Self::Three(curve) => match curve.as_ref() {
484 Curve3::Line(line) => Some(line.direction.length()),
485 Curve3::Circle(circle) => Some(circle.radius),
486 _ => None,
487 },
488 }
489 }
490
491 fn parameter(&self, m: Scalar) -> GeomResult<Scalar> {
498 if let Some(rate) = self.rate() {
499 return Ok(m / rate);
500 }
501 let (length, lo, hi, seams) = match self {
502 Self::Two(curve) => {
503 let domain = domain2(curve);
504 (
505 station_length2(curve)?,
506 domain.start.min(domain.end),
507 domain.start.max(domain.end),
508 super::station_seams2(curve)?,
509 )
510 }
511 Self::Three(curve) => {
512 let domain = domain3(curve);
513 (
514 station_length3(curve)?,
515 domain.start.min(domain.end),
516 domain.start.max(domain.end),
517 super::station_seams3(curve)?,
518 )
519 }
520 };
521 let length = length.ok_or_else(uncertified)?;
522 let closed = match self {
523 Self::Two(curve) => matches!(curve.as_ref(), Curve2::Ellipse(_)),
524 Self::Three(curve) => matches!(curve.as_ref(), Curve3::Ellipse(_)),
525 };
526 let (turn, m) = if closed && m > length + slack(length) {
527 (hi - lo, m - length)
528 } else {
529 (0.0, m)
530 };
531 let t = if m.abs() <= slack(0.0) {
532 lo
533 } else if (m - length).abs() <= slack(length) {
534 hi
535 } else if let Some(seam) = seams
536 .iter()
537 .find(|seam| (seam.distance - m).abs() <= slack(m))
538 {
539 seam.parameter
540 } else {
541 match self {
542 Self::Two(curve) => parameter_at_arc_length2(curve, lo, m)?,
543 Self::Three(curve) => parameter_at_arc_length3(curve, lo, m)?,
544 }
545 };
546 Ok(turn + t)
547 }
548}
549
550pub(crate) fn read_parameter(curve: StationCurve<'_>, m: Scalar) -> GeomResult<Scalar> {
554 Native::of(curve, m, m)?.parameter(m)
555}
556
557#[derive(Debug, Clone)]
563pub(crate) struct CurveLengths {
564 native: Native<'static>,
565 table: beside::BaseTable,
566}
567
568impl CurveLengths {
569 pub(crate) fn new(curve: &Curve2, t0: Scalar, t1: Scalar, target: Scalar) -> GeomResult<Self> {
576 if !matches!(
577 curve,
578 Curve2::Line(_) | Curve2::Circle(_) | Curve2::Ellipse(_) | Curve2::BSpline(_)
579 ) {
580 return Err(uncertified());
581 }
582 let native = Native::Two(Cow::Owned(curve.clone()));
583 let mut breaks = native.breaks(2);
584 breaks.extend(native.breaks(1));
585 let table = native.measured(|m| beside::BaseTable::build(m, t0, t1, &breaks, target))?;
586 Ok(Self { native, table })
587 }
588
589 pub(crate) fn at(&self, t: Scalar) -> GeomResult<(Scalar, Scalar)> {
595 self.native.measured(|m| self.table.at(m, t))
596 }
597
598 pub(crate) fn total(&self) -> (Scalar, Scalar) {
600 self.table.total()
601 }
602
603 pub(crate) fn panels(&self) -> Vec<(Scalar, Scalar)> {
605 self.table.spans()
606 }
607
608 pub(crate) fn extent(&self) -> Scalar {
610 self.native.extent()
611 }
612}
613
614fn segment<P: Copy>(
617 points: &[P],
618 closed: bool,
619 mid: Scalar,
620 lift: impl Fn(P) -> Vec3,
621) -> GeomResult<(Point3, Vec3)> {
622 let count = points.len();
623 let spans = if closed {
624 count
625 } else {
626 count.saturating_sub(1)
627 };
628 let mut run = 0.0;
629 let mut found = None;
630 for i in 0..spans {
631 let (a, b) = (lift(points[i]), lift(points[(i + 1) % count]));
632 let length = (b - a).length();
633 if length > 0.0 {
634 found = Some((a, (b - a) / length, run));
635 if mid <= run + length {
636 break;
637 }
638 }
639 run += length;
640 }
641 let (start, unit, at) = found.ok_or_else(uncertified)?;
642 Ok((start - unit * at, unit))
643}
644
645fn sum<const N: usize>(f: Jet<N>, g: Jet<N>) -> Jet<N> {
648 core::array::from_fn(|k| f[k] + g[k])
649}
650
651fn composed(outer: Jet<4>, p: Jet<4>) -> Jet<4> {
655 [
656 outer[0],
657 outer[1] * p[1],
658 outer[2] * p[1] * p[1] + outer[1] * p[2],
659 outer[3] * p[1].powi(3) + 3.0 * outer[2] * p[1] * p[2] + outer[1] * p[3],
660 ]
661}
662
663fn linear(pair: (Scalar, Scalar), length: Scalar) -> Jet<4> {
666 [
667 pair.0.abs().max(pair.1.abs()),
668 (pair.1 - pair.0).abs() / length,
669 0.0,
670 0.0,
671 ]
672}
673
674#[derive(Debug, Clone, Copy)]
676enum Curvature<'a> {
677 Constant(Scalar),
679 Law {
682 law: &'a CurvatureLaw,
683 shift: Scalar,
684 sign: Scalar,
685 },
686}
687
688impl Curvature<'_> {
689 fn value(&self, s: Scalar) -> GeomResult<Scalar> {
690 match self {
691 Self::Constant(k) => Ok(*k),
692 Self::Law { law, shift, sign } => crate::frenet::law_value(law, s - shift)
693 .map(|k| sign * k)
694 .ok_or_else(uncertified),
695 }
696 }
697
698 fn jet(&self, lo: Scalar, hi: Scalar) -> GeomResult<Jet<3>> {
700 match self {
701 Self::Constant(k) => Ok([k.abs(), 0.0, 0.0]),
702 Self::Law { law, shift, .. } => {
703 let (lo, hi) = (lo - shift, hi - shift);
704 let first = law.derivative();
705 let second = first.derivative();
706 let bound = |law: &CurvatureLaw| {
707 crate::chain::curvature_bound(law, lo, hi)
708 .filter(|b| b.is_finite())
709 .ok_or_else(uncertified)
710 };
711 Ok([bound(law)?, bound(&first)?, bound(&second)?])
712 }
713 }
714 }
715}
716
717fn handedness(frame: &Frame2) -> GeomResult<Scalar> {
724 let skew = (frame.x.length() - 1.0)
725 .abs()
726 .max((frame.y.length() - 1.0).abs())
727 .max(frame.x.dot(frame.y).abs());
728 if skew.is_nan() || skew > 1e-12 {
729 return Err(uncertified());
730 }
731 Ok(frame.x.perp_dot(frame.y).signum())
732}
733
734fn plan_curvature(plan: &Curve2, lo: Scalar, hi: Scalar) -> GeomResult<Curvature<'_>> {
737 match plan {
738 Curve2::Line(_) | Curve2::Polyline(_) => Ok(Curvature::Constant(0.0)),
739 Curve2::Circle(circle) => {
740 let sign = handedness(&circle.frame)?;
741 if !(circle.radius.is_finite() && circle.radius > 0.0) {
742 return Err(uncertified());
743 }
744 Ok(Curvature::Constant(sign / circle.radius))
745 }
746 Curve2::Intrinsic(intrinsic) => Ok(Curvature::Law {
747 law: &intrinsic.curvature,
748 shift: 0.0,
749 sign: handedness(&intrinsic.start)?,
750 }),
751 Curve2::Chain(chain) => {
752 let sign = handedness(&chain.start)?;
753 let mid = 0.5 * (lo + hi);
754 match chain.piece_at(mid) {
755 Some((_, ChainPiece2::Intrinsic { curvature, .. }, local)) => Ok(Curvature::Law {
756 law: curvature,
757 shift: mid - local,
758 sign,
759 }),
760 _ => Err(uncertified()),
761 }
762 }
763 _ => Err(uncertified()),
764 }
765}
766
767fn level_curvature(intrinsic: &Intrinsic3) -> GeomResult<Curvature<'_>> {
781 let (x, y) = (intrinsic.start.x, intrinsic.start.y);
782 if !intrinsic.is_planar() || x.z != 0.0 || y.z != 0.0 {
783 return Err(uncertified());
784 }
785 let plan = Frame2 {
786 origin: Point2::new(intrinsic.start.origin.x, intrinsic.start.origin.y),
787 x: Vec2::new(x.x, x.y),
788 y: Vec2::new(y.x, y.y),
789 };
790 Ok(Curvature::Law {
791 law: &intrinsic.curvature,
792 shift: 0.0,
793 sign: handedness(&plan)?,
794 })
795}
796
797struct PlanModel<'a> {
800 curvature: Curvature<'a>,
801 grade: Option<&'a ElevationLaw>,
804 lateral: (Scalar, Scalar),
805 vertical: (Scalar, Scalar),
806 longitudinal: (Scalar, Scalar),
807 vertical_rate: bool,
810 start: Scalar,
811 end: Scalar,
812 reversed: bool,
813}
814
815impl<'a> PlanModel<'a> {
816 fn of(offset: &StationOffset<'a>) -> GeomResult<Option<Self>> {
820 let base = offset.base();
821 let (lo, hi) = (base.start, base.end);
822 let (curvature, elevation, vertical_rate) = match base.curve {
823 StationCurve::Two(plan @ (Curve2::Intrinsic(_) | Curve2::Chain(_))) => {
824 (plan_curvature(plan, lo, hi)?, None, true)
825 }
826 StationCurve::Three(Curve3::Elevated(elevated)) => (
827 plan_curvature(&elevated.plan, lo, hi)?,
828 Some(&elevated.elevation),
829 false,
830 ),
831 StationCurve::Three(Curve3::Intrinsic(intrinsic)) => {
832 (level_curvature(intrinsic)?, None, true)
833 }
834 StationCurve::Three(Curve3::Banked(_)) => return Err(uncertified()),
835 _ => return Ok(None),
836 };
837 if let Some(rigid) = base.placement {
838 if (rigid.transform_vector3(Vec3::Z) - Vec3::Z).length() > KEEPS_UP_TOLERANCE {
839 return Err(uncertified());
840 }
841 }
842 let zero = (0.0, 0.0);
843 let (lateral, vertical, longitudinal, leans) = match offset.law() {
844 OffsetLaw::Planar { distance } => ((distance, distance), zero, zero, false),
845 OffsetLaw::Directed {
846 distance,
847 reference_direction,
848 } => {
849 let v = reference_direction.normalize();
850 if v.x.hypot(v.y) > 1e-12 {
851 return Err(uncertified());
852 }
853 let a = distance * v.z.signum();
854 ((a, a), zero, zero, false)
855 }
856 OffsetLaw::Linear { start, end, frame } => {
857 let leans = match frame {
858 OffsetFrame::Section => true,
859 OffsetFrame::Plan => false,
860 _ => return Err(uncertified()),
861 };
862 (
863 (start.lateral, end.lateral),
864 (start.vertical, end.vertical),
865 (start.longitudinal, end.longitudinal),
866 leans,
867 )
868 }
869 _ => return Err(uncertified()),
870 };
871 let grade = elevation.filter(|_| leans && (vertical != zero || longitudinal != zero));
872 Ok(Some(Self {
873 curvature,
874 grade,
875 lateral,
876 vertical,
877 longitudinal,
878 vertical_rate,
879 start: base.start,
880 end: base.end,
881 reversed: base.reversed,
882 }))
883 }
884
885 fn length_of_base(&self) -> Scalar {
886 self.end - self.start
887 }
888
889 fn measure(&self, v: Scalar) -> Scalar {
891 if self.reversed {
892 self.end - v
893 } else {
894 self.start + v
895 }
896 }
897
898 fn travel(&self) -> Scalar {
899 if self.reversed {
900 -1.0
901 } else {
902 1.0
903 }
904 }
905
906 fn speed(&self, v: Scalar) -> GeomResult<(Scalar, Scalar)> {
908 let length = self.length_of_base();
909 let s = self.measure(v);
910 let u = (v / length).clamp(0.0, 1.0);
911 let at = |pair: (Scalar, Scalar)| pair.0 + (pair.1 - pair.0) * u;
912 let rate = |pair: (Scalar, Scalar)| (pair.1 - pair.0) / length;
913 let k = self.travel() * self.curvature.value(s)?;
914 let (a, da) = (at(self.lateral), rate(self.lateral));
915 let (long, dlong) = (at(self.longitudinal), rate(self.longitudinal));
916 let (vert, dvert) = (at(self.vertical), rate(self.vertical));
917 let (b, db) = match self.grade {
918 Some(law) => {
919 let p = self.travel() * crate::elevation::elevation_grade(law, s)?;
920 let q = crate::elevation::elevation_second(law, s)?;
921 let cos = 1.0 / p.hypot(1.0);
922 let sin = p * cos;
923 let turn = q / (1.0 + p * p);
924 (
925 long * cos - vert * sin,
926 dlong * cos - long * sin * turn - dvert * sin - vert * cos * turn,
927 )
928 }
929 None => (long, dlong),
930 };
931 let forward = 1.0 + db - a * k;
932 let across = da + b * k;
933 let rise = if self.vertical_rate { dvert } else { 0.0 };
934 let speed = (forward * forward + across * across + rise * rise).sqrt();
935 if !speed.is_finite() {
936 return Err(uncertified());
937 }
938 Ok((forward, speed))
939 }
940
941 fn bounds(&self, va: Scalar, vb: Scalar) -> GeomResult<(Scalar, Jet<3>, Jet<3>)> {
945 let length = self.length_of_base();
946 let (sa, sb) = (self.measure(va), self.measure(vb));
947 let (lo, hi) = (sa.min(sb), sa.max(sb));
948 let k = self.curvature.jet(lo, hi)?;
949 let k: Jet<4> = [k[0], k[1], k[2], 0.0];
950 let a = linear(self.lateral, length);
951 let long = linear(self.longitudinal, length);
952 let vert = linear(self.vertical, length);
953 let b = match self.grade {
954 Some(law) => {
955 let z = crate::elevation::profile_bounds4(law, lo, hi).ok_or_else(uncertified)?;
956 let p: Jet<4> = z;
957 sum(
958 product(long, composed(COS_OF_ATAN, p)),
959 product(vert, composed(SIN_OF_ATAN, p)),
960 )
961 }
962 None => long,
963 };
964 let ak = product(a, k);
965 let bk = product(b, k);
966 let forward = [1.0 + b[1] + ak[0], b[2] + ak[1], b[3] + ak[2]];
967 let across = [a[1] + bk[0], a[2] + bk[1], a[3] + bk[2]];
968 Ok((forward[1], forward, across))
969 }
970
971 fn panel(&self, a: Scalar, b: Scalar) -> GeomResult<(Scalar, Scalar, Scalar, Scalar)> {
975 let h = b - a;
976 let mid = 0.5 * (a + b);
977 let (forward_mid, speed_mid) = self.speed(mid)?;
978 let (rate, forward, across) = self.bounds(a, b)?;
979 let floor = forward_mid - 0.5 * h * rate;
980 let error = if floor > 0.0 {
981 let g1 = 2.0 * (forward[0] * forward[1] + across[0] * across[1]);
982 let g2 = 2.0
983 * (forward[1] * forward[1]
984 + forward[0] * forward[2]
985 + across[1] * across[1]
986 + across[0] * across[2]);
987 let gmin = floor * floor;
988 let bend = g2 / (2.0 * floor) + g1 * g1 / (4.0 * gmin * floor);
989 h * h * h / 24.0 * bend + VALUE_ROUNDING * h * speed_mid
990 } else {
991 Scalar::INFINITY
992 };
993 if !error.is_finite() {
994 return Ok((a, b, Scalar::NEG_INFINITY, Scalar::INFINITY));
995 }
996 Ok((a, b, h * speed_mid - error, h * speed_mid + error))
997 }
998
999 fn length(&self, va: Scalar, vb: Scalar, tolerance: Scalar) -> GeomResult<DistanceInterval> {
1002 let refined = adapt(
1003 vec![self.panel(va, vb)?],
1004 PANEL_SHARE * tolerance,
1005 |p| p.3 - p.2,
1006 |&(a, b, ..)| {
1007 let mid = 0.5 * (a + b);
1008 if !(mid > a && mid < b) {
1009 return Err(uncertified());
1010 }
1011 Ok((self.panel(a, mid)?, self.panel(mid, b)?))
1012 },
1013 )?;
1014 let (mut lower, mut upper) = (Compensated::default(), Compensated::default());
1015 for &(_, _, lo, hi) in &refined.panels {
1016 lower.add(lo);
1017 upper.add(hi);
1018 }
1019 finish(lower.value(), upper.value(), tolerance).map_err(|error| {
1020 if refined.exhausted {
1021 budget()
1022 } else {
1023 error
1024 }
1025 })
1026 }
1027}
1028
1029#[derive(Debug, Clone, Copy, PartialEq)]
1034pub(crate) struct CertifiedSeam {
1035 pub(crate) distance: Scalar,
1037 pub(crate) interval: DistanceInterval,
1039}
1040
1041pub(crate) fn atomic_seams(
1045 curve: StationCurve<'_>,
1046 seams: Vec<StationSeam>,
1047 tolerance: Scalar,
1048) -> GeomResult<Vec<CertifiedSeam>> {
1049 seams
1050 .into_iter()
1051 .filter(|seam| !seam.smooth)
1052 .map(|seam| {
1053 let interval = if seam.exact {
1054 DistanceInterval::point(seam.distance)
1055 } else {
1056 curve_span(curve, 0.0, seam.distance, tolerance)?
1057 };
1058 Ok(CertifiedSeam {
1059 distance: seam.distance,
1060 interval,
1061 })
1062 })
1063 .collect()
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068 use super::*;
1069
1070 #[test]
1074 fn the_grade_suprema_bound_their_functions() {
1075 let derivatives = |p: Scalar| {
1076 let u = 1.0 + p * p;
1077 (
1078 [
1079 u.powf(-0.5),
1080 -p * u.powf(-1.5),
1081 (2.0 * p * p - 1.0) * u.powf(-2.5),
1082 p * (9.0 - 6.0 * p * p) * u.powf(-3.5),
1083 ],
1084 [
1085 p * u.powf(-0.5),
1086 u.powf(-1.5),
1087 -3.0 * p * u.powf(-2.5),
1088 (12.0 * p * p - 3.0) * u.powf(-3.5),
1089 ],
1090 )
1091 };
1092 for i in -200_000..=200_000 {
1093 let p = Scalar::from(i) * 1e-4;
1094 let (cos, sin) = derivatives(p);
1095 for k in 0..4 {
1096 assert!(cos[k].abs() <= COS_OF_ATAN[k], "cos^({k}) at {p}");
1097 assert!(sin[k].abs() <= SIN_OF_ATAN[k], "sin^({k}) at {p}");
1098 }
1099 }
1100 let h = 1e-5;
1101 for p in [-3.0, -0.4, 0.0, 0.3, 2.5] {
1102 let (at, _) = derivatives(p);
1103 let (up, _) = derivatives(p + h);
1104 let (down, _) = derivatives(p - h);
1105 for k in 0..3 {
1106 let difference = (up[k] - down[k]) / (2.0 * h);
1107 assert!(
1108 (difference - at[k + 1]).abs() < 1e-6,
1109 "cos^({}) at {p}",
1110 k + 1
1111 );
1112 }
1113 }
1114 }
1115
1116 fn cubic(a: Scalar, reach: Scalar) -> Curve2 {
1120 Curve2::BSpline(axiolid_curve::BSplineCurve2 {
1121 degree: 3,
1122 control_points: vec![
1123 Point2::new(0.0, 0.0),
1124 Point2::new(reach / 3.0, 0.0),
1125 Point2::new(2.0 * reach / 3.0, 0.0),
1126 Point2::new(reach, a * reach.powi(3)),
1127 ],
1128 knots: vec![0.0, 1.0],
1129 multiplicities: vec![4, 4],
1130 weights: None,
1131 closed: false,
1132 self_intersect: None,
1133 knot_spec: axiolid_curve::KnotSpec::PiecewiseBezier,
1134 })
1135 }
1136
1137 fn cubic_series(a: Scalar, x: Scalar) -> Scalar {
1138 let z = 9.0 * a * a * x.powi(4);
1139 let (mut binomial, mut power, mut sum) = (1.0, 1.0, 0.0);
1140 for n in 0..200 {
1141 let term = binomial * power / (4.0 * Scalar::from(n) + 1.0);
1142 sum += term;
1143 if term.abs() < 1e-30 {
1144 break;
1145 }
1146 binomial *= (0.5 - Scalar::from(n)) / (Scalar::from(n) + 1.0);
1147 power *= z;
1148 }
1149 x * sum
1150 }
1151
1152 #[test]
1153 fn curve_lengths_enclose_the_cubic_series() {
1154 let (radius, reach) = (300.0, 60.0);
1155 let a = 1.0 / (6.0 * radius * reach);
1156 let curve = cubic(a, reach);
1157 for target in [1e-9, 1e-11] {
1158 let lengths = CurveLengths::new(&curve, 0.0, 1.0, target).unwrap();
1159 for i in 0..=1000 {
1160 let t = Scalar::from(i) / 1000.0;
1161 let (lo, hi) = lengths.at(t).unwrap();
1162 let series = cubic_series(a, t * reach);
1163 assert!(
1164 lo <= series && series <= hi,
1165 "t={t} target={target}: {lo} {series} {hi}"
1166 );
1167 assert!(
1168 hi - lo <= 2.0 * target,
1169 "t={t} target={target}: width {}",
1170 hi - lo
1171 );
1172 }
1173 let (lo, hi) = lengths.total();
1174 let series = cubic_series(a, reach);
1175 assert!(lo <= series && series <= hi);
1176 assert!(!lengths.panels().is_empty());
1177 }
1178 }
1179
1180 #[test]
1181 fn curve_lengths_refuse_a_family_without_a_closed_form() {
1182 let curve = Curve2::Polyline(axiolid_curve::Polyline2 {
1183 points: vec![Point2::new(0.0, 0.0), Point2::new(1.0, 0.0)],
1184 closed: false,
1185 });
1186 assert!(CurveLengths::new(&curve, 0.0, 1.0, 1e-9).is_err());
1187 }
1188}