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<S: Scalar, const D: usize> NurbCurve<S, D> {
7 pub fn split(&self, t: S) -> GeopResult<(NurbCurve<S, D>, NurbCurve<S, D>)> {
34 let p = self.degree;
35 let mut knots = self.knot_vector.clone();
36 let mut rows = [self.control_points.clone()];
37 let (right_knots, right_rows) = knot_insertion::split(&mut knots, &mut rows, p, t)?;
38 let [pts] = rows;
39 let right_pts = right_rows.into_iter().next().unwrap_or_default();
40 Ok((
41 NurbCurve {
42 aabb: compute_aabb(&pts),
43 degree: p,
44 control_points: pts,
45 knot_vector: knots,
46 },
47 NurbCurve {
48 aabb: compute_aabb(&right_pts),
49 degree: p,
50 control_points: right_pts,
51 knot_vector: right_knots,
52 },
53 ))
54 }
55
56 pub fn sub_curve(&self, t0: S, t1: S) -> GeopResult<NurbCurve<S, D>> {
65 let p = self.degree;
66 let mut knots = self.knot_vector.clone();
67 let mut rows = [self.control_points.clone()];
68 knot_insertion::restrict(&mut knots, &mut rows, p, t0, t1)?;
69 let [pts] = rows;
70 Ok(NurbCurve {
71 aabb: compute_aabb(&pts),
72 degree: p,
73 control_points: pts,
74 knot_vector: knots,
75 })
76 }
77
78 pub fn split_mid(&self) -> GeopResult<(NurbCurve<S, D>, NurbCurve<S, D>)> {
80 let (t0, t1) = self.domain();
81 let mid = t0.add(t1).div(S::TWO)?.sharpen();
85 self.split(mid)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::nurb_curve::NurbCurve;
92 use geop_core_math::for_all_scalars;
93 use geop_core_math::{scalars::Scalar, vector::Vector4};
94
95 fn pt<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
96 Vector4::from_array([
97 S::from_f64(x),
98 S::from_f64(y),
99 S::from_f64(z),
100 S::from_f64(w),
101 ])
102 }
103
104 fn line<S: Scalar>() -> NurbCurve<S, 4> {
105 NurbCurve::try_new(
106 1,
107 vec![pt(0.0, 0.0, 0.0, 1.0), pt(1.0, 0.0, 0.0, 1.0)],
108 vec![
109 S::from_f64(0.0),
110 S::from_f64(0.0),
111 S::from_f64(1.0),
112 S::from_f64(1.0),
113 ],
114 )
115 .unwrap()
116 }
117
118 fn check_split_line_halves_domain<S: Scalar>() {
119 let (left, right) = line::<S>().split(S::from_f64(0.5)).unwrap();
120
121 let l0 = left.evaluate(S::ZERO).unwrap();
122 assert!(l0[0].could_be_equal(S::ZERO));
123
124 let l1 = left.evaluate(S::from_f64(0.5)).unwrap();
125 let r0 = right.evaluate(S::from_f64(0.5)).unwrap();
126 assert!(l1[0].could_be_equal(r0[0]));
127 assert!(l1[0].could_be_equal(S::from_f64(0.5)));
128
129 let r1 = right.evaluate(S::ONE).unwrap();
130 assert!(r1[0].could_be_equal(S::ONE));
131 }
132 #[test]
133 fn split_line_halves_domain() {
134 for_all_scalars!(check_split_line_halves_domain);
135 }
136
137 fn check_split_preserves_points_on_curve<S: Scalar>() {
138 let curve = line::<S>();
139 let (left, right) = curve.split(S::from_f64(0.25)).unwrap();
140
141 let orig = curve.evaluate(S::from_f64(0.1)).unwrap();
142 let from_left = left.evaluate(S::from_f64(0.1)).unwrap();
143 assert!(orig[0].could_be_equal(from_left[0]));
144
145 let orig2 = curve.evaluate(S::from_f64(0.75)).unwrap();
146 let from_right = right.evaluate(S::from_f64(0.75)).unwrap();
147 assert!(orig2[0].could_be_equal(from_right[0]));
148 }
149 #[test]
150 fn split_preserves_points_on_curve() {
151 for_all_scalars!(check_split_preserves_points_on_curve);
152 }
153
154 fn check_split_at_boundary_returns_err<S: Scalar>() {
155 let c = line::<S>();
156 assert!(c.split(S::ZERO).is_err());
157 assert!(c.split(S::ONE).is_err());
158 }
159 #[test]
160 fn split_at_boundary_returns_err() {
161 for_all_scalars!(check_split_at_boundary_returns_err);
162 }
163
164 fn check_split_cubic_at_existing_knot<S: Scalar>() {
165 let f = S::from_f64;
166 let curve = NurbCurve::try_new(
167 3,
168 vec![
169 pt(0.0, 0.0, 0.0, 1.0),
170 pt(0.25, 1.0, 0.0, 1.0),
171 pt(0.5, 0.0, 0.0, 1.0),
172 pt(0.75, 1.0, 0.0, 1.0),
173 pt(1.0, 0.0, 0.0, 1.0),
174 ],
175 vec![
176 f(0.0),
177 f(0.0),
178 f(0.0),
179 f(0.0),
180 f(0.5),
181 f(1.0),
182 f(1.0),
183 f(1.0),
184 f(1.0),
185 ],
186 )
187 .unwrap();
188
189 let t_split = f(0.5);
190 let (left, right) = curve.split(t_split).unwrap();
191
192 let orig_at_split = curve.evaluate(t_split).unwrap();
193 let left_at_split = left.evaluate(t_split).unwrap();
194 let right_at_split = right.evaluate(t_split).unwrap();
195
196 for c in 0..3 {
197 assert!(
198 orig_at_split[c].could_be_equal(left_at_split[c]),
199 "left junction mismatch at coord {c}"
200 );
201 assert!(
202 orig_at_split[c].could_be_equal(right_at_split[c]),
203 "right junction mismatch at coord {c}"
204 );
205 }
206 }
207 #[test]
208 fn split_cubic_at_existing_knot() {
209 for_all_scalars!(check_split_cubic_at_existing_knot);
210 }
211}