geop_core_geometry/nurb_curve/
length.rs1use geop_core_math::{
5 geop_error::{GeopError, GeopResult},
6 quadrature::{Quadrature, integrate},
7 scalars::Scalar,
8};
9
10use super::NurbCurve;
11
12impl<const D: usize> NurbCurve<D> {
13 fn integrated(
19 &self,
20 mut speed: impl FnMut(Scalar) -> GeopResult<Scalar>,
21 ) -> GeopResult<(Scalar, bool)> {
22 let integral = integrate(
23 |t| Ok(vec![speed(t)?]),
24 &self.breakpoints(),
25 1,
26 &Quadrature::default(),
27 )?;
28 Ok((integral.value[0], integral.converged))
29 }
30}
31
32pub trait Length: Sized {
34 fn length(&self) -> GeopResult<(Scalar, bool)>;
37}
38
39impl Length for NurbCurve<4> {
40 fn length(&self) -> GeopResult<(Scalar, bool)> {
41 self.integrated(|t| Ok(self.tangent(t)?.norm()))
42 }
43}
44
45impl Length for NurbCurve<3> {
46 fn length(&self) -> GeopResult<(Scalar, bool)> {
47 self.integrated(|t| Ok(self.tangent(t)?.norm()))
48 }
49}
50
51impl<const D: usize> NurbCurve<D>
52where
53 Self: Length,
54{
55 pub fn length(&self) -> GeopResult<(Scalar, bool)> {
58 Length::length(self)
59 }
60
61 pub fn parameter_at_length_fraction(&self, fraction: f64) -> GeopResult<Scalar> {
67 if !(0.0 < fraction && fraction < 1.0) {
68 return Err(GeopError::new(format!(
69 "parameter_at_length_fraction: {fraction} is not inside (0, 1)"
70 )));
71 }
72 let (t0, t1) = self.domain();
73 let target = fraction * self.length()?.0.to_f64();
74 let (mut lo, mut hi) = (t0.to_f64(), t1.to_f64());
75 for _ in 0..64 {
77 let mid = 0.5 * (lo + hi);
78 if mid <= lo || mid >= hi {
79 break;
80 }
81 let run = self
82 .sub_curve(t0, Scalar::from_f64(mid))?
83 .length()?
84 .0
85 .to_f64();
86 if run < target {
87 lo = mid;
88 } else {
89 hi = mid;
90 }
91 }
92 Ok(Scalar::from_f64(0.5 * (lo + hi)))
93 }
94}