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 panels;
118mod reading;
119
120pub(crate) use reading::PieceReader;
121
122pub const UNCERTIFIED_LENGTH: &str =
124 "a certified length of a station piece no bound here reaches: an offset of a banked curve, a \
125 3D intrinsic curve with torsion or out of a level plane, a chain's parametric piece or an \
126 elevated curve with an intrinsic profile in a leaning frame; a 3D offset direction that is \
127 not vertical beside a plan-measured or intrinsic base; a base placed in a frame that tilts \
128 +Z; a skewed frame; or a tolerance below the points' rounding";
129
130pub const POINT_ROUNDING: Scalar = 1e-14;
135
136const VALUE_ROUNDING: Scalar = 1e-14;
138
139pub const MAX_CERTIFIED_PANELS: usize = 1 << 16;
141
142const COS_OF_ATAN: [Scalar; 4] = [1.0, 0.39, 1.0, 1.94];
144
145const SIN_OF_ATAN: [Scalar; 4] = [1.0, 1.0, 0.86, 3.0];
147
148fn uncertified() -> GeomError {
149 GeomError::UnsupportedInput {
150 backend: BackendId::new("axiolid-evaluate"),
151 operation: Operation::CurveEvaluation,
152 input: UNCERTIFIED_LENGTH,
153 }
154}
155
156fn budget() -> GeomError {
157 GeomError::BudgetExceeded {
158 resource: "certified length panels",
159 }
160}
161
162fn slack(distance: Scalar) -> Scalar {
163 ARC_LENGTH_TOLERANCE * distance.abs().max(1.0)
164}
165
166pub(crate) fn checked_tolerance(tolerance: Scalar) -> GeomResult<Scalar> {
172 if tolerance.is_finite() && tolerance > 0.0 {
173 Ok(tolerance)
174 } else {
175 Err(GeomError::InvalidInput(format!(
176 "station: a certified distance needs a finite, positive tolerance, not {tolerance}"
177 )))
178 }
179}
180
181#[derive(Debug, Clone, Copy)]
186pub(crate) struct Running {
187 lower: Scalar,
188 upper: Scalar,
189 terms: usize,
190 exact: bool,
191}
192
193impl Running {
194 pub(crate) const ZERO: Self = Self {
195 lower: 0.0,
196 upper: 0.0,
197 terms: 0,
198 exact: true,
199 };
200
201 pub(crate) fn plus(self, term: DistanceInterval) -> Self {
203 Self {
204 lower: self.lower + term.lower,
205 upper: self.upper + term.upper,
206 terms: self.terms + 1,
207 exact: self.exact && term.is_point(),
208 }
209 }
210
211 pub(crate) fn interval(self) -> DistanceInterval {
213 if self.exact {
214 return DistanceInterval::new(self.lower, self.upper);
215 }
216 let rounding = 2.0 * (self.terms as Scalar + 1.0) * Scalar::EPSILON;
217 DistanceInterval::new(
218 self.lower - rounding * self.lower.abs(),
219 self.upper + rounding * self.upper.abs(),
220 )
221 }
222}
223
224pub(crate) fn piece_length(
235 piece: &StationPiece<'_>,
236 table: Option<&OffsetTable>,
237 tolerance: Scalar,
238) -> GeomResult<DistanceInterval> {
239 match piece.curve {
240 StationCurve::Offset(offset) => {
241 if offset.measure_is_exact() {
242 return Ok(DistanceInterval::point(piece.length()));
243 }
244 let built;
245 let table = match table {
246 Some(table) => table,
247 None => {
248 built = offset.table()?;
249 &built
250 }
251 };
252 let a = offset.base_measure(piece.start, table)?;
253 let b = offset.base_measure(piece.end, table)?;
254 offset_length(&offset, a.min(b), a.max(b), tolerance)
255 }
256 curve if curve.measure_is_exact() => Ok(DistanceInterval::point(piece.length())),
257 curve => curve_span(curve, piece.start, piece.end, tolerance),
258 }
259}
260
261pub(crate) fn curve_span(
269 curve: StationCurve<'_>,
270 m0: Scalar,
271 m1: Scalar,
272 tolerance: Scalar,
273) -> GeomResult<DistanceInterval> {
274 if curve.measure_is_exact() {
275 return Ok(DistanceInterval::point(m1 - m0));
276 }
277 let native = Native::of(curve, m0, m1)?;
278 let (t0, t1) = (native.parameter(m0)?, native.parameter(m1)?);
279 let mut breaks = native.breaks(2);
281 breaks.extend(native.breaks(1));
282 native.measured(|measured| measured.chord_turning(t0.min(t1), t0.max(t1), &breaks, tolerance))
283}
284
285fn offset_length(
289 offset: &StationOffset<'_>,
290 va: Scalar,
291 vb: Scalar,
292 tolerance: Scalar,
293) -> GeomResult<DistanceInterval> {
294 if vb <= va {
295 return Ok(DistanceInterval::point(0.0));
296 }
297 if let Some(model) = PlanModel::of(offset)? {
298 return model.length(va, vb, tolerance);
299 }
300 chord_offset_length(offset, va, vb, tolerance)
301}
302
303enum Native<'c> {
308 Two(Cow<'c, Curve2>),
309 Three(Cow<'c, Curve3>),
310}
311
312impl<'c> Native<'c> {
313 fn of(curve: StationCurve<'c>, m0: Scalar, m1: Scalar) -> GeomResult<Self> {
315 let mid = 0.5 * (m0 + m1);
316 match curve {
317 StationCurve::Two(Curve2::Polyline(polyline)) => {
318 let (origin, unit) = segment(&polyline.points, polyline.closed, mid, |p| {
319 Vec3::new(p.x, p.y, 0.0)
320 })?;
321 Ok(Self::Two(Cow::Owned(Curve2::Line(Line2 {
322 origin: Point2::new(origin.x, origin.y),
323 direction: Vec2::new(unit.x, unit.y),
324 }))))
325 }
326 StationCurve::Three(Curve3::Polyline(polyline)) => {
327 let (origin, unit) = segment(&polyline.points, polyline.closed, mid, |p| p)?;
328 Ok(Self::Three(Cow::Owned(Curve3::Line(Line3 {
329 origin,
330 direction: unit,
331 }))))
332 }
333 StationCurve::Two(
334 curve @ (Curve2::Line(_)
335 | Curve2::Circle(_)
336 | Curve2::Ellipse(_)
337 | Curve2::BSpline(_)),
338 ) => Ok(Self::Two(Cow::Borrowed(curve))),
339 StationCurve::Three(
340 curve @ (Curve3::Line(_)
341 | Curve3::Circle(_)
342 | Curve3::Ellipse(_)
343 | Curve3::BSpline(_)),
344 ) => Ok(Self::Three(Cow::Borrowed(curve))),
345 _ => Err(uncertified()),
346 }
347 }
348
349 fn point(&self, t: Scalar) -> GeomResult<Point3> {
350 match self {
351 Self::Two(curve) => {
352 let p = evaluate2(curve, t)?;
353 Ok(Point3::new(p.x, p.y, 0.0))
354 }
355 Self::Three(curve) => evaluate3(curve, t),
356 }
357 }
358
359 fn derivative(&self, t: Scalar) -> GeomResult<Vec3> {
360 match self {
361 Self::Two(curve) => {
362 let d = derivative2(curve, t)?;
363 Ok(Vec3::new(d.x, d.y, 0.0))
364 }
365 Self::Three(curve) => derivative3(curve, t),
366 }
367 }
368
369 fn second_derivative(&self, t: Scalar) -> GeomResult<Vec3> {
370 match self {
371 Self::Two(curve) => {
372 let d = second_derivative2(curve, t)?;
373 Ok(Vec3::new(d.x, d.y, 0.0))
374 }
375 Self::Three(curve) => second_derivative3(curve, t),
376 }
377 }
378
379 fn extent(&self) -> Scalar {
383 let flat = |v: Vec2| v.abs().max_element();
384 let out = match self {
385 Self::Two(curve) => match curve.as_ref() {
386 Curve2::Line(line) => flat(line.origin),
387 Curve2::Circle(c) => {
388 flat(c.frame.origin) + c.radius * (flat(c.frame.x) + flat(c.frame.y))
389 }
390 Curve2::Ellipse(e) => {
391 flat(e.frame.origin)
392 + e.semi_axis_x * flat(e.frame.x)
393 + e.semi_axis_y * flat(e.frame.y)
394 }
395 Curve2::BSpline(spline) => spline
396 .control_points
397 .iter()
398 .map(|p| flat(*p))
399 .fold(0.0, Scalar::max),
400 _ => Scalar::INFINITY,
401 },
402 Self::Three(curve) => match curve.as_ref() {
403 Curve3::Line(line) => line.origin.abs().max_element(),
404 Curve3::Circle(c) => {
405 c.frame.origin.abs().max_element()
406 + c.radius * (c.frame.x.abs().max_element() + c.frame.y.abs().max_element())
407 }
408 Curve3::Ellipse(e) => {
409 e.frame.origin.abs().max_element()
410 + e.semi_axis_x * e.frame.x.abs().max_element()
411 + e.semi_axis_y * e.frame.y.abs().max_element()
412 }
413 Curve3::BSpline(spline) => spline
414 .control_points
415 .iter()
416 .map(|p| p.abs().max_element())
417 .fold(0.0, Scalar::max),
418 _ => Scalar::INFINITY,
419 },
420 };
421 out.max(1.0)
422 }
423
424 fn bounds(&self, a: Scalar, b: Scalar) -> GeomResult<CurveDerivativeBounds> {
426 match self {
427 Self::Two(curve) => curve_derivative_bounds2(curve, a, b),
428 Self::Three(curve) => curve_derivative_bounds3(curve, a, b),
429 }
430 .filter(|d| d.first.is_finite() && d.second.is_finite() && d.third.is_finite())
431 .ok_or_else(uncertified)
432 }
433
434 fn fourth(&self, a: Scalar, b: Scalar) -> Option<Scalar> {
436 match self {
437 Self::Two(curve) => curve_fourth_bound2(curve, a, b),
438 Self::Three(curve) => curve_fourth_bound3(curve, a, b),
439 }
440 .filter(|d| d.is_finite())
441 }
442
443 fn measured<R>(&self, run: impl FnOnce(&Measured<'_>) -> GeomResult<R>) -> GeomResult<R> {
446 let point = |t: Scalar| Ok((self.point(t)?, 0.0));
447 let velocity = |t: Scalar| {
448 let d = self.derivative(t)?;
449 Ok((d, VELOCITY_ROUNDING * d.length()))
450 };
451 let bounds = |a: Scalar, b: Scalar| {
452 let d = self.bounds(a, b)?;
453 Ok((d.second, Some(d.third)))
454 };
455 run(&Measured {
456 point: &point,
457 velocity: &velocity,
458 bounds: &bounds,
459 scale: self.extent(),
460 })
461 }
462
463 fn breaks(&self, k: usize) -> Vec<Scalar> {
465 match self {
466 Self::Two(curve) => continuity_breaks2(curve, k),
467 Self::Three(curve) => continuity_breaks3(curve, k),
468 }
469 }
470
471 fn rate(&self) -> Option<Scalar> {
474 match self {
475 Self::Two(curve) => match curve.as_ref() {
476 Curve2::Line(line) => Some(line.direction.length()),
477 Curve2::Circle(circle) => Some(circle.radius),
478 _ => None,
479 },
480 Self::Three(curve) => match curve.as_ref() {
481 Curve3::Line(line) => Some(line.direction.length()),
482 Curve3::Circle(circle) => Some(circle.radius),
483 _ => None,
484 },
485 }
486 }
487
488 fn parameter(&self, m: Scalar) -> GeomResult<Scalar> {
495 if let Some(rate) = self.rate() {
496 return Ok(m / rate);
497 }
498 let (length, lo, hi, seams) = match self {
499 Self::Two(curve) => {
500 let domain = domain2(curve);
501 (
502 station_length2(curve)?,
503 domain.start.min(domain.end),
504 domain.start.max(domain.end),
505 super::station_seams2(curve)?,
506 )
507 }
508 Self::Three(curve) => {
509 let domain = domain3(curve);
510 (
511 station_length3(curve)?,
512 domain.start.min(domain.end),
513 domain.start.max(domain.end),
514 super::station_seams3(curve)?,
515 )
516 }
517 };
518 let length = length.ok_or_else(uncertified)?;
519 let closed = match self {
520 Self::Two(curve) => matches!(curve.as_ref(), Curve2::Ellipse(_)),
521 Self::Three(curve) => matches!(curve.as_ref(), Curve3::Ellipse(_)),
522 };
523 let (turn, m) = if closed && m > length + slack(length) {
524 (hi - lo, m - length)
525 } else {
526 (0.0, m)
527 };
528 let t = if m.abs() <= slack(0.0) {
529 lo
530 } else if (m - length).abs() <= slack(length) {
531 hi
532 } else if let Some(seam) = seams
533 .iter()
534 .find(|seam| (seam.distance - m).abs() <= slack(m))
535 {
536 seam.parameter
537 } else {
538 match self {
539 Self::Two(curve) => parameter_at_arc_length2(curve, lo, m)?,
540 Self::Three(curve) => parameter_at_arc_length3(curve, lo, m)?,
541 }
542 };
543 Ok(turn + t)
544 }
545}
546
547fn segment<P: Copy>(
550 points: &[P],
551 closed: bool,
552 mid: Scalar,
553 lift: impl Fn(P) -> Vec3,
554) -> GeomResult<(Point3, Vec3)> {
555 let count = points.len();
556 let spans = if closed {
557 count
558 } else {
559 count.saturating_sub(1)
560 };
561 let mut run = 0.0;
562 let mut found = None;
563 for i in 0..spans {
564 let (a, b) = (lift(points[i]), lift(points[(i + 1) % count]));
565 let length = (b - a).length();
566 if length > 0.0 {
567 found = Some((a, (b - a) / length, run));
568 if mid <= run + length {
569 break;
570 }
571 }
572 run += length;
573 }
574 let (start, unit, at) = found.ok_or_else(uncertified)?;
575 Ok((start - unit * at, unit))
576}
577
578fn sum<const N: usize>(f: Jet<N>, g: Jet<N>) -> Jet<N> {
581 core::array::from_fn(|k| f[k] + g[k])
582}
583
584fn composed(outer: Jet<4>, p: Jet<4>) -> Jet<4> {
588 [
589 outer[0],
590 outer[1] * p[1],
591 outer[2] * p[1] * p[1] + outer[1] * p[2],
592 outer[3] * p[1].powi(3) + 3.0 * outer[2] * p[1] * p[2] + outer[1] * p[3],
593 ]
594}
595
596fn linear(pair: (Scalar, Scalar), length: Scalar) -> Jet<4> {
599 [
600 pair.0.abs().max(pair.1.abs()),
601 (pair.1 - pair.0).abs() / length,
602 0.0,
603 0.0,
604 ]
605}
606
607#[derive(Debug, Clone, Copy)]
609enum Curvature<'a> {
610 Constant(Scalar),
612 Law {
615 law: &'a CurvatureLaw,
616 shift: Scalar,
617 sign: Scalar,
618 },
619}
620
621impl Curvature<'_> {
622 fn value(&self, s: Scalar) -> GeomResult<Scalar> {
623 match self {
624 Self::Constant(k) => Ok(*k),
625 Self::Law { law, shift, sign } => crate::frenet::law_value(law, s - shift)
626 .map(|k| sign * k)
627 .ok_or_else(uncertified),
628 }
629 }
630
631 fn jet(&self, lo: Scalar, hi: Scalar) -> GeomResult<Jet<3>> {
633 match self {
634 Self::Constant(k) => Ok([k.abs(), 0.0, 0.0]),
635 Self::Law { law, shift, .. } => {
636 let (lo, hi) = (lo - shift, hi - shift);
637 let first = law.derivative();
638 let second = first.derivative();
639 let bound = |law: &CurvatureLaw| {
640 crate::chain::curvature_bound(law, lo, hi)
641 .filter(|b| b.is_finite())
642 .ok_or_else(uncertified)
643 };
644 Ok([bound(law)?, bound(&first)?, bound(&second)?])
645 }
646 }
647 }
648}
649
650fn handedness(frame: &Frame2) -> GeomResult<Scalar> {
657 let skew = (frame.x.length() - 1.0)
658 .abs()
659 .max((frame.y.length() - 1.0).abs())
660 .max(frame.x.dot(frame.y).abs());
661 if skew.is_nan() || skew > 1e-12 {
662 return Err(uncertified());
663 }
664 Ok(frame.x.perp_dot(frame.y).signum())
665}
666
667fn plan_curvature(plan: &Curve2, lo: Scalar, hi: Scalar) -> GeomResult<Curvature<'_>> {
670 match plan {
671 Curve2::Line(_) | Curve2::Polyline(_) => Ok(Curvature::Constant(0.0)),
672 Curve2::Circle(circle) => {
673 let sign = handedness(&circle.frame)?;
674 if !(circle.radius.is_finite() && circle.radius > 0.0) {
675 return Err(uncertified());
676 }
677 Ok(Curvature::Constant(sign / circle.radius))
678 }
679 Curve2::Intrinsic(intrinsic) => Ok(Curvature::Law {
680 law: &intrinsic.curvature,
681 shift: 0.0,
682 sign: handedness(&intrinsic.start)?,
683 }),
684 Curve2::Chain(chain) => {
685 let sign = handedness(&chain.start)?;
686 let mid = 0.5 * (lo + hi);
687 match chain.piece_at(mid) {
688 Some((_, ChainPiece2::Intrinsic { curvature, .. }, local)) => Ok(Curvature::Law {
689 law: curvature,
690 shift: mid - local,
691 sign,
692 }),
693 _ => Err(uncertified()),
694 }
695 }
696 _ => Err(uncertified()),
697 }
698}
699
700fn level_curvature(intrinsic: &Intrinsic3) -> GeomResult<Curvature<'_>> {
714 let (x, y) = (intrinsic.start.x, intrinsic.start.y);
715 if !intrinsic.is_planar() || x.z != 0.0 || y.z != 0.0 {
716 return Err(uncertified());
717 }
718 let plan = Frame2 {
719 origin: Point2::new(intrinsic.start.origin.x, intrinsic.start.origin.y),
720 x: Vec2::new(x.x, x.y),
721 y: Vec2::new(y.x, y.y),
722 };
723 Ok(Curvature::Law {
724 law: &intrinsic.curvature,
725 shift: 0.0,
726 sign: handedness(&plan)?,
727 })
728}
729
730struct PlanModel<'a> {
733 curvature: Curvature<'a>,
734 grade: Option<&'a ElevationLaw>,
737 lateral: (Scalar, Scalar),
738 vertical: (Scalar, Scalar),
739 longitudinal: (Scalar, Scalar),
740 vertical_rate: bool,
743 start: Scalar,
744 end: Scalar,
745 reversed: bool,
746}
747
748impl<'a> PlanModel<'a> {
749 fn of(offset: &StationOffset<'a>) -> GeomResult<Option<Self>> {
753 let base = offset.base();
754 let (lo, hi) = (base.start, base.end);
755 let (curvature, elevation, vertical_rate) = match base.curve {
756 StationCurve::Two(plan @ (Curve2::Intrinsic(_) | Curve2::Chain(_))) => {
757 (plan_curvature(plan, lo, hi)?, None, true)
758 }
759 StationCurve::Three(Curve3::Elevated(elevated)) => (
760 plan_curvature(&elevated.plan, lo, hi)?,
761 Some(&elevated.elevation),
762 false,
763 ),
764 StationCurve::Three(Curve3::Intrinsic(intrinsic)) => {
765 (level_curvature(intrinsic)?, None, true)
766 }
767 StationCurve::Three(Curve3::Banked(_)) => return Err(uncertified()),
768 _ => return Ok(None),
769 };
770 if let Some(rigid) = base.placement {
771 if (rigid.transform_vector3(Vec3::Z) - Vec3::Z).length() > KEEPS_UP_TOLERANCE {
772 return Err(uncertified());
773 }
774 }
775 let zero = (0.0, 0.0);
776 let (lateral, vertical, longitudinal, leans) = match offset.law() {
777 OffsetLaw::Planar { distance } => ((distance, distance), zero, zero, false),
778 OffsetLaw::Directed {
779 distance,
780 reference_direction,
781 } => {
782 let v = reference_direction.normalize();
783 if v.x.hypot(v.y) > 1e-12 {
784 return Err(uncertified());
785 }
786 let a = distance * v.z.signum();
787 ((a, a), zero, zero, false)
788 }
789 OffsetLaw::Linear { start, end, frame } => {
790 let leans = match frame {
791 OffsetFrame::Section => true,
792 OffsetFrame::Plan => false,
793 _ => return Err(uncertified()),
794 };
795 (
796 (start.lateral, end.lateral),
797 (start.vertical, end.vertical),
798 (start.longitudinal, end.longitudinal),
799 leans,
800 )
801 }
802 _ => return Err(uncertified()),
803 };
804 let grade = elevation.filter(|_| leans && (vertical != zero || longitudinal != zero));
805 Ok(Some(Self {
806 curvature,
807 grade,
808 lateral,
809 vertical,
810 longitudinal,
811 vertical_rate,
812 start: base.start,
813 end: base.end,
814 reversed: base.reversed,
815 }))
816 }
817
818 fn length_of_base(&self) -> Scalar {
819 self.end - self.start
820 }
821
822 fn measure(&self, v: Scalar) -> Scalar {
824 if self.reversed {
825 self.end - v
826 } else {
827 self.start + v
828 }
829 }
830
831 fn travel(&self) -> Scalar {
832 if self.reversed {
833 -1.0
834 } else {
835 1.0
836 }
837 }
838
839 fn speed(&self, v: Scalar) -> GeomResult<(Scalar, Scalar)> {
841 let length = self.length_of_base();
842 let s = self.measure(v);
843 let u = (v / length).clamp(0.0, 1.0);
844 let at = |pair: (Scalar, Scalar)| pair.0 + (pair.1 - pair.0) * u;
845 let rate = |pair: (Scalar, Scalar)| (pair.1 - pair.0) / length;
846 let k = self.travel() * self.curvature.value(s)?;
847 let (a, da) = (at(self.lateral), rate(self.lateral));
848 let (long, dlong) = (at(self.longitudinal), rate(self.longitudinal));
849 let (vert, dvert) = (at(self.vertical), rate(self.vertical));
850 let (b, db) = match self.grade {
851 Some(law) => {
852 let p = self.travel() * crate::elevation::elevation_grade(law, s)?;
853 let q = crate::elevation::elevation_second(law, s)?;
854 let cos = 1.0 / p.hypot(1.0);
855 let sin = p * cos;
856 let turn = q / (1.0 + p * p);
857 (
858 long * cos - vert * sin,
859 dlong * cos - long * sin * turn - dvert * sin - vert * cos * turn,
860 )
861 }
862 None => (long, dlong),
863 };
864 let forward = 1.0 + db - a * k;
865 let across = da + b * k;
866 let rise = if self.vertical_rate { dvert } else { 0.0 };
867 let speed = (forward * forward + across * across + rise * rise).sqrt();
868 if !speed.is_finite() {
869 return Err(uncertified());
870 }
871 Ok((forward, speed))
872 }
873
874 fn bounds(&self, va: Scalar, vb: Scalar) -> GeomResult<(Scalar, Jet<3>, Jet<3>)> {
878 let length = self.length_of_base();
879 let (sa, sb) = (self.measure(va), self.measure(vb));
880 let (lo, hi) = (sa.min(sb), sa.max(sb));
881 let k = self.curvature.jet(lo, hi)?;
882 let k: Jet<4> = [k[0], k[1], k[2], 0.0];
883 let a = linear(self.lateral, length);
884 let long = linear(self.longitudinal, length);
885 let vert = linear(self.vertical, length);
886 let b = match self.grade {
887 Some(law) => {
888 let z = crate::elevation::profile_bounds4(law, lo, hi).ok_or_else(uncertified)?;
889 let p: Jet<4> = z;
890 sum(
891 product(long, composed(COS_OF_ATAN, p)),
892 product(vert, composed(SIN_OF_ATAN, p)),
893 )
894 }
895 None => long,
896 };
897 let ak = product(a, k);
898 let bk = product(b, k);
899 let forward = [1.0 + b[1] + ak[0], b[2] + ak[1], b[3] + ak[2]];
900 let across = [a[1] + bk[0], a[2] + bk[1], a[3] + bk[2]];
901 Ok((forward[1], forward, across))
902 }
903
904 fn panel(&self, a: Scalar, b: Scalar) -> GeomResult<(Scalar, Scalar, Scalar, Scalar)> {
908 let h = b - a;
909 let mid = 0.5 * (a + b);
910 let (forward_mid, speed_mid) = self.speed(mid)?;
911 let (rate, forward, across) = self.bounds(a, b)?;
912 let floor = forward_mid - 0.5 * h * rate;
913 let error = if floor > 0.0 {
914 let g1 = 2.0 * (forward[0] * forward[1] + across[0] * across[1]);
915 let g2 = 2.0
916 * (forward[1] * forward[1]
917 + forward[0] * forward[2]
918 + across[1] * across[1]
919 + across[0] * across[2]);
920 let gmin = floor * floor;
921 let bend = g2 / (2.0 * floor) + g1 * g1 / (4.0 * gmin * floor);
922 h * h * h / 24.0 * bend + VALUE_ROUNDING * h * speed_mid
923 } else {
924 Scalar::INFINITY
925 };
926 if !error.is_finite() {
927 return Ok((a, b, Scalar::NEG_INFINITY, Scalar::INFINITY));
928 }
929 Ok((a, b, h * speed_mid - error, h * speed_mid + error))
930 }
931
932 fn length(&self, va: Scalar, vb: Scalar, tolerance: Scalar) -> GeomResult<DistanceInterval> {
935 let refined = adapt(
936 vec![self.panel(va, vb)?],
937 PANEL_SHARE * tolerance,
938 |p| p.3 - p.2,
939 |&(a, b, ..)| {
940 let mid = 0.5 * (a + b);
941 if !(mid > a && mid < b) {
942 return Err(uncertified());
943 }
944 Ok((self.panel(a, mid)?, self.panel(mid, b)?))
945 },
946 )?;
947 let (mut lower, mut upper) = (Compensated::default(), Compensated::default());
948 for &(_, _, lo, hi) in &refined.panels {
949 lower.add(lo);
950 upper.add(hi);
951 }
952 finish(lower.value(), upper.value(), tolerance).map_err(|error| {
953 if refined.exhausted {
954 budget()
955 } else {
956 error
957 }
958 })
959 }
960}
961
962#[derive(Debug, Clone, Copy, PartialEq)]
967pub(crate) struct CertifiedSeam {
968 pub(crate) distance: Scalar,
970 pub(crate) interval: DistanceInterval,
972}
973
974pub(crate) fn atomic_seams(
978 curve: StationCurve<'_>,
979 seams: Vec<StationSeam>,
980 tolerance: Scalar,
981) -> GeomResult<Vec<CertifiedSeam>> {
982 seams
983 .into_iter()
984 .filter(|seam| !seam.smooth)
985 .map(|seam| {
986 let interval = if seam.exact {
987 DistanceInterval::point(seam.distance)
988 } else {
989 curve_span(curve, 0.0, seam.distance, tolerance)?
990 };
991 Ok(CertifiedSeam {
992 distance: seam.distance,
993 interval,
994 })
995 })
996 .collect()
997}
998
999#[cfg(test)]
1000mod tests {
1001 use super::*;
1002
1003 #[test]
1007 fn the_grade_suprema_bound_their_functions() {
1008 let derivatives = |p: Scalar| {
1009 let u = 1.0 + p * p;
1010 (
1011 [
1012 u.powf(-0.5),
1013 -p * u.powf(-1.5),
1014 (2.0 * p * p - 1.0) * u.powf(-2.5),
1015 p * (9.0 - 6.0 * p * p) * u.powf(-3.5),
1016 ],
1017 [
1018 p * u.powf(-0.5),
1019 u.powf(-1.5),
1020 -3.0 * p * u.powf(-2.5),
1021 (12.0 * p * p - 3.0) * u.powf(-3.5),
1022 ],
1023 )
1024 };
1025 for i in -200_000..=200_000 {
1026 let p = Scalar::from(i) * 1e-4;
1027 let (cos, sin) = derivatives(p);
1028 for k in 0..4 {
1029 assert!(cos[k].abs() <= COS_OF_ATAN[k], "cos^({k}) at {p}");
1030 assert!(sin[k].abs() <= SIN_OF_ATAN[k], "sin^({k}) at {p}");
1031 }
1032 }
1033 let h = 1e-5;
1034 for p in [-3.0, -0.4, 0.0, 0.3, 2.5] {
1035 let (at, _) = derivatives(p);
1036 let (up, _) = derivatives(p + h);
1037 let (down, _) = derivatives(p - h);
1038 for k in 0..3 {
1039 let difference = (up[k] - down[k]) / (2.0 * h);
1040 assert!(
1041 (difference - at[k + 1]).abs() < 1e-6,
1042 "cos^({}) at {p}",
1043 k + 1
1044 );
1045 }
1046 }
1047 }
1048}