geop_core_geometry/nurb_curve/
split.rs1use geop_core_math::{geop_error::GeopResult, scalars::Scalar};
2
3use super::NurbCurve;
4use crate::{aabb::compute_aabb, knot_insertion};
5
6impl<const D: usize> NurbCurve<D> {
7 pub fn split(&self, t: Scalar) -> GeopResult<(NurbCurve<D>, NurbCurve<D>)> {
27 let p = self.degree;
28 let mut knots = self.knot_vector.clone();
29 let mut rows = [self.control_points.clone()];
30 let (right_knots, right_rows) = knot_insertion::split(&mut knots, &mut rows, p, t)?;
31 let [pts] = rows;
32 let right_pts = right_rows.into_iter().next().unwrap_or_default();
33 Ok((
34 NurbCurve {
35 aabb: compute_aabb(&pts),
36 degree: p,
37 control_points: pts,
38 knot_vector: knots,
39 },
40 NurbCurve {
41 aabb: compute_aabb(&right_pts),
42 degree: p,
43 control_points: right_pts,
44 knot_vector: right_knots,
45 },
46 ))
47 }
48
49 pub fn sub_curve(&self, t0: Scalar, t1: Scalar) -> GeopResult<NurbCurve<D>> {
58 let p = self.degree;
59 let mut knots = self.knot_vector.clone();
60 let mut rows = [self.control_points.clone()];
61 knot_insertion::restrict(&mut knots, &mut rows, p, t0, t1)?;
62 let [pts] = rows;
63 Ok(NurbCurve {
64 aabb: compute_aabb(&pts),
65 degree: p,
66 control_points: pts,
67 knot_vector: knots,
68 })
69 }
70
71 pub fn bezier_pieces(&self) -> GeopResult<Vec<Self>> {
74 let end = self.domain().1;
75 let interior = &self.knot_vector[self.degree + 1..self.control_points.len()];
76 let mut rest = self.clone();
77 let mut pieces = Vec::new();
78 for &k in interior {
79 if k.definitely_greater(rest.domain().0) && k.definitely_less(end) {
80 let (left, right) = rest.split(k)?;
81 pieces.push(left);
82 rest = right;
83 }
84 }
85 pieces.push(rest);
86 Ok(pieces)
87 }
88
89 pub fn split_mid(&self) -> GeopResult<(NurbCurve<D>, NurbCurve<D>)> {
91 self.split(self.mid_parameter()?)
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use crate::nurb_curve::NurbCurve;
100 use crate::test_fixtures::{hom, line};
101 use geop_core_math::scalars::Scalar;
102
103 #[test]
104 fn split_line_halves_domain() {
105 let (left, right) = line([0.; 3], [1., 0., 0.])
106 .split(Scalar::from_f64(0.5))
107 .unwrap();
108
109 let l0 = left.evaluate(Scalar::ZERO).unwrap();
110 assert!(l0[0].could_be_equal(Scalar::ZERO));
111
112 let l1 = left.evaluate(Scalar::from_f64(0.5)).unwrap();
113 let r0 = right.evaluate(Scalar::from_f64(0.5)).unwrap();
114 assert!(l1[0].could_be_equal(r0[0]));
115 assert!(l1[0].could_be_equal(Scalar::from_f64(0.5)));
116
117 let r1 = right.evaluate(Scalar::ONE).unwrap();
118 assert!(r1[0].could_be_equal(Scalar::ONE));
119 }
120
121 #[test]
122 fn split_preserves_points_on_curve() {
123 let curve = line([0.; 3], [1., 0., 0.]);
124 let (left, right) = curve.split(Scalar::from_f64(0.25)).unwrap();
125
126 let orig = curve.evaluate(Scalar::from_f64(0.1)).unwrap();
127 let from_left = left.evaluate(Scalar::from_f64(0.1)).unwrap();
128 assert!(orig[0].could_be_equal(from_left[0]));
129
130 let orig2 = curve.evaluate(Scalar::from_f64(0.75)).unwrap();
131 let from_right = right.evaluate(Scalar::from_f64(0.75)).unwrap();
132 assert!(orig2[0].could_be_equal(from_right[0]));
133 }
134
135 #[test]
136 fn split_at_boundary_returns_err() {
137 let c = line([0.; 3], [1., 0., 0.]);
138 assert!(c.split(Scalar::ZERO).is_err());
139 assert!(c.split(Scalar::ONE).is_err());
140 }
141
142 #[test]
150 fn a_rim_cut_many_times_stays_as_narrow_as_cut_once() {
151 use crate::contains::curve::curve_could_contain;
152 use geop_core_math::vector::Vector3;
153 let f = Scalar::from_f64;
154 let v = |x: f64, y: f64, z: f64| Vector3::from_array([f(x), f(y), f(z)]);
155 let mut piece = NurbCurve::<4>::arc(
156 v(0.0, -4.0, 1.0),
157 v(4.0, -4.0, 1.0),
158 v(4.0, 0.0, 1.0),
159 crate::nurb_curve::sqrt2_over_2(),
160 )
161 .unwrap();
162 for k in 1..=40 {
163 let angle = -std::f64::consts::FRAC_PI_2 * (1.0 - k as f64 / 41.0);
164 let crossing = v(4.0 * angle.cos(), 4.0 * angle.sin(), 1.0);
165 let t = curve_could_contain(&piece, &crossing, 10_000, f(1e-4))
166 .unwrap()
167 .unwrap();
168 let t = piece.refine_parameter_at_point(t, &crossing);
169 let vertex = piece.evaluate(t).unwrap();
170 let t = piece.split_parameter_at_point(t, &vertex, &vertex).unwrap();
171 piece = piece.split(t).unwrap().1;
172 let widest = piece
173 .control_points
174 .iter()
175 .flat_map(|p| (0..4).map(move |c| p[c].width().to_f64()))
176 .fold(0.0, f64::max);
177 assert!(widest < 1e-13, "cut {k}: {widest:e}");
178 let z = piece.evaluate(piece.mid_parameter().unwrap()).unwrap()[2];
179 assert!(
180 z.could_be_equal(Scalar::ONE) && z.width().to_f64() < 1e-13,
181 "cut {k}: {z:?}"
182 );
183 }
184 }
185
186 #[test]
193 fn cut_at_a_knot_it_could_be_repeats_that_knot() {
194 let f = Scalar::from_f64;
195 let joint = f(0.5 - 1e-15).union(f(0.5 + 1e-15));
196 let curve = NurbCurve::try_new(
197 2,
198 vec![
199 hom(0.0, 0.0, 0.0, 1.0),
200 hom(1.0, 1.0, 0.0, 1.0),
201 hom(2.0, 0.0, 0.0, 1.0),
202 hom(3.0, 1.0, 0.0, 1.0),
203 hom(4.0, 0.0, 0.0, 1.0),
204 ],
205 vec![f(0.0), f(0.0), f(0.0), joint, joint, f(1.0), f(1.0), f(1.0)],
206 )
207 .unwrap();
208 let back = curve.sub_curve(f(0.5 + 2e-16), f(1.0)).unwrap();
209 assert_eq!(back.knot_vector[..3], [joint; 3]);
210 assert_eq!(back.control_points[0], curve.control_points[2]);
211 }
212
213 #[test]
214 fn split_cubic_at_existing_knot() {
215 let f = Scalar::from_f64;
216 let curve = NurbCurve::try_new(
217 3,
218 vec![
219 hom(0.0, 0.0, 0.0, 1.0),
220 hom(0.25, 1.0, 0.0, 1.0),
221 hom(0.5, 0.0, 0.0, 1.0),
222 hom(0.75, 1.0, 0.0, 1.0),
223 hom(1.0, 0.0, 0.0, 1.0),
224 ],
225 vec![
226 f(0.0),
227 f(0.0),
228 f(0.0),
229 f(0.0),
230 f(0.5),
231 f(1.0),
232 f(1.0),
233 f(1.0),
234 f(1.0),
235 ],
236 )
237 .unwrap();
238
239 let t_split = f(0.5);
240 let (left, right) = curve.split(t_split).unwrap();
241
242 let orig_at_split = curve.evaluate(t_split).unwrap();
243 let left_at_split = left.evaluate(t_split).unwrap();
244 let right_at_split = right.evaluate(t_split).unwrap();
245
246 for c in 0..3 {
247 assert!(
248 orig_at_split[c].could_be_equal(left_at_split[c]),
249 "left junction mismatch at coord {c}"
250 );
251 assert!(
252 orig_at_split[c].could_be_equal(right_at_split[c]),
253 "right junction mismatch at coord {c}"
254 );
255 }
256 }
257}