geop_core_geometry/nurb_curve/
extend.rs1use geop_core_math::{
5 geop_error::{GeopError, GeopResult},
6 scalars::Scalar,
7 vector::Vector,
8};
9
10use super::NurbCurve;
11use crate::knot_insertion::{insert, is_clamped_at};
12
13fn continued<const D: usize>(points: &[Vector<D>], s: Scalar) -> Vec<Vector<D>> {
17 let mut level = points.to_vec();
18 let mut out = vec![level[0]];
19 for r in 1..points.len() {
20 for i in 0..points.len() - r {
21 level[i] = Vector::interpolate(&level[i], &level[i + 1], s);
22 }
23 out.push(level[0]);
24 }
25 out
26}
27
28fn beyond<const D: usize>(points: &[Vector<D>], s: Scalar) -> GeopResult<Vec<Vector<D>>> {
34 let mut level = continued(points, s);
35 let t = Scalar::ONE.div(s)?;
36 let n = level.len();
37 let mut out = vec![level[n - 1]];
38 for r in 1..n {
39 for i in 0..n - r {
40 level[i] = Vector::interpolate(&level[i], &level[i + 1], t);
41 }
42 out.push(level[n - r - 1]);
43 }
44 out.reverse();
45 Ok(out)
46}
47
48impl<const D: usize> NurbCurve<D> {
49 pub fn extended(&self, at_end: bool, by: Scalar) -> GeopResult<Self> {
66 let p = self.degree;
67 if !is_clamped_at(&self.knot_vector, p, !at_end) {
68 return Err(GeopError::new(format!(
69 "NurbCurve::extended: the knot vector {:?} is not clamped at the end to extend",
70 self.knot_vector
71 )));
72 }
73 if !by.definitely_greater(Scalar::ZERO) {
74 return Err(GeopError::new(format!(
75 "NurbCurve::extended: extending by {by:?}, which is not greater than zero"
76 )));
77 }
78 if !at_end {
79 let (t0, t1) = self.domain();
83 let reversed = self.reverse().extended(true, by)?;
84 let mut out = reversed.reverse();
85 for k in &mut out.knot_vector {
88 *k = k.sub(by);
89 }
90 debug_assert!(
91 out.domain().1.could_be_equal(t1) && out.domain().0.could_be_equal(t0.sub(by))
92 );
93 return Ok(out);
94 }
95 let n = self.control_points.len();
96 let end = self.knot_vector[n];
97 let start = self.knot_vector[..n]
99 .iter()
100 .rev()
101 .find(|k| k.definitely_less(end))
102 .copied()
103 .ok_or_else(|| GeopError::new("NurbCurve::extended: the curve has no span"))?;
104 let mut knots = self.knot_vector.clone();
105 let mut rows = [self.control_points.clone()];
106 let multiplicity = knots.iter().filter(|k| k.could_be_equal(start)).count();
107 for _ in multiplicity.min(p)..p {
108 insert(&mut knots, &mut rows, p, start);
109 }
110 let [isolated] = rows;
111 let m = isolated.len();
112 let s = end.add(by).sub(start).div(end.sub(start))?;
113 let tail = beyond(&isolated[m - p - 1..], s)?;
114 let mut points = self.control_points.clone();
117 let last = points.last_mut().expect("a curve has control points");
118 if !last.could_be_equal(&tail[0]) {
119 return Err(GeopError::new(format!(
120 "NurbCurve::extended: the continuation starts at {:?}, not at the end {last:?}",
121 tail[0]
122 )));
123 }
124 *last = Vector::from_array(std::array::from_fn(|k| last[k].intersect(tail[0][k])));
125 points.extend_from_slice(&tail[1..]);
126 let mut knots = self.knot_vector[..self.knot_vector.len() - 1].to_vec();
127 knots.extend(std::iter::repeat_n(end.add(by), p + 1));
128 NurbCurve::try_new(p, points, knots)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use crate::test_fixtures::weighted;
135 use geop_core_math::scalars::Scalar;
136
137 use crate::nurb_curve::{NurbCurve, NurbCurve3D};
138
139 #[test]
142 fn cubic_continues_as_itself() {
143 let f = Scalar::from_f64;
144 let curve: NurbCurve3D = NurbCurve::try_new(
145 3,
146 vec![
147 weighted(0., 0., 0., 1.),
148 weighted(1., 2., 0., 1.),
149 weighted(2., -1., 1., 1.),
150 weighted(3., 1., 0., 1.),
151 weighted(4., 0., 2., 1.),
152 ],
153 [0., 0., 0., 0., 0.5, 1., 1., 1., 1.].map(f).to_vec(),
154 )
155 .unwrap();
156 let longer = curve.extended(true, f(0.25)).unwrap();
157 let (t0, t1) = longer.domain();
158 assert!(t0.could_be_equal(f(0.)) && t1.could_be_equal(f(1.25)));
159 for i in 0..=8 {
160 let t = Scalar::from_ratio(i, 8).unwrap();
161 assert!(
162 longer
163 .evaluate(t)
164 .unwrap()
165 .could_be_equal(&curve.evaluate(t).unwrap())
166 );
167 }
168 let earlier = curve.extended(false, f(0.5)).unwrap();
171 let (t0, t1) = earlier.domain();
172 assert!(t0.could_be_equal(f(-0.5)) && t1.could_be_equal(f(1.)));
173 for i in 0..=8 {
174 let t = Scalar::from_ratio(i, 8).unwrap();
175 assert!(
176 earlier
177 .evaluate(t)
178 .unwrap()
179 .could_be_equal(&curve.evaluate(t).unwrap())
180 );
181 }
182 }
183
184 #[test]
186 fn arc_continues_round_its_circle() {
187 let f = Scalar::from_f64;
188 let arc: NurbCurve3D = NurbCurve::try_new(
189 2,
190 vec![
191 weighted(1., 0., 0., 1.),
192 weighted(1., 1., 0., std::f64::consts::SQRT_2 / 2.0),
193 weighted(0., 1., 0., 1.),
194 ],
195 [0., 0., 0., 1., 1., 1.].map(f).to_vec(),
196 )
197 .unwrap();
198 let longer = arc.extended(true, f(0.3)).unwrap();
199 for i in 0..=13 {
200 let t = Scalar::from_ratio(i, 10).unwrap();
201 let p = longer.evaluate(t).unwrap();
202 assert!(p.norm_sq().could_be_equal(Scalar::ONE), "{t:?}: {p:?}");
203 }
204 }
205}