1use crate::Vec3;
2use serde::{Deserialize, Serialize};
3
4const EPS: f64 = crate::tolerance::FP_FLOOR;
5
6pub const KNOT_IDENTITY_TOL: f64 = 1e-9;
13
14pub const KNOT_DEDUP_EPS: f64 = 1e-12;
28
29pub(crate) fn interior_knot_count(knots: &[f64], degree: usize) -> usize {
33 distinct_interior_knots(knots, degree).count()
34}
35
36fn distinct_interior_knots(knots: &[f64], degree: usize) -> impl Iterator<Item = f64> + '_ {
37 let start = knots[degree];
38 let end = knots[knots.len() - 1 - degree];
39 let mut previous = None;
40 knots.iter().copied().filter(move |&knot| {
41 if knot <= start + KNOT_DEDUP_EPS || knot >= end - KNOT_DEDUP_EPS {
42 return false;
43 }
44 if previous.is_none_or(|value: f64| (value - knot).abs() > KNOT_DEDUP_EPS) {
45 previous = Some(knot);
46 true
47 } else {
48 false
49 }
50 })
51}
52
53#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
54pub struct Vec4 {
55 pub x: f64,
56 pub y: f64,
57 pub z: f64,
58 pub w: f64,
59}
60
61pub(crate) fn parameter_line(u0: f64, v0: f64, u1: f64, v1: f64) -> Result<NurbsCurve, String> {
63 make_line(Vec3::new(u0, v0, 0.0), Vec3::new(u1, v1, 0.0))
64}
65
66pub(crate) fn curve_to_plane_parameters(
68 curve: &NurbsCurve,
69 origin: Vec3,
70 x_axis: Vec3,
71 y_axis: Vec3,
72) -> Result<NurbsCurve, String> {
73 NurbsCurve::new(
74 curve.degree,
75 curve.knots.clone(),
76 curve
77 .control_points
78 .iter()
79 .map(|point| {
80 let euclidean = Vec3::new(point.x / point.w, point.y / point.w, point.z / point.w);
81 let delta = euclidean.sub(origin);
82 Vec4 {
83 x: delta.dot(x_axis) * point.w,
84 y: delta.dot(y_axis) * point.w,
85 z: 0.0,
86 w: point.w,
87 }
88 })
89 .collect(),
90 )
91}
92
93impl Vec4 {
94 pub fn from_point(point: Vec3, weight: f64) -> Self {
95 Self {
96 x: point.x * weight,
97 y: point.y * weight,
98 z: point.z * weight,
99 w: weight,
100 }
101 }
102
103 pub(crate) fn add(self, rhs: Self) -> Self {
104 Self {
105 x: self.x + rhs.x,
106 y: self.y + rhs.y,
107 z: self.z + rhs.z,
108 w: self.w + rhs.w,
109 }
110 }
111
112 pub(crate) fn scale(self, factor: f64) -> Self {
113 Self {
114 x: self.x * factor,
115 y: self.y * factor,
116 z: self.z * factor,
117 w: self.w * factor,
118 }
119 }
120
121 pub(crate) fn point(self) -> Result<Vec3, String> {
122 if self.w.abs() <= EPS {
123 return Err("cannot project a homogeneous point with zero weight".into());
124 }
125 Ok(Vec3::new(self.x / self.w, self.y / self.w, self.z / self.w))
126 }
127}
128
129pub(crate) const MAX_STACK_DEGREE: usize = 7;
133pub(crate) const MAX_STACK_ORDER: usize = MAX_STACK_DEGREE + 1;
134
135fn knot_reject(reason: &str, knots: &[f64], degree: usize) -> String {
141 if std::env::var("BREP_DEBUG_KNOTS").is_err() {
142 return reason.to_string();
143 }
144 let mut worst_descent = f64::NEG_INFINITY;
145 let mut worst_index = 0usize;
146 for (index, pair) in knots.windows(2).enumerate() {
147 let descent = pair[0] - pair[1];
148 if descent > worst_descent {
149 worst_descent = descent;
150 worst_index = index;
151 }
152 }
153 let detail = format!(
154 "{reason} [degree={degree} count={} worst_descent={worst_descent:.6e} at index \
155 {worst_index} knots={knots:?}]",
156 knots.len()
157 );
158 eprintln!(
159 "KNOT-REJECT {detail}\n{}",
160 std::backtrace::Backtrace::force_capture()
161 );
162 detail
163}
164
165pub(crate) fn validate_knots(knots: &[f64], degree: usize) -> Result<(), String> {
169 if degree < 1 {
170 return Err("KnotVector: degree must be >= 1".into());
171 }
172 if knots.len() < 2 * (degree + 1) {
173 return Err(format!(
174 "KnotVector: need at least {} knots for degree {}, got {}",
175 2 * (degree + 1),
176 degree,
177 knots.len()
178 ));
179 }
180 if knots.iter().any(|value| !value.is_finite()) {
181 return Err("KnotVector: knots must be finite".into());
182 }
183 if knots
184 .windows(2)
185 .any(|pair| pair[1] < pair[0] - KNOT_IDENTITY_TOL)
186 {
187 return Err(knot_reject(
188 "KnotVector: knots must be non-decreasing",
189 knots,
190 degree,
191 ));
192 }
193 let first = knots[0];
194 let last = knots[knots.len() - 1];
195 for index in 0..=degree {
196 if (knots[index] - first).abs() > KNOT_IDENTITY_TOL {
197 return Err(knot_reject(
198 "KnotVector: expected clamped start",
199 knots,
200 degree,
201 ));
202 }
203 if (knots[knots.len() - 1 - index] - last).abs() > KNOT_IDENTITY_TOL {
204 return Err(knot_reject("KnotVector: expected clamped end", knots, degree));
205 }
206 }
207 if last - first <= KNOT_IDENTITY_TOL {
208 return Err(knot_reject(
209 "KnotVector: degenerate parameter range",
210 knots,
211 degree,
212 ));
213 }
214 Ok(())
215}
216
217pub(crate) fn knot_domain(knots: &[f64], degree: usize) -> [f64; 2] {
218 [knots[degree], knots[knots.len() - 1 - degree]]
219}
220
221pub(crate) fn knot_clamp(knots: &[f64], degree: usize, parameter: f64) -> f64 {
222 let [start, end] = knot_domain(knots, degree);
223 parameter.clamp(start, end)
224}
225
226pub(crate) fn knot_find_span(knots: &[f64], degree: usize, parameter: f64) -> usize {
227 let parameter = knot_clamp(knots, degree, parameter);
228 let n = knots.len() - degree - 2;
229 if parameter >= knots[n + 1] {
230 let mut span = n;
237 while span > degree && knots[span] >= knots[span + 1] {
238 span -= 1;
239 }
240 return span;
241 }
242 if parameter <= knots[degree] {
243 let mut span = degree;
250 while span < n && knots[span + 1] <= parameter {
251 span += 1;
252 }
253 return span;
254 }
255 let mut low = degree;
256 let mut high = n + 1;
257 let mut middle = (low + high) / 2;
258 while parameter < knots[middle] || parameter >= knots[middle + 1] {
259 if parameter < knots[middle] {
260 high = middle;
261 } else {
262 low = middle;
263 }
264 middle = (low + high) / 2;
265 }
266 middle
267}
268
269pub(crate) fn basis_functions_into(
272 knots: &[f64],
273 degree: usize,
274 span: usize,
275 parameter: f64,
276 basis: &mut [f64; MAX_STACK_ORDER],
277) {
278 debug_assert!(degree <= MAX_STACK_DEGREE);
279 match degree {
286 1 => basis_functions_scratch::<2>(knots, 1, span, parameter, basis),
287 2 => basis_functions_scratch::<3>(knots, 2, span, parameter, basis),
288 3 => basis_functions_scratch::<4>(knots, 3, span, parameter, basis),
289 4 => basis_functions_scratch::<5>(knots, 4, span, parameter, basis),
290 5 => basis_functions_scratch::<6>(knots, 5, span, parameter, basis),
291 _ => basis_functions_scratch::<MAX_STACK_ORDER>(knots, degree, span, parameter, basis),
292 }
293}
294
295#[inline(always)]
299fn basis_functions_scratch<const ORDER: usize>(
300 knots: &[f64],
301 degree: usize,
302 span: usize,
303 parameter: f64,
304 basis: &mut [f64; MAX_STACK_ORDER],
305) {
306 debug_assert!(degree < ORDER);
307 let mut left = [0.0f64; ORDER];
308 let mut right = [0.0f64; ORDER];
309 basis_functions_raw_into(knots, degree, span, parameter, EPS, basis, &mut left, &mut right);
310}
311
312pub(crate) fn basis_derivatives_into(
318 knots: &[f64],
319 degree: usize,
320 span: usize,
321 parameter: f64,
322 derivative_count: usize,
323 out: &mut [[f64; MAX_STACK_ORDER]],
324) {
325 debug_assert!(degree <= MAX_STACK_DEGREE);
326 debug_assert!(out.len() > derivative_count);
327 if switch!("BREP_BASIS_DERIVATIVE_COUNT_FIXED", true) {
331 match derivative_count {
332 0 => return basis_derivatives_count_fixed::<0>(knots, degree, span, parameter, out),
333 1 => return basis_derivatives_count_fixed::<1>(knots, degree, span, parameter, out),
334 2 => return basis_derivatives_count_fixed::<2>(knots, degree, span, parameter, out),
335 _ => {}
336 }
337 }
338 match degree {
343 1 => basis_derivatives_scratch::<2>(knots, 1, span, parameter, derivative_count, out),
344 2 => basis_derivatives_scratch::<3>(knots, 2, span, parameter, derivative_count, out),
345 3 => basis_derivatives_scratch::<4>(knots, 3, span, parameter, derivative_count, out),
346 4 => basis_derivatives_scratch::<5>(knots, 4, span, parameter, derivative_count, out),
347 5 => basis_derivatives_scratch::<6>(knots, 5, span, parameter, derivative_count, out),
348 _ => basis_derivatives_scratch::<MAX_STACK_ORDER>(knots, degree, span, parameter, derivative_count, out),
349 }
350}
351
352#[inline(always)]
353fn basis_derivatives_count_fixed<const COUNT: usize>(
354 knots: &[f64], degree: usize, span: usize, parameter: f64,
355 out: &mut [[f64; MAX_STACK_ORDER]],
356) {
357 match degree {
358 1 => basis_derivatives_scratch::<2>(knots, 1, span, parameter, COUNT, out),
359 2 => basis_derivatives_scratch::<3>(knots, 2, span, parameter, COUNT, out),
360 3 => basis_derivatives_scratch::<4>(knots, 3, span, parameter, COUNT, out),
361 4 => basis_derivatives_scratch::<5>(knots, 4, span, parameter, COUNT, out),
362 5 => basis_derivatives_scratch::<6>(knots, 5, span, parameter, COUNT, out),
363 _ => basis_derivatives_scratch::<MAX_STACK_ORDER>(knots, degree, span, parameter, COUNT, out),
364 }
365}
366
367#[inline(always)]
373fn basis_derivatives_scratch<const ORDER: usize>(
374 knots: &[f64],
375 degree: usize,
376 span: usize,
377 parameter: f64,
378 derivative_count: usize,
379 out: &mut [[f64; MAX_STACK_ORDER]],
380) {
381 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::BASIS);
382 debug_assert!(degree < ORDER);
383 let p = degree;
384 let n = derivative_count.min(p);
385 let mut ndu = [[0.0f64; ORDER]; ORDER];
386 let mut left = [0.0f64; ORDER];
387 let mut right = [0.0f64; ORDER];
388 ndu[0][0] = 1.0;
389
390 for j in 1..=p {
391 left[j] = parameter - knots[span + 1 - j];
392 right[j] = knots[span + j] - parameter;
393 let mut saved = 0.0;
394 for r in 0..j {
395 ndu[j][r] = right[r + 1] + left[j - r];
396 let temporary = if ndu[j][r].abs() <= EPS {
397 0.0
398 } else {
399 ndu[r][j - 1] / ndu[j][r]
400 };
401 ndu[r][j] = saved + right[r + 1] * temporary;
402 saved = left[j - r] * temporary;
403 }
404 ndu[j][j] = saved;
405 }
406
407 for j in 0..=p {
408 out[0][j] = ndu[j][p];
409 }
410 let mut a = [[0.0f64; ORDER]; 2];
411 for r in 0..=p {
412 let mut s1 = 0;
413 let mut s2 = 1;
414 a[0][0] = 1.0;
415 for k in 1..=n {
416 a[s2] = [0.0; ORDER];
417 let mut value = 0.0;
418 let rk = r as isize - k as isize;
419 let pk = p - k;
420 if r >= k {
421 let denominator = ndu[pk + 1][rk as usize];
422 a[s2][0] = a[s1][0] / denominator;
423 value = a[s2][0] * ndu[rk as usize][pk];
424 }
425 let j1 = if rk >= -1 { 1 } else { (-rk) as usize };
426 let j2 = if r <= pk + 1 { k - 1 } else { p - r };
427 if j1 <= j2 {
428 for j in j1..=j2 {
429 let index = (rk + j as isize) as usize;
430 a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][index];
431 value += a[s2][j] * ndu[index][pk];
432 }
433 }
434 if r <= pk {
435 a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r];
436 value += a[s2][k] * ndu[r][pk];
437 }
438 out[k][r] = value;
439 std::mem::swap(&mut s1, &mut s2);
440 }
441 }
442 let mut factor = p as f64;
443 for k in 1..=n {
444 for value in out[k][..=p].iter_mut() {
445 *value *= factor;
446 }
447 factor *= (p - k) as f64;
448 }
449}
450
451#[derive(Clone, Debug, Deserialize, Serialize)]
452pub struct KnotVector {
453 pub knots: Vec<f64>,
454 pub degree: usize,
455}
456
457pub(crate) fn interior_knots(knots: &[f64], degree: usize) -> Vec<f64> {
459 distinct_interior_knots(knots, degree).collect()
460}
461
462impl KnotVector {
463 pub fn new(knots: Vec<f64>, degree: usize) -> Result<Self, String> {
464 validate_knots(&knots, degree)?;
465 Ok(Self { knots, degree })
466 }
467
468 pub fn control_point_count(&self) -> usize {
469 self.knots.len() - self.degree - 1
470 }
471
472 pub fn domain(&self) -> [f64; 2] {
473 [
474 self.knots[self.degree],
475 self.knots[self.knots.len() - 1 - self.degree],
476 ]
477 }
478
479 pub fn clamp_param(&self, parameter: f64) -> f64 {
480 knot_clamp(&self.knots, self.degree, parameter)
481 }
482
483 pub fn find_span(&self, parameter: f64) -> usize {
484 knot_find_span(&self.knots, self.degree, parameter)
485 }
486
487 pub fn basis_functions(&self, span: usize, parameter: f64) -> Vec<f64> {
488 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::BASIS);
489 if self.degree <= MAX_STACK_DEGREE {
490 let mut basis = [0.0f64; MAX_STACK_ORDER];
491 basis_functions_into(&self.knots, self.degree, span, parameter, &mut basis);
492 return basis[..=self.degree].to_vec();
493 }
494 basis_functions_raw(&self.knots, self.degree, span, parameter, EPS)
495 }
496
497 pub fn basis_derivatives(
498 &self,
499 span: usize,
500 parameter: f64,
501 derivative_count: usize,
502 ) -> Vec<Vec<f64>> {
503 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::BASIS);
504 if self.degree <= MAX_STACK_DEGREE && derivative_count <= MAX_STACK_DEGREE {
505 let mut rows = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
506 basis_derivatives_into(
507 &self.knots,
508 self.degree,
509 span,
510 parameter,
511 derivative_count,
512 &mut rows[..=derivative_count],
513 );
514 return rows[..=derivative_count]
515 .iter()
516 .map(|row| row[..=self.degree].to_vec())
517 .collect();
518 }
519 let p = self.degree;
520 let n = derivative_count.min(p);
521 let mut ndu = vec![vec![0.0; p + 1]; p + 1];
522 let mut left = vec![0.0; p + 1];
523 let mut right = vec![0.0; p + 1];
524 ndu[0][0] = 1.0;
525
526 for j in 1..=p {
527 left[j] = parameter - self.knots[span + 1 - j];
528 right[j] = self.knots[span + j] - parameter;
529 let mut saved = 0.0;
530 for r in 0..j {
531 ndu[j][r] = right[r + 1] + left[j - r];
532 let temporary = if ndu[j][r].abs() <= EPS {
533 0.0
534 } else {
535 ndu[r][j - 1] / ndu[j][r]
536 };
537 ndu[r][j] = saved + right[r + 1] * temporary;
538 saved = left[j - r] * temporary;
539 }
540 ndu[j][j] = saved;
541 }
542
543 let mut derivatives = vec![vec![0.0; p + 1]; derivative_count + 1];
544 for j in 0..=p {
545 derivatives[0][j] = ndu[j][p];
546 }
547 let mut a = vec![vec![0.0; p + 1]; 2];
548 for r in 0..=p {
549 let mut s1 = 0;
550 let mut s2 = 1;
551 a[0][0] = 1.0;
552 for k in 1..=n {
553 a[s2].fill(0.0);
554 let mut value = 0.0;
555 let rk = r as isize - k as isize;
556 let pk = p - k;
557 if r >= k {
558 let denominator = ndu[pk + 1][rk as usize];
559 a[s2][0] = a[s1][0] / denominator;
560 value = a[s2][0] * ndu[rk as usize][pk];
561 }
562 let j1 = if rk >= -1 { 1 } else { (-rk) as usize };
563 let j2 = if r <= pk + 1 { k - 1 } else { p - r };
564 if j1 <= j2 {
565 for j in j1..=j2 {
566 let index = (rk + j as isize) as usize;
567 a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][index];
568 value += a[s2][j] * ndu[index][pk];
569 }
570 }
571 if r <= pk {
572 a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r];
573 value += a[s2][k] * ndu[r][pk];
574 }
575 derivatives[k][r] = value;
576 std::mem::swap(&mut s1, &mut s2);
577 }
578 }
579 let mut factor = p as f64;
580 for (k, row) in derivatives.iter_mut().enumerate().take(n + 1).skip(1) {
581 for value in row {
582 *value *= factor;
583 }
584 factor *= (p - k) as f64;
585 }
586 derivatives
587 }
588}
589
590#[derive(Clone, Debug, Deserialize, Serialize)]
598pub struct NurbsCurve {
599 pub degree: usize,
600 pub knots: Vec<f64>,
601 pub control_points: Vec<Vec4>,
602 #[serde(skip, default)]
606 validated: std::cell::Cell<bool>,
607}
608
609impl NurbsCurve {
610 pub(crate) fn is_validated(&self) -> bool {
613 self.validated.get()
614 }
615
616 pub(crate) fn set_validated(&mut self, validated: bool) {
618 self.validated = std::cell::Cell::new(validated);
619 }
620
621 pub fn new(degree: usize, knots: Vec<f64>, control_points: Vec<Vec4>) -> Result<Self, String> {
622 let curve = Self {
623 degree,
624 knots,
625 control_points,
626 validated: std::cell::Cell::new(false),
627 };
628 curve.ensure_valid()?;
629 Ok(curve)
630 }
631
632 fn ensure_valid(&self) -> Result<(), String> {
634 if self.validated.get() {
635 return Ok(());
636 }
637 validate_knots(&self.knots, self.degree)?;
638 let expected = self.knots.len() - self.degree - 1;
639 if self.control_points.len() != expected {
640 return Err(format!(
641 "NurbsCurve: knot vector implies {} control points, got {}",
642 expected,
643 self.control_points.len()
644 ));
645 }
646 if self.control_points.iter().any(|point| {
647 point.w <= EPS
648 || ![point.x, point.y, point.z, point.w]
649 .iter()
650 .all(|value| value.is_finite())
651 }) {
652 return Err("NurbsCurve: control points must be finite with positive weights".into());
653 }
654 self.validated.set(true);
655 Ok(())
656 }
657
658 fn knot_vector(&self) -> Result<KnotVector, String> {
659 KnotVector::new(self.knots.clone(), self.degree)
660 }
661
662 pub fn domain(&self) -> Result<[f64; 2], String> {
663 self.ensure_valid()?;
664 Ok(knot_domain(&self.knots, self.degree))
665 }
666
667 #[cfg_attr(feature = "profile-wall", track_caller)]
668 pub fn evaluate_homogeneous(&self, parameter: f64) -> Result<Vec4, String> {
669 let _reader = crate::reader_profile::Point::open(crate::reader_profile::CURVE, std::panic::Location::caller());
670 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
671 self.ensure_valid()?;
672 let span = knot_find_span(&self.knots, self.degree, parameter);
673 let parameter = knot_clamp(&self.knots, self.degree, parameter);
674 let mut point = Vec4 {
675 x: 0.0,
676 y: 0.0,
677 z: 0.0,
678 w: 0.0,
679 };
680 if self.degree <= MAX_STACK_DEGREE {
681 let mut basis = [0.0f64; MAX_STACK_ORDER];
682 basis_functions_into(&self.knots, self.degree, span, parameter, &mut basis);
683 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
684 for (index, value) in basis[..=self.degree].iter().enumerate() {
685 point = point.add(self.control_points[span - self.degree + index].scale(*value));
686 }
687 } else {
688 let knot_vector = self.knot_vector()?;
689 let basis = knot_vector.basis_functions(span, parameter);
690 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
691 for (index, value) in basis.iter().enumerate() {
692 point = point.add(self.control_points[span - self.degree + index].scale(*value));
693 }
694 }
695 Ok(point)
696 }
697
698 #[cfg_attr(feature = "profile-wall", track_caller)]
699 pub fn evaluate(&self, parameter: f64) -> Result<Vec3, String> {
700 let _reader = crate::reader_profile::Point::open(crate::reader_profile::CURVE, std::panic::Location::caller());
701 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
702 self.evaluate_homogeneous(parameter)?.point()
703 }
704
705 pub(crate) fn straight_segment(&self, tolerance: f64) -> Option<(Vec3, Vec3)> {
725 self.ensure_valid().ok()?;
726 let points = self
727 .control_points
728 .iter()
729 .map(|point| point.point().ok())
730 .collect::<Option<Vec<Vec3>>>()?;
731 let start = *points.first()?;
732 let end = *points.last()?;
733 let chord = end.sub(start);
734 let length = chord.length();
735 if length <= tolerance {
736 return None;
737 }
738 let direction = chord.scale(1.0 / length);
739 let mut reached = 0.0f64;
740 for point in &points {
741 let offset = point.sub(start);
742 let axial = offset.dot(direction);
743 if offset.sub(direction.scale(axial)).length() > tolerance {
744 return None; }
746 if axial < reached - tolerance {
747 return None; }
749 reached = reached.max(axial);
750 }
751 Some((start, end))
752 }
753
754 #[cfg_attr(feature = "profile-wall", track_caller)]
759 pub fn evaluate_extended(&self, parameter: f64) -> Result<Vec3, String> {
760 let _reader = crate::reader_profile::Point::open(crate::reader_profile::CURVE, std::panic::Location::caller());
761 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
762 Ok(self.derivatives_extended(parameter, 0)?[0])
763 }
764
765 pub fn derivatives_extended(
769 &self,
770 parameter: f64,
771 derivative_count: usize,
772 ) -> Result<Vec<Vec3>, String> {
773 let [start, end] = self.domain()?;
774 if parameter >= start && parameter <= end {
775 return self.derivatives(parameter, derivative_count);
776 }
777 let period = end - start;
778 if period > 0.0 {
779 let closed =
780 self.evaluate(start)?.sub(self.evaluate(end)?).length() <= 1e-9 * (1.0 + period);
781 if closed {
782 let wrapped = start + (parameter - start).rem_euclid(period);
783 return self.derivatives(wrapped, derivative_count);
784 }
785 }
786 let boundary = if parameter < start { start } else { end };
787 let base = self.derivatives(boundary, derivative_count.max(1))?;
788 let mut result = Vec::with_capacity(derivative_count + 1);
789 result.push(base[0].add(base[1].scale(parameter - boundary)));
790 if derivative_count >= 1 {
791 result.push(base[1]);
792 }
793 for _ in 2..=derivative_count {
794 result.push(Vec3::default());
795 }
796 Ok(result)
797 }
798
799 pub fn derivatives(
800 &self,
801 parameter: f64,
802 derivative_count: usize,
803 ) -> Result<Vec<Vec3>, String> {
804 if derivative_count <= 2 && small_curve_derivatives_enabled() {
805 return Ok(self.derivatives_small(parameter, derivative_count)?[..=derivative_count].to_vec());
806 }
807 self.ensure_valid()?;
808 let parameter = knot_clamp(&self.knots, self.degree, parameter);
809 let span = knot_find_span(&self.knots, self.degree, parameter);
810 let calculated_count = derivative_count.min(self.degree);
811 let mut homogeneous: Vec<Vec4> = Vec::with_capacity(calculated_count + 1);
812 if self.degree <= MAX_STACK_DEGREE {
813 let mut rows = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
814 basis_derivatives_into(
815 &self.knots,
816 self.degree,
817 span,
818 parameter,
819 calculated_count,
820 &mut rows[..=calculated_count],
821 );
822 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
823 for row in rows.iter().take(calculated_count + 1) {
824 let mut point = Vec4 {
825 x: 0.0,
826 y: 0.0,
827 z: 0.0,
828 w: 0.0,
829 };
830 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
831 point =
832 point.add(self.control_points[span - self.degree + index].scale(*value));
833 }
834 homogeneous.push(point);
835 }
836 } else {
837 let knot_vector = self.knot_vector()?;
838 let basis = knot_vector.basis_derivatives(span, parameter, calculated_count);
839 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
840 for row in basis.iter().take(calculated_count + 1) {
841 let mut point = Vec4 {
842 x: 0.0,
843 y: 0.0,
844 z: 0.0,
845 w: 0.0,
846 };
847 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
848 point =
849 point.add(self.control_points[span - self.degree + index].scale(*value));
850 }
851 homogeneous.push(point);
852 }
853 }
854
855 let mut result: Vec<Vec3> = Vec::with_capacity(derivative_count + 1);
856 for k in 0..=calculated_count {
857 let mut value = Vec3::new(homogeneous[k].x, homogeneous[k].y, homogeneous[k].z);
858 for i in 1..=k {
859 value = value.sub(result[k - i].scale(binomial(k, i) * homogeneous[i].w));
860 }
861 result.push(value.scale(1.0 / homogeneous[0].w));
862 }
863 result.resize(derivative_count + 1, Vec3::default());
864 Ok(result)
865 }
866
867 pub fn unit_tangent(
892 &self,
893 parameter: f64,
894 range_start: f64,
895 range_end: f64,
896 ) -> Result<Vec3, String> {
897 let derivative = self.derivatives(parameter, 1)?[1];
898 self.tangent_or_one_sided_limit(derivative, parameter, range_start, range_end)
899 }
900
901 pub fn point_and_unit_tangent_extended(
923 &self,
924 parameter: f64,
925 range_start: f64,
926 range_end: f64,
927 ) -> Result<(Vec3, Vec3), String> {
928 let derivatives = self.derivatives_extended(parameter, 1)?;
929 if let Ok(tangent) = derivatives[1].normalized() {
930 return Ok((derivatives[0], tangent));
931 }
932 let read_at = self.extended_read_parameter(parameter)?;
933 let low = range_start.min(range_end).min(read_at);
934 let high = range_start.max(range_end).max(read_at);
935 let tangent = self.tangent_or_one_sided_limit(derivatives[1], read_at, low, high)?;
936 Ok((derivatives[0], tangent))
937 }
938
939 fn extended_read_parameter(&self, parameter: f64) -> Result<f64, String> {
945 let [start, end] = self.domain()?;
946 if parameter >= start && parameter <= end {
947 return Ok(parameter);
948 }
949 if self.extension_wraps(start, end)? {
950 return Ok(start + (parameter - start).rem_euclid(end - start));
951 }
952 Ok(if parameter < start { start } else { end })
953 }
954
955 fn extension_wraps(&self, start: f64, end: f64) -> Result<bool, String> {
958 let period = end - start;
959 Ok(period > 0.0
960 && self.evaluate(start)?.sub(self.evaluate(end)?).length() <= 1e-9 * (1.0 + period))
961 }
962
963 pub(crate) fn stationary_open_end(&self, parameter: f64) -> Result<Option<f64>, String> {
975 let [start, end] = self.domain()?;
976 if parameter >= start && parameter <= end {
977 return Ok(None);
978 }
979 let boundary = if parameter < start { start } else { end };
980 if self.derivatives(boundary, 1)?[1].normalized().is_ok()
981 || self.extension_wraps(start, end)?
982 {
983 return Ok(None);
984 }
985 Ok(Some(boundary))
986 }
987
988 fn tangent_or_one_sided_limit(
993 &self,
994 derivative: Vec3,
995 parameter: f64,
996 range_start: f64,
997 range_end: f64,
998 ) -> Result<Vec3, String> {
999 let error = match derivative.normalized() {
1000 Ok(tangent) => return Ok(tangent),
1001 Err(error) => error,
1002 };
1003 if std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0") {
1004 return Err(error);
1005 }
1006 self.stationary_tangent_rescue(parameter, range_start, range_end)
1007 .ok_or_else(|| {
1008 format!(
1009 "curve tangent: the first derivative vanishes at t = {parameter} and \
1010 everywhere in [{range_start}, {range_end}] the probe can reach, so this \
1011 curve has no direction of travel to report"
1012 )
1013 })
1014 }
1015
1016 pub fn stationary_tangent_rescue(
1044 &self,
1045 parameter: f64,
1046 range_start: f64,
1047 range_end: f64,
1048 ) -> Option<Vec3> {
1049 let low = range_start.min(range_end);
1050 let high = range_start.max(range_end);
1051 let span = high - low;
1052 if !(span > 0.0) {
1053 return None;
1054 }
1055 for scale in [1e-6, 1e-4, 1e-2, 1e-1] {
1056 let step = span * scale;
1057 for probe in [parameter + step, parameter - step] {
1058 if probe < low || probe > high {
1059 continue;
1060 }
1061 if let Some(unit) = self
1062 .derivatives(probe, 1)
1063 .ok()
1064 .and_then(|derivatives| derivatives[1].normalized().ok())
1065 {
1066 return Some(unit);
1067 }
1068 }
1069 }
1070 None
1071 }
1072
1073 pub(crate) fn derivatives_small(
1084 &self,
1085 parameter: f64,
1086 derivative_count: usize,
1087 ) -> Result<[Vec3; 3], String> {
1088 debug_assert!(derivative_count <= 2);
1089 self.ensure_valid()?;
1090 let parameter = knot_clamp(&self.knots, self.degree, parameter);
1091 let span = knot_find_span(&self.knots, self.degree, parameter);
1092 let calculated_count = derivative_count.min(self.degree);
1093 let zero = Vec4 {
1094 x: 0.0,
1095 y: 0.0,
1096 z: 0.0,
1097 w: 0.0,
1098 };
1099 let mut homogeneous = [zero; 3];
1100 if self.degree <= MAX_STACK_DEGREE {
1101 let mut rows = [[0.0f64; MAX_STACK_ORDER]; 3];
1104 basis_derivatives_into(
1105 &self.knots,
1106 self.degree,
1107 span,
1108 parameter,
1109 calculated_count,
1110 &mut rows[..=calculated_count],
1111 );
1112 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
1113 for (k, row) in rows.iter().take(calculated_count + 1).enumerate() {
1114 let mut point = zero;
1115 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
1116 point =
1117 point.add(self.control_points[span - self.degree + index].scale(*value));
1118 }
1119 homogeneous[k] = point;
1120 }
1121 } else {
1122 let knot_vector = self.knot_vector()?;
1123 let basis = knot_vector.basis_derivatives(span, parameter, calculated_count);
1124 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
1125 for (k, row) in basis.iter().take(calculated_count + 1).enumerate() {
1126 let mut point = zero;
1127 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
1128 point =
1129 point.add(self.control_points[span - self.degree + index].scale(*value));
1130 }
1131 homogeneous[k] = point;
1132 }
1133 }
1134
1135 let mut result = [Vec3::default(); 3];
1136 for k in 0..=calculated_count {
1137 let mut value = Vec3::new(homogeneous[k].x, homogeneous[k].y, homogeneous[k].z);
1138 for i in 1..=k {
1139 value = value.sub(result[k - i].scale(binomial(k, i) * homogeneous[i].w));
1140 }
1141 result[k] = value.scale(1.0 / homogeneous[0].w);
1142 }
1143 Ok(result)
1144 }
1145
1146 #[inline]
1149 pub(crate) fn deriv1(&self, parameter: f64) -> Result<(Vec3, Vec3), String> {
1150 let d = self.derivatives_small(parameter, 1)?;
1151 Ok((d[0], d[1]))
1152 }
1153
1154 pub fn reversed(&self) -> Result<Self, String> {
1155 let start = self.knots[0];
1156 let end = self.knots[self.knots.len() - 1];
1157 let knots = self
1158 .knots
1159 .iter()
1160 .rev()
1161 .map(|knot| start + end - knot)
1162 .collect();
1163 let control_points = self.control_points.iter().rev().copied().collect();
1164 Self::new(self.degree, knots, control_points)
1165 }
1166
1167 pub fn insert_knot(&self, parameter: f64, requested: usize) -> Result<Self, String> {
1168 let knot_vector = self.knot_vector()?;
1169 let degree = self.degree;
1170 let parameter = knot_vector.clamp_param(parameter);
1171 let parameter = self
1176 .knots
1177 .iter()
1178 .copied()
1179 .find(|knot| (knot - parameter).abs() <= KNOT_IDENTITY_TOL)
1180 .unwrap_or(parameter);
1181 let multiplicity = self
1182 .knots
1183 .iter()
1184 .filter(|knot| (**knot - parameter).abs() <= KNOT_IDENTITY_TOL)
1185 .count();
1186 let insertion_count = requested.min(degree.saturating_sub(multiplicity));
1187 if insertion_count == 0 {
1188 return Ok(self.clone());
1189 }
1190 let span = knot_vector.find_span(parameter);
1191 let last_control = self.control_points.len() - 1;
1192 let mut knots = Vec::with_capacity(self.knots.len() + insertion_count);
1193 knots.extend_from_slice(&self.knots[..=span]);
1194 knots.extend(std::iter::repeat_n(parameter, insertion_count));
1195 knots.extend_from_slice(&self.knots[span + 1..]);
1196
1197 let mut output = vec![
1198 Vec4 {
1199 x: 0.0,
1200 y: 0.0,
1201 z: 0.0,
1202 w: 1.0,
1203 };
1204 last_control + 1 + insertion_count
1205 ];
1206 output[..=span - degree].copy_from_slice(&self.control_points[..=span - degree]);
1207 for index in span - multiplicity..=last_control {
1208 output[index + insertion_count] = self.control_points[index];
1209 }
1210 let mut affected = vec![
1211 Vec4 {
1212 x: 0.0,
1213 y: 0.0,
1214 z: 0.0,
1215 w: 1.0,
1216 };
1217 degree + 1
1218 ];
1219 affected[..=degree - multiplicity]
1220 .copy_from_slice(&self.control_points[span - degree..=span - multiplicity]);
1221 let mut left = 0;
1222 for insertion in 1..=insertion_count {
1223 left = span - degree + insertion;
1224 for index in 0..=degree - insertion - multiplicity {
1225 let denominator = self.knots[index + span + 1] - self.knots[left + index];
1226 let alpha = (parameter - self.knots[left + index]) / denominator;
1227 affected[index] = affected[index + 1]
1228 .scale(alpha)
1229 .add(affected[index].scale(1.0 - alpha));
1230 }
1231 output[left] = affected[0];
1232 output[span + insertion_count - insertion - multiplicity] =
1233 affected[degree - insertion - multiplicity];
1234 }
1235 for index in left + 1..span - multiplicity {
1236 output[index] = affected[index - left];
1237 }
1238 Self::new(degree, knots, output)
1239 }
1240
1241 pub fn split(&self, parameter: f64) -> Result<(Self, Self), String> {
1242 let [start, end] = self.domain()?;
1243 if parameter <= start + KNOT_IDENTITY_TOL || parameter >= end - KNOT_IDENTITY_TOL {
1244 return Err(format!(
1245 "NurbsCurve.split: parameter {parameter} must be strictly inside domain [{start}, {end}]"
1246 ));
1247 }
1248 let parameter = self
1251 .knots
1252 .iter()
1253 .copied()
1254 .find(|knot| (knot - parameter).abs() <= KNOT_IDENTITY_TOL)
1255 .unwrap_or(parameter);
1256 let multiplicity = self
1257 .knots
1258 .iter()
1259 .filter(|knot| (**knot - parameter).abs() <= KNOT_IDENTITY_TOL)
1260 .count();
1261 let refined = self.insert_knot(parameter, self.degree.saturating_sub(multiplicity))?;
1262 let first = refined
1263 .knots
1264 .iter()
1265 .position(|knot| (*knot - parameter).abs() <= KNOT_IDENTITY_TOL)
1266 .ok_or_else(|| "NurbsCurve.split: inserted knot not found".to_string())?;
1267 let mut left_knots = refined.knots[..first + self.degree].to_vec();
1268 left_knots.push(parameter);
1269 let left_points = refined.control_points[..first].to_vec();
1270 let mut right_knots = vec![parameter; self.degree + 1];
1271 right_knots.extend_from_slice(&refined.knots[first + self.degree..]);
1272 let right_points = refined.control_points[first - 1..].to_vec();
1273 Ok((
1274 Self::new(self.degree, left_knots, left_points)?,
1275 Self::new(self.degree, right_knots, right_points)?,
1276 ))
1277 }
1278}
1279
1280pub fn make_line(start: Vec3, end: Vec3) -> Result<NurbsCurve, String> {
1281 NurbsCurve::new(
1282 1,
1283 vec![0.0, 0.0, 1.0, 1.0],
1284 vec![Vec4::from_point(start, 1.0), Vec4::from_point(end, 1.0)],
1285 )
1286}
1287
1288pub fn make_arc(
1289 center: Vec3,
1290 x_axis: Vec3,
1291 y_axis: Vec3,
1292 radius: f64,
1293 start_angle: f64,
1294 end_angle: f64,
1295) -> Result<NurbsCurve, String> {
1296 if radius <= EPS {
1297 return Err("makeArc: radius must be positive".into());
1298 }
1299 let x_axis = x_axis.normalized()?;
1300 let y_axis = y_axis.normalized()?;
1301 if x_axis.dot(y_axis).abs() > 1e-9 {
1302 return Err("makeArc: xAxis and yAxis must be orthogonal".into());
1303 }
1304 let mut theta = end_angle - start_angle;
1305 if theta <= EPS {
1306 return Err("makeArc: endAngle must exceed startAngle".into());
1307 }
1308 if theta > std::f64::consts::TAU + EPS {
1309 return Err("makeArc: sweep exceeds full circle".into());
1310 }
1311 theta = theta.min(std::f64::consts::TAU);
1312 let segment_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
1313 let segment_angle = theta / segment_count as f64;
1314 let middle_weight = (segment_angle / 2.0).cos();
1315 let point_at = |angle: f64| {
1316 center
1317 .add(x_axis.scale(radius * angle.cos()))
1318 .add(y_axis.scale(radius * angle.sin()))
1319 };
1320 let tangent_at = |angle: f64| x_axis.scale(-angle.sin()).add(y_axis.scale(angle.cos()));
1321
1322 let mut points = Vec::with_capacity(2 * segment_count + 1);
1323 let mut angle = start_angle;
1324 let mut first_point = point_at(angle);
1325 let mut first_tangent = tangent_at(angle);
1326 points.push(Vec4::from_point(first_point, 1.0));
1327 for _ in 0..segment_count {
1328 angle += segment_angle;
1329 let end_point = point_at(angle);
1330 let end_tangent = tangent_at(angle);
1331 let cross = first_tangent.cross(end_tangent);
1332 let denominator = cross.length_squared();
1333 if denominator <= EPS {
1334 return Err("makeArc: arc tangents are parallel".into());
1335 }
1336 let distance = end_point.sub(first_point).cross(end_tangent).dot(cross) / denominator;
1337 let middle = first_point.add(first_tangent.scale(distance));
1338 points.push(Vec4::from_point(middle, middle_weight));
1339 points.push(Vec4::from_point(end_point, 1.0));
1340 first_point = end_point;
1341 first_tangent = end_tangent;
1342 }
1343 let mut knots = vec![0.0, 0.0, 0.0];
1344 for index in 1..segment_count {
1345 let knot = index as f64 / segment_count as f64;
1346 knots.extend([knot, knot]);
1347 }
1348 knots.extend([1.0, 1.0, 1.0]);
1349 NurbsCurve::new(2, knots, points)
1350}
1351
1352pub fn make_circle(center: Vec3, normal: Vec3, radius: f64) -> Result<NurbsCurve, String> {
1353 let normal = normal.normalized()?;
1354 let x_axis = normal.perpendicular()?;
1355 let y_axis = normal.cross(x_axis).normalized()?;
1356 make_arc(center, x_axis, y_axis, radius, 0.0, std::f64::consts::TAU)
1357}
1358
1359fn conic_frame(fn_name: &str, primary: Vec3, hint: Vec3) -> Result<(Vec3, Vec3), String> {
1365 let x_axis = primary
1366 .normalized()
1367 .map_err(|_| format!("{fn_name}: primary axis must be non-zero"))?;
1368 if hint.length() <= EPS {
1369 return Err(format!("{fn_name}: in-plane direction must be non-zero"));
1370 }
1371 hint.sub(x_axis.scale(hint.dot(x_axis)))
1372 .normalized()
1373 .map(|y_axis| (x_axis, y_axis))
1374 .map_err(|_| format!("{fn_name}: frame directions must not be parallel"))
1375}
1376
1377fn conic_range(fn_name: &str, t0: f64, t1: f64) -> Result<(), String> {
1381 if !t0.is_finite() || !t1.is_finite() {
1382 return Err(format!("{fn_name}: parameter range must be finite"));
1383 }
1384 if t1 - t0 <= KNOT_IDENTITY_TOL {
1385 return Err(format!("{fn_name}: t1 ({t1}) must exceed t0 ({t0})"));
1386 }
1387 Ok(())
1388}
1389
1390fn conic_points_finite(fn_name: &str, points: &[Vec4]) -> Result<(), String> {
1395 if points.iter().any(|point| {
1396 ![point.x, point.y, point.z, point.w]
1397 .iter()
1398 .all(|value| value.is_finite())
1399 }) {
1400 return Err(format!(
1401 "{fn_name}: control points overflow f64 — parameter range too extreme"
1402 ));
1403 }
1404 Ok(())
1405}
1406
1407pub fn make_parabola(
1415 vertex: Vec3,
1416 axis: Vec3,
1417 latus_direction: Vec3,
1418 focal: f64,
1419 t0: f64,
1420 t1: f64,
1421) -> Result<NurbsCurve, String> {
1422 if !focal.is_finite() || focal <= EPS {
1423 return Err("make_parabola: focal distance must be positive".into());
1424 }
1425 conic_range("make_parabola", t0, t1)?;
1426 let (x_axis, y_axis) = conic_frame("make_parabola", axis, latus_direction)?;
1427 let point_at = |t: f64| {
1428 vertex
1429 .add(x_axis.scale(focal * t * t))
1430 .add(y_axis.scale(2.0 * focal * t))
1431 };
1432 let middle = vertex
1437 .add(x_axis.scale(focal * t0 * t1))
1438 .add(y_axis.scale(focal * (t0 + t1)));
1439 let control_points = vec![
1440 Vec4::from_point(point_at(t0), 1.0),
1441 Vec4::from_point(middle, 1.0),
1442 Vec4::from_point(point_at(t1), 1.0),
1443 ];
1444 conic_points_finite("make_parabola", &control_points)?;
1445 NurbsCurve::new(2, vec![t0, t0, t0, t1, t1, t1], control_points)
1446}
1447
1448pub fn make_hyperbola(
1458 center: Vec3,
1459 major_axis: Vec3,
1460 minor_axis: Vec3,
1461 a: f64,
1462 b: f64,
1463 t0: f64,
1464 t1: f64,
1465) -> Result<NurbsCurve, String> {
1466 if !a.is_finite() || a <= EPS {
1467 return Err("make_hyperbola: semi-axis a must be positive".into());
1468 }
1469 if !b.is_finite() || b <= EPS {
1470 return Err("make_hyperbola: semi-axis b must be positive".into());
1471 }
1472 conic_range("make_hyperbola", t0, t1)?;
1473 let (x_axis, y_axis) = conic_frame("make_hyperbola", major_axis, minor_axis)?;
1474 let point_at = |t: f64| {
1475 center
1476 .add(x_axis.scale(a * t.cosh()))
1477 .add(y_axis.scale(b * t.sinh()))
1478 };
1479 let mid = 0.5 * (t0 + t1);
1480 let middle_weight = (0.5 * (t1 - t0)).cosh();
1481 let apex = center
1488 .add(x_axis.scale(a * mid.cosh() / middle_weight))
1489 .add(y_axis.scale(b * mid.sinh() / middle_weight));
1490 let control_points = vec![
1491 Vec4::from_point(point_at(t0), 1.0),
1492 Vec4::from_point(apex, middle_weight),
1493 Vec4::from_point(point_at(t1), 1.0),
1494 ];
1495 conic_points_finite("make_hyperbola", &control_points)?;
1496 NurbsCurve::new(2, vec![t0, t0, t0, t1, t1, t1], control_points)
1497}
1498
1499pub fn uniform_clamped_knots(
1500 control_point_count: usize,
1501 degree: usize,
1502) -> Result<Vec<f64>, String> {
1503 if control_point_count == 0 || control_point_count - 1 < degree {
1504 return Err("uniformClampedKnots: need at least degree+1 control points".into());
1505 }
1506 let n = control_point_count - 1;
1507 let mut knots = vec![0.0; degree + 1];
1508 let interior = n - degree;
1509 for index in 1..=interior {
1510 knots.push(index as f64 / (interior + 1) as f64);
1511 }
1512 knots.extend(std::iter::repeat_n(1.0, degree + 1));
1513 Ok(knots)
1514}
1515
1516fn binomial(n: usize, k: usize) -> f64 {
1517 if k > n {
1518 return 0.0;
1519 }
1520 let k = k.min(n - k);
1521 (1..=k).fold(1.0, |value, index| {
1522 value * (n - k + index) as f64 / index as f64
1523 })
1524}
1525
1526
1527
1528
1529pub(crate) fn small_curve_derivatives_enabled() -> bool {
1537 switch!("BREP_CURVE_SMALL_DERIVATIVES", true)
1538}
1539
1540
1541
1542pub(crate) fn bezier_decompose_raw<T: Copy>(knots: &[f64], points: &[T], degree: usize, zero: T, blend: impl Fn(T, T, f64) -> T) -> Option<Vec<Vec<T>>> {
1544 let p = degree;
1545 let n = points.len();
1546 if p == 0 || n < p + 1 || knots.len() != n + p + 1 || knots.iter().any(|k| !k.is_finite()) || knots.windows(2).any(|w| w[1] < w[0]) {
1547 return None;
1548 }
1549 let (a, b) = (knots[0], knots[knots.len() - 1]);
1550 if !(b > a) || knots[..=p].iter().any(|k| *k != a) || knots[knots.len() - p - 1..].iter().any(|k| *k != b) {
1551 return None;
1552 }
1553 let m = knots.len() - 1;
1554 let (mut a, mut b) = (p, p + 1);
1555 let mut spans = Vec::with_capacity(n);
1556 let mut span: Vec<T> = points[..=p].to_vec();
1557 let mut alphas = vec![0.; p];
1558 while b < m {
1559 let first = b;
1560 while b < m && knots[b + 1] == knots[b] {
1561 b += 1;
1562 }
1563 let mult = b - first + 1;
1564 if b < m && mult > p {
1565 return None;
1566 }
1567 let mut next = vec![zero; p + 1];
1568 if mult < p {
1569 let numer = knots[b] - knots[a];
1570 for j in (mult + 1..=p).rev() {
1571 alphas[j - mult - 1] = numer / (knots[a + j] - knots[a]);
1572 }
1573 let r = p - mult;
1574 for j in 1..=r {
1575 let s = mult + j;
1576 for k in (s..=p).rev() {
1577 let alpha = alphas[k - s];
1578 span[k] = blend(span[k], span[k - 1], alpha);
1579 }
1580 if b < m {
1581 next[r - j] = span[p];
1582 }
1583 }
1584 }
1585 spans.push(span);
1586 if b >= m {
1587 break;
1588 }
1589 for i in p - mult..=p {
1590 next[i] = points[b - p + i];
1591 }
1592 span = next;
1593 a = b;
1594 b += 1;
1595 }
1596 Some(spans)
1597}
1598
1599pub(crate) fn bezier_decompose(curve: &NurbsCurve) -> Option<Vec<Vec<Vec4>>> {
1602 bezier_decompose_raw(&curve.knots, &curve.control_points, curve.degree,
1603 Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 },
1604 |a, b, alpha| a.scale(alpha).add(b.scale(1.0 - alpha)))
1605}
1606
1607pub(crate) fn boehm_insert_raw<T: Copy>(
1610 points: &[T], knots: &[f64], degree: usize, parameter: f64, span: usize,
1611 blend: impl Fn(T, T, f64, f64) -> T,
1612) -> (Vec<T>, Vec<f64>) {
1613 let mut out = Vec::with_capacity(points.len() + 1);
1614 out.extend_from_slice(&points[..=span - degree]);
1615 for i in span - degree + 1..=span {
1616 out.push(blend(points[i - 1], points[i], parameter - knots[i], knots[i + degree] - knots[i]));
1617 }
1618 out.extend_from_slice(&points[span..]);
1619 let mut fresh_knots = knots.to_vec();
1620 fresh_knots.insert(span + 1, parameter);
1621 (out, fresh_knots)
1622}
1623
1624
1625pub(crate) fn basis_functions_raw(knots: &[f64], degree: usize, span: usize, parameter: f64, guard: f64) -> Vec<f64> {
1627 let mut basis = vec![0.0; degree + 1];
1628 let mut left = vec![0.0; degree + 1];
1629 let mut right = vec![0.0; degree + 1];
1630 basis_functions_raw_into(knots, degree, span, parameter, guard, &mut basis, &mut left, &mut right);
1631 basis
1632}
1633
1634#[inline]
1635pub(crate) fn basis_functions_raw_into(knots: &[f64], degree: usize, span: usize, parameter: f64,
1636 guard: f64, basis: &mut [f64], left: &mut [f64], right: &mut [f64]) {
1637 let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::BASIS);
1638 basis[0] = 1.0;
1639 for j in 1..=degree {
1640 left[j] = parameter - knots[span + 1 - j];
1641 right[j] = knots[span + j] - parameter;
1642 let mut saved = 0.0;
1643 for r in 0..j {
1644 let denominator = right[r + 1] + left[j - r];
1645 let temporary = if denominator.abs() <= guard {
1646 0.0
1647 } else {
1648 basis[r] / denominator
1649 };
1650 basis[r] = saved + right[r + 1] * temporary;
1651 saved = left[j - r] * temporary;
1652 }
1653 basis[j] = saved;
1654 }
1655}