1use axiolid_contracts::{GeomError, GeomResult};
4use axiolid_core::{Point2, Point3, Scalar};
5use axiolid_curve::{BSplineCurve, BSplineCurve2, BSplineCurve3};
6use axiolid_evaluate::curve::{bspline_jet2, bspline_jet3};
7
8pub fn reverse2(curve: &BSplineCurve2) -> GeomResult<BSplineCurve2> {
10 bspline_jet2(curve, 0.0)?;
11 reverse(curve)
12}
13
14pub fn reverse3(curve: &BSplineCurve3) -> GeomResult<BSplineCurve3> {
16 bspline_jet3(curve, 0.0)?;
17 reverse(curve)
18}
19
20pub fn insert_knot2(curve: &BSplineCurve2, parameter: Scalar) -> GeomResult<BSplineCurve2> {
25 bspline_jet2(curve, parameter)?;
26 insert(
27 curve,
28 parameter,
29 |p| [p.x, p.y],
30 |p| Point2::new(p[0], p[1]),
31 )
32}
33
34pub fn insert_knot3(curve: &BSplineCurve3, parameter: Scalar) -> GeomResult<BSplineCurve3> {
39 bspline_jet3(curve, parameter)?;
40 insert(
41 curve,
42 parameter,
43 |p| [p.x, p.y, p.z],
44 |p| Point3::new(p[0], p[1], p[2]),
45 )
46}
47
48pub fn split2(
52 curve: &BSplineCurve2,
53 parameter: Scalar,
54) -> GeomResult<(BSplineCurve2, BSplineCurve2)> {
55 bspline_jet2(curve, parameter)?;
56 let mut refined = curve.clone();
57 check_interior(&refined, parameter)?;
58 while multiplicity(&refined, parameter) < usize::from(refined.degree) {
59 refined = insert_knot2(&refined, parameter)?;
60 }
61 split_ready(&refined, parameter)
62}
63
64pub fn split3(
68 curve: &BSplineCurve3,
69 parameter: Scalar,
70) -> GeomResult<(BSplineCurve3, BSplineCurve3)> {
71 bspline_jet3(curve, parameter)?;
72 let mut refined = curve.clone();
73 check_interior(&refined, parameter)?;
74 while multiplicity(&refined, parameter) < usize::from(refined.degree) {
75 refined = insert_knot3(&refined, parameter)?;
76 }
77 split_ready(&refined, parameter)
78}
79
80pub fn bezier_segments2(curve: &BSplineCurve2) -> GeomResult<Vec<BSplineCurve2>> {
82 bspline_jet2(curve, 0.0)?;
83 decompose(curve, split2)
84}
85
86pub fn bezier_segments3(curve: &BSplineCurve3) -> GeomResult<Vec<BSplineCurve3>> {
88 bspline_jet3(curve, 0.0)?;
89 decompose(curve, split3)
90}
91
92fn reverse<P: Clone>(curve: &BSplineCurve<P>) -> GeomResult<BSplineCurve<P>> {
93 let (knots, multiplicities) = crate::axis::reverse_axis(&curve.knots, &curve.multiplicities)?;
94 let mut control_points = curve.control_points.clone();
95 control_points.reverse();
96 let weights = curve.weights.as_ref().map(|weights| {
97 let mut reversed = weights.clone();
98 reversed.reverse();
99 reversed
100 });
101 Ok(BSplineCurve {
102 degree: curve.degree,
103 control_points,
104 knots,
105 multiplicities,
106 weights,
107 knot_spec: curve.knot_spec,
108 closed: curve.closed,
109 self_intersect: curve.self_intersect,
110 })
111}
112
113fn check_interior<P>(curve: &BSplineCurve<P>, parameter: Scalar) -> GeomResult<()> {
114 if !parameter.is_finite() {
115 return Err(GeomError::InvalidInput(
116 "split parameter must be finite".to_owned(),
117 ));
118 }
119 let expanded = expand(curve);
120 let lo = expanded[usize::from(curve.degree)];
121 let hi = expanded[curve.control_points.len()];
122 if parameter <= lo || parameter >= hi {
123 return Err(GeomError::InvalidInput(
124 "split parameter must lie strictly inside the active domain".to_owned(),
125 ));
126 }
127 Ok(())
128}
129
130fn expand<P>(curve: &BSplineCurve<P>) -> Vec<Scalar> {
131 let mut expanded =
132 Vec::with_capacity(curve.control_points.len() + usize::from(curve.degree) + 1);
133 for (&knot, &multiplicity) in curve.knots.iter().zip(&curve.multiplicities) {
134 expanded.extend(core::iter::repeat_n(knot, multiplicity as usize));
135 }
136 expanded
137}
138
139fn multiplicity<P>(curve: &BSplineCurve<P>, parameter: Scalar) -> usize {
140 curve
141 .knots
142 .iter()
143 .position(|&k| k == parameter)
144 .map_or(0, |i| curve.multiplicities[i] as usize)
145}
146fn split_ready<P: Clone>(
147 curve: &BSplineCurve<P>,
148 parameter: Scalar,
149) -> GeomResult<(BSplineCurve<P>, BSplineCurve<P>)> {
150 let expanded = expand(curve);
151 let p = usize::from(curve.degree);
152 let n = curve.control_points.len() - 1;
153 let k = find_span(&expanded, n, p, parameter);
154 let shared = k
155 .checked_sub(p)
156 .ok_or_else(|| GeomError::InvalidInput("split control index underflows".to_owned()))?;
157 if shared == 0 || shared >= curve.control_points.len() - 1 {
158 return Err(GeomError::InvalidInput(
159 "split would create an empty segment".to_owned(),
160 ));
161 }
162 let ki = curve
163 .knots
164 .iter()
165 .position(|&value| value == parameter)
166 .ok_or_else(|| {
167 GeomError::InvalidInput("split knot is absent after refinement".to_owned())
168 })?;
169 let mut lm = curve.multiplicities[..=ki].to_vec();
170 let mut rm = curve.multiplicities[ki..].to_vec();
171 lm[ki] = u32::from(curve.degree) + 1;
172 rm[0] = u32::from(curve.degree) + 1;
173 let si = if curve.self_intersect == Some(false) {
174 Some(false)
175 } else {
176 None
177 };
178 let make = |control_points: Vec<P>,
179 knots: Vec<Scalar>,
180 multiplicities: Vec<u32>,
181 weights: Option<Vec<Scalar>>| BSplineCurve {
182 degree: curve.degree,
183 control_points,
184 knots,
185 multiplicities,
186 weights,
187 knot_spec: curve.knot_spec,
188 closed: false,
189 self_intersect: si,
190 };
191 let lw = curve.weights.as_ref().map(|w| w[..=shared].to_vec());
192 let rw = curve.weights.as_ref().map(|w| w[shared..].to_vec());
193 Ok((
194 make(
195 curve.control_points[..=shared].to_vec(),
196 curve.knots[..=ki].to_vec(),
197 lm,
198 lw,
199 ),
200 make(
201 curve.control_points[shared..].to_vec(),
202 curve.knots[ki..].to_vec(),
203 rm,
204 rw,
205 ),
206 ))
207}
208type SplitFn<P> = fn(&BSplineCurve<P>, Scalar) -> GeomResult<(BSplineCurve<P>, BSplineCurve<P>)>;
209
210fn decompose<P: Clone>(
211 curve: &BSplineCurve<P>,
212 split: SplitFn<P>,
213) -> GeomResult<Vec<BSplineCurve<P>>> {
214 let expanded = expand(curve);
215 let lo = expanded[usize::from(curve.degree)];
216 let hi = expanded[curve.control_points.len()];
217 let internal: Vec<_> = curve
218 .knots
219 .iter()
220 .copied()
221 .filter(|&k| k > lo && k < hi)
222 .collect();
223 let mut result = Vec::with_capacity(internal.len() + 1);
224 let mut remainder = curve.clone();
225 for parameter in internal {
226 let (left, right) = split(&remainder, parameter)?;
227 result.push(left);
228 remainder = right;
229 }
230 result.push(remainder);
231 Ok(result)
232}
233
234pub(crate) fn insert<const N: usize, P: Clone>(
235 curve: &BSplineCurve<P>,
236 parameter: Scalar,
237 coordinates: impl Fn(&P) -> [Scalar; N],
238 point: impl Fn([Scalar; N]) -> P,
239) -> GeomResult<BSplineCurve<P>> {
240 if !parameter.is_finite() {
241 return Err(GeomError::InvalidInput(
242 "inserted knot must be finite".to_owned(),
243 ));
244 }
245 let expanded = expand_knots(curve);
246 let p = usize::from(curve.degree);
247 let n = curve.control_points.len() - 1;
248 let lo = expanded[p];
249 let hi = expanded[n + 1];
250 if parameter <= lo || parameter >= hi {
251 return Err(GeomError::InvalidInput(format!(
252 "inserted knot {parameter} must be strictly inside ({lo}, {hi})"
253 )));
254 }
255 let k = find_span(&expanded, n, p, parameter);
256 let s = expanded.iter().filter(|&&knot| knot == parameter).count();
257 if s >= p {
258 return Err(GeomError::InvalidInput(format!(
259 "knot multiplicity {s} already reaches degree {p}"
260 )));
261 }
262 let weights = curve.weights.clone().unwrap_or_else(|| vec![1.0; n + 1]);
263 let homogeneous: Vec<_> = curve
264 .control_points
265 .iter()
266 .zip(&weights)
267 .map(|(control, &weight)| {
268 let mut h = coordinates(control);
269 for value in &mut h {
270 *value *= weight;
271 }
272 (h, weight)
273 })
274 .collect();
275 let mut output = vec![([0.0; N], 0.0); n + 2];
276 output[..=k - p].clone_from_slice(&homogeneous[..=k - p]);
277 output[k - s + 1..n + 2].copy_from_slice(&homogeneous[k - s..n + 1]);
278 for i in k - p + 1..=k - s {
279 let denominator = expanded[i + p] - expanded[i];
280 if denominator == 0.0 {
281 return Err(GeomError::Degenerate(
282 "knot insertion denominator is zero".to_owned(),
283 ));
284 }
285 let alpha = (parameter - expanded[i]) / denominator;
286 let mut h = [0.0; N];
287 for (d, value) in h.iter_mut().enumerate() {
288 *value = alpha * homogeneous[i].0[d] + (1.0 - alpha) * homogeneous[i - 1].0[d];
289 }
290 output[i] = (
291 h,
292 alpha * homogeneous[i].1 + (1.0 - alpha) * homogeneous[i - 1].1,
293 );
294 }
295 let mut new_expanded = expanded;
296 new_expanded.insert(k + 1, parameter);
297 let (knots, multiplicities) = compact(&new_expanded)?;
298 let mut controls = Vec::with_capacity(output.len());
299 let mut new_weights = Vec::with_capacity(output.len());
300 for (mut h, weight) in output {
301 if !weight.is_finite() || weight <= 0.0 {
302 return Err(GeomError::Degenerate(
303 "inserted homogeneous weight is not positive and finite".to_owned(),
304 ));
305 }
306 for value in &mut h {
307 *value /= weight;
308 }
309 controls.push(point(h));
310 new_weights.push(weight);
311 }
312 Ok(BSplineCurve {
313 degree: curve.degree,
314 control_points: controls,
315 knots,
316 multiplicities,
317 weights: curve.weights.as_ref().map(|_| new_weights),
318 knot_spec: curve.knot_spec,
319 closed: curve.closed,
320 self_intersect: curve.self_intersect,
321 })
322}
323
324fn expand_knots<P>(curve: &BSplineCurve<P>) -> Vec<Scalar> {
325 let expected = curve.control_points.len() + usize::from(curve.degree) + 1;
326 let mut expanded = Vec::with_capacity(expected);
327 for (&knot, &multiplicity) in curve.knots.iter().zip(&curve.multiplicities) {
328 expanded.extend(core::iter::repeat_n(knot, multiplicity as usize));
329 }
330 expanded
331}
332
333fn find_span(knots: &[Scalar], n: usize, degree: usize, parameter: Scalar) -> usize {
334 if parameter >= knots[n + 1] {
335 return n;
336 }
337 let mut low = degree;
338 let mut high = n + 1;
339 let mut mid = (low + high) / 2;
340 while parameter < knots[mid] || parameter >= knots[mid + 1] {
341 if parameter < knots[mid] {
342 high = mid;
343 } else {
344 low = mid;
345 }
346 mid = (low + high) / 2;
347 }
348 mid
349}
350
351fn compact(expanded: &[Scalar]) -> GeomResult<(Vec<Scalar>, Vec<u32>)> {
352 let mut knots = Vec::new();
353 let mut multiplicities = Vec::new();
354 for &knot in expanded {
355 if knots.last().copied() == Some(knot) {
356 *multiplicities
357 .last_mut()
358 .expect("knot and multiplicity stay parallel") += 1;
359 } else {
360 knots.push(knot);
361 multiplicities.push(1);
362 }
363 }
364 Ok((knots, multiplicities))
365}