1use crate::Vec3;
2use serde::{Deserialize, Serialize};
3
4const EPS: f64 = 1e-12;
5
6pub const KNOT_IDENTITY_TOL: f64 = 1e-9;
13
14pub const KNOT_DEDUP_EPS: f64 = 1e-12;
21
22#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
23pub struct Vec4 {
24 pub x: f64,
25 pub y: f64,
26 pub z: f64,
27 pub w: f64,
28}
29
30impl Vec4 {
31 pub fn from_point(point: Vec3, weight: f64) -> Self {
32 Self {
33 x: point.x * weight,
34 y: point.y * weight,
35 z: point.z * weight,
36 w: weight,
37 }
38 }
39
40 pub(crate) fn add(self, rhs: Self) -> Self {
41 Self {
42 x: self.x + rhs.x,
43 y: self.y + rhs.y,
44 z: self.z + rhs.z,
45 w: self.w + rhs.w,
46 }
47 }
48
49 pub(crate) fn scale(self, factor: f64) -> Self {
50 Self {
51 x: self.x * factor,
52 y: self.y * factor,
53 z: self.z * factor,
54 w: self.w * factor,
55 }
56 }
57
58 pub(crate) fn point(self) -> Result<Vec3, String> {
59 if self.w.abs() <= EPS {
60 return Err("cannot project a homogeneous point with zero weight".into());
61 }
62 Ok(Vec3::new(self.x / self.w, self.y / self.w, self.z / self.w))
63 }
64}
65
66pub(crate) const MAX_STACK_DEGREE: usize = 7;
70pub(crate) const MAX_STACK_ORDER: usize = MAX_STACK_DEGREE + 1;
71
72pub(crate) fn validate_knots(knots: &[f64], degree: usize) -> Result<(), String> {
76 if degree < 1 {
77 return Err("KnotVector: degree must be >= 1".into());
78 }
79 if knots.len() < 2 * (degree + 1) {
80 return Err(format!(
81 "KnotVector: need at least {} knots for degree {}, got {}",
82 2 * (degree + 1),
83 degree,
84 knots.len()
85 ));
86 }
87 if knots.iter().any(|value| !value.is_finite()) {
88 return Err("KnotVector: knots must be finite".into());
89 }
90 if knots
91 .windows(2)
92 .any(|pair| pair[1] < pair[0] - KNOT_IDENTITY_TOL)
93 {
94 return Err("KnotVector: knots must be non-decreasing".into());
95 }
96 let first = knots[0];
97 let last = knots[knots.len() - 1];
98 for index in 0..=degree {
99 if (knots[index] - first).abs() > KNOT_IDENTITY_TOL {
100 return Err("KnotVector: expected clamped start".into());
101 }
102 if (knots[knots.len() - 1 - index] - last).abs() > KNOT_IDENTITY_TOL {
103 return Err("KnotVector: expected clamped end".into());
104 }
105 }
106 if last - first <= KNOT_IDENTITY_TOL {
107 return Err("KnotVector: degenerate parameter range".into());
108 }
109 Ok(())
110}
111
112pub(crate) fn knot_domain(knots: &[f64], degree: usize) -> [f64; 2] {
113 [knots[degree], knots[knots.len() - 1 - degree]]
114}
115
116pub(crate) fn knot_clamp(knots: &[f64], degree: usize, parameter: f64) -> f64 {
117 let [start, end] = knot_domain(knots, degree);
118 parameter.clamp(start, end)
119}
120
121pub(crate) fn knot_find_span(knots: &[f64], degree: usize, parameter: f64) -> usize {
122 let parameter = knot_clamp(knots, degree, parameter);
123 let n = knots.len() - degree - 2;
124 if parameter >= knots[n + 1] {
125 let mut span = n;
132 while span > degree && knots[span] >= knots[span + 1] {
133 span -= 1;
134 }
135 return span;
136 }
137 if parameter <= knots[degree] {
138 let mut span = degree;
145 while span < n && knots[span + 1] <= parameter {
146 span += 1;
147 }
148 return span;
149 }
150 let mut low = degree;
151 let mut high = n + 1;
152 let mut middle = (low + high) / 2;
153 while parameter < knots[middle] || parameter >= knots[middle + 1] {
154 if parameter < knots[middle] {
155 high = middle;
156 } else {
157 low = middle;
158 }
159 middle = (low + high) / 2;
160 }
161 middle
162}
163
164pub(crate) fn basis_functions_into(
167 knots: &[f64],
168 degree: usize,
169 span: usize,
170 parameter: f64,
171 basis: &mut [f64; MAX_STACK_ORDER],
172) {
173 debug_assert!(degree <= MAX_STACK_DEGREE);
174 let mut left = [0.0f64; MAX_STACK_ORDER];
175 let mut right = [0.0f64; MAX_STACK_ORDER];
176 basis[0] = 1.0;
177 for j in 1..=degree {
178 left[j] = parameter - knots[span + 1 - j];
179 right[j] = knots[span + j] - parameter;
180 let mut saved = 0.0;
181 for r in 0..j {
182 let denominator = right[r + 1] + left[j - r];
183 let temporary = if denominator.abs() <= EPS {
184 0.0
185 } else {
186 basis[r] / denominator
187 };
188 basis[r] = saved + right[r + 1] * temporary;
189 saved = left[j - r] * temporary;
190 }
191 basis[j] = saved;
192 }
193}
194
195pub(crate) fn basis_derivatives_into(
201 knots: &[f64],
202 degree: usize,
203 span: usize,
204 parameter: f64,
205 derivative_count: usize,
206 out: &mut [[f64; MAX_STACK_ORDER]],
207) {
208 debug_assert!(degree <= MAX_STACK_DEGREE);
209 debug_assert!(out.len() > derivative_count);
210 let p = degree;
211 let n = derivative_count.min(p);
212 let mut ndu = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
213 let mut left = [0.0f64; MAX_STACK_ORDER];
214 let mut right = [0.0f64; MAX_STACK_ORDER];
215 ndu[0][0] = 1.0;
216
217 for j in 1..=p {
218 left[j] = parameter - knots[span + 1 - j];
219 right[j] = knots[span + j] - parameter;
220 let mut saved = 0.0;
221 for r in 0..j {
222 ndu[j][r] = right[r + 1] + left[j - r];
223 let temporary = if ndu[j][r].abs() <= EPS {
224 0.0
225 } else {
226 ndu[r][j - 1] / ndu[j][r]
227 };
228 ndu[r][j] = saved + right[r + 1] * temporary;
229 saved = left[j - r] * temporary;
230 }
231 ndu[j][j] = saved;
232 }
233
234 for j in 0..=p {
235 out[0][j] = ndu[j][p];
236 }
237 let mut a = [[0.0f64; MAX_STACK_ORDER]; 2];
238 for r in 0..=p {
239 let mut s1 = 0;
240 let mut s2 = 1;
241 a[0][0] = 1.0;
242 for k in 1..=n {
243 a[s2] = [0.0; MAX_STACK_ORDER];
244 let mut value = 0.0;
245 let rk = r as isize - k as isize;
246 let pk = p - k;
247 if r >= k {
248 let denominator = ndu[pk + 1][rk as usize];
249 a[s2][0] = a[s1][0] / denominator;
250 value = a[s2][0] * ndu[rk as usize][pk];
251 }
252 let j1 = if rk >= -1 { 1 } else { (-rk) as usize };
253 let j2 = if r <= pk + 1 { k - 1 } else { p - r };
254 if j1 <= j2 {
255 for j in j1..=j2 {
256 let index = (rk + j as isize) as usize;
257 a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][index];
258 value += a[s2][j] * ndu[index][pk];
259 }
260 }
261 if r <= pk {
262 a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r];
263 value += a[s2][k] * ndu[r][pk];
264 }
265 out[k][r] = value;
266 std::mem::swap(&mut s1, &mut s2);
267 }
268 }
269 let mut factor = p as f64;
270 for k in 1..=n {
271 for value in out[k][..=p].iter_mut() {
272 *value *= factor;
273 }
274 factor *= (p - k) as f64;
275 }
276}
277
278#[derive(Clone, Debug, Deserialize, Serialize)]
279pub struct KnotVector {
280 pub knots: Vec<f64>,
281 pub degree: usize,
282}
283
284impl KnotVector {
285 pub fn new(knots: Vec<f64>, degree: usize) -> Result<Self, String> {
286 validate_knots(&knots, degree)?;
287 Ok(Self { knots, degree })
288 }
289
290 pub fn control_point_count(&self) -> usize {
291 self.knots.len() - self.degree - 1
292 }
293
294 pub fn domain(&self) -> [f64; 2] {
295 [
296 self.knots[self.degree],
297 self.knots[self.knots.len() - 1 - self.degree],
298 ]
299 }
300
301 pub fn clamp_param(&self, parameter: f64) -> f64 {
302 knot_clamp(&self.knots, self.degree, parameter)
303 }
304
305 pub fn find_span(&self, parameter: f64) -> usize {
306 knot_find_span(&self.knots, self.degree, parameter)
307 }
308
309 pub fn basis_functions(&self, span: usize, parameter: f64) -> Vec<f64> {
310 if self.degree <= MAX_STACK_DEGREE {
311 let mut basis = [0.0f64; MAX_STACK_ORDER];
312 basis_functions_into(&self.knots, self.degree, span, parameter, &mut basis);
313 return basis[..=self.degree].to_vec();
314 }
315 let mut basis = vec![0.0; self.degree + 1];
316 let mut left = vec![0.0; self.degree + 1];
317 let mut right = vec![0.0; self.degree + 1];
318 basis[0] = 1.0;
319 for j in 1..=self.degree {
320 left[j] = parameter - self.knots[span + 1 - j];
321 right[j] = self.knots[span + j] - parameter;
322 let mut saved = 0.0;
323 for r in 0..j {
324 let denominator = right[r + 1] + left[j - r];
325 let temporary = if denominator.abs() <= EPS {
326 0.0
327 } else {
328 basis[r] / denominator
329 };
330 basis[r] = saved + right[r + 1] * temporary;
331 saved = left[j - r] * temporary;
332 }
333 basis[j] = saved;
334 }
335 basis
336 }
337
338 pub fn basis_derivatives(
339 &self,
340 span: usize,
341 parameter: f64,
342 derivative_count: usize,
343 ) -> Vec<Vec<f64>> {
344 if self.degree <= MAX_STACK_DEGREE && derivative_count <= MAX_STACK_DEGREE {
345 let mut rows = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
346 basis_derivatives_into(
347 &self.knots,
348 self.degree,
349 span,
350 parameter,
351 derivative_count,
352 &mut rows[..=derivative_count],
353 );
354 return rows[..=derivative_count]
355 .iter()
356 .map(|row| row[..=self.degree].to_vec())
357 .collect();
358 }
359 let p = self.degree;
360 let n = derivative_count.min(p);
361 let mut ndu = vec![vec![0.0; p + 1]; p + 1];
362 let mut left = vec![0.0; p + 1];
363 let mut right = vec![0.0; p + 1];
364 ndu[0][0] = 1.0;
365
366 for j in 1..=p {
367 left[j] = parameter - self.knots[span + 1 - j];
368 right[j] = self.knots[span + j] - parameter;
369 let mut saved = 0.0;
370 for r in 0..j {
371 ndu[j][r] = right[r + 1] + left[j - r];
372 let temporary = if ndu[j][r].abs() <= EPS {
373 0.0
374 } else {
375 ndu[r][j - 1] / ndu[j][r]
376 };
377 ndu[r][j] = saved + right[r + 1] * temporary;
378 saved = left[j - r] * temporary;
379 }
380 ndu[j][j] = saved;
381 }
382
383 let mut derivatives = vec![vec![0.0; p + 1]; derivative_count + 1];
384 for j in 0..=p {
385 derivatives[0][j] = ndu[j][p];
386 }
387 let mut a = vec![vec![0.0; p + 1]; 2];
388 for r in 0..=p {
389 let mut s1 = 0;
390 let mut s2 = 1;
391 a[0][0] = 1.0;
392 for k in 1..=n {
393 a[s2].fill(0.0);
394 let mut value = 0.0;
395 let rk = r as isize - k as isize;
396 let pk = p - k;
397 if r >= k {
398 let denominator = ndu[pk + 1][rk as usize];
399 a[s2][0] = a[s1][0] / denominator;
400 value = a[s2][0] * ndu[rk as usize][pk];
401 }
402 let j1 = if rk >= -1 { 1 } else { (-rk) as usize };
403 let j2 = if r <= pk + 1 { k - 1 } else { p - r };
404 if j1 <= j2 {
405 for j in j1..=j2 {
406 let index = (rk + j as isize) as usize;
407 a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][index];
408 value += a[s2][j] * ndu[index][pk];
409 }
410 }
411 if r <= pk {
412 a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r];
413 value += a[s2][k] * ndu[r][pk];
414 }
415 derivatives[k][r] = value;
416 std::mem::swap(&mut s1, &mut s2);
417 }
418 }
419 let mut factor = p as f64;
420 for (k, row) in derivatives.iter_mut().enumerate().take(n + 1).skip(1) {
421 for value in row {
422 *value *= factor;
423 }
424 factor *= (p - k) as f64;
425 }
426 derivatives
427 }
428}
429
430#[derive(Clone, Debug, Deserialize, Serialize)]
431pub struct NurbsCurve {
432 pub degree: usize,
433 pub knots: Vec<f64>,
434 pub control_points: Vec<Vec4>,
435 #[serde(skip, default)]
439 validated: std::cell::Cell<bool>,
440}
441
442impl NurbsCurve {
443 pub fn new(degree: usize, knots: Vec<f64>, control_points: Vec<Vec4>) -> Result<Self, String> {
444 let curve = Self {
445 degree,
446 knots,
447 control_points,
448 validated: std::cell::Cell::new(false),
449 };
450 curve.ensure_valid()?;
451 Ok(curve)
452 }
453
454 fn ensure_valid(&self) -> Result<(), String> {
456 if self.validated.get() {
457 return Ok(());
458 }
459 validate_knots(&self.knots, self.degree)?;
460 let expected = self.knots.len() - self.degree - 1;
461 if self.control_points.len() != expected {
462 return Err(format!(
463 "NurbsCurve: knot vector implies {} control points, got {}",
464 expected,
465 self.control_points.len()
466 ));
467 }
468 if self.control_points.iter().any(|point| {
469 point.w <= EPS
470 || ![point.x, point.y, point.z, point.w]
471 .iter()
472 .all(|value| value.is_finite())
473 }) {
474 return Err("NurbsCurve: control points must be finite with positive weights".into());
475 }
476 self.validated.set(true);
477 Ok(())
478 }
479
480 fn knot_vector(&self) -> Result<KnotVector, String> {
481 KnotVector::new(self.knots.clone(), self.degree)
482 }
483
484 pub fn domain(&self) -> Result<[f64; 2], String> {
485 self.ensure_valid()?;
486 Ok(knot_domain(&self.knots, self.degree))
487 }
488
489 pub fn evaluate_homogeneous(&self, parameter: f64) -> Result<Vec4, String> {
490 self.ensure_valid()?;
491 let span = knot_find_span(&self.knots, self.degree, parameter);
492 let parameter = knot_clamp(&self.knots, self.degree, parameter);
493 let mut point = Vec4 {
494 x: 0.0,
495 y: 0.0,
496 z: 0.0,
497 w: 0.0,
498 };
499 if self.degree <= MAX_STACK_DEGREE {
500 let mut basis = [0.0f64; MAX_STACK_ORDER];
501 basis_functions_into(&self.knots, self.degree, span, parameter, &mut basis);
502 for (index, value) in basis[..=self.degree].iter().enumerate() {
503 point = point.add(self.control_points[span - self.degree + index].scale(*value));
504 }
505 } else {
506 let knot_vector = self.knot_vector()?;
507 let basis = knot_vector.basis_functions(span, parameter);
508 for (index, value) in basis.iter().enumerate() {
509 point = point.add(self.control_points[span - self.degree + index].scale(*value));
510 }
511 }
512 Ok(point)
513 }
514
515 pub fn evaluate(&self, parameter: f64) -> Result<Vec3, String> {
516 self.evaluate_homogeneous(parameter)?.point()
517 }
518
519 pub fn evaluate_extended(&self, parameter: f64) -> Result<Vec3, String> {
524 Ok(self.derivatives_extended(parameter, 0)?[0])
525 }
526
527 pub fn derivatives_extended(
531 &self,
532 parameter: f64,
533 derivative_count: usize,
534 ) -> Result<Vec<Vec3>, String> {
535 let [start, end] = self.domain()?;
536 if parameter >= start && parameter <= end {
537 return self.derivatives(parameter, derivative_count);
538 }
539 let period = end - start;
540 if period > 0.0 {
541 let closed =
542 self.evaluate(start)?.sub(self.evaluate(end)?).length() <= 1e-9 * (1.0 + period);
543 if closed {
544 let wrapped = start + (parameter - start).rem_euclid(period);
545 return self.derivatives(wrapped, derivative_count);
546 }
547 }
548 let boundary = if parameter < start { start } else { end };
549 let base = self.derivatives(boundary, derivative_count.max(1))?;
550 let mut result = Vec::with_capacity(derivative_count + 1);
551 result.push(base[0].add(base[1].scale(parameter - boundary)));
552 if derivative_count >= 1 {
553 result.push(base[1]);
554 }
555 for _ in 2..=derivative_count {
556 result.push(Vec3::default());
557 }
558 Ok(result)
559 }
560
561 pub fn derivatives(
562 &self,
563 parameter: f64,
564 derivative_count: usize,
565 ) -> Result<Vec<Vec3>, String> {
566 self.ensure_valid()?;
567 let parameter = knot_clamp(&self.knots, self.degree, parameter);
568 let span = knot_find_span(&self.knots, self.degree, parameter);
569 let calculated_count = derivative_count.min(self.degree);
570 let mut homogeneous: Vec<Vec4> = Vec::with_capacity(calculated_count + 1);
571 if self.degree <= MAX_STACK_DEGREE {
572 let mut rows = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
573 basis_derivatives_into(
574 &self.knots,
575 self.degree,
576 span,
577 parameter,
578 calculated_count,
579 &mut rows[..=calculated_count],
580 );
581 for row in rows.iter().take(calculated_count + 1) {
582 let mut point = Vec4 {
583 x: 0.0,
584 y: 0.0,
585 z: 0.0,
586 w: 0.0,
587 };
588 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
589 point =
590 point.add(self.control_points[span - self.degree + index].scale(*value));
591 }
592 homogeneous.push(point);
593 }
594 } else {
595 let knot_vector = self.knot_vector()?;
596 let basis = knot_vector.basis_derivatives(span, parameter, calculated_count);
597 for row in basis.iter().take(calculated_count + 1) {
598 let mut point = Vec4 {
599 x: 0.0,
600 y: 0.0,
601 z: 0.0,
602 w: 0.0,
603 };
604 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
605 point =
606 point.add(self.control_points[span - self.degree + index].scale(*value));
607 }
608 homogeneous.push(point);
609 }
610 }
611
612 let mut result: Vec<Vec3> = Vec::with_capacity(derivative_count + 1);
613 for k in 0..=calculated_count {
614 let mut value = Vec3::new(homogeneous[k].x, homogeneous[k].y, homogeneous[k].z);
615 for i in 1..=k {
616 value = value.sub(result[k - i].scale(binomial(k, i) * homogeneous[i].w));
617 }
618 result.push(value.scale(1.0 / homogeneous[0].w));
619 }
620 result.resize(derivative_count + 1, Vec3::default());
621 Ok(result)
622 }
623
624 pub(crate) fn derivatives_small(
635 &self,
636 parameter: f64,
637 derivative_count: usize,
638 ) -> Result<[Vec3; 3], String> {
639 debug_assert!(derivative_count <= 2);
640 self.ensure_valid()?;
641 let parameter = knot_clamp(&self.knots, self.degree, parameter);
642 let span = knot_find_span(&self.knots, self.degree, parameter);
643 let calculated_count = derivative_count.min(self.degree);
644 let zero = Vec4 {
645 x: 0.0,
646 y: 0.0,
647 z: 0.0,
648 w: 0.0,
649 };
650 let mut homogeneous = [zero; 3];
651 if self.degree <= MAX_STACK_DEGREE {
652 let mut rows = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
653 basis_derivatives_into(
654 &self.knots,
655 self.degree,
656 span,
657 parameter,
658 calculated_count,
659 &mut rows[..=calculated_count],
660 );
661 for (k, row) in rows.iter().take(calculated_count + 1).enumerate() {
662 let mut point = zero;
663 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
664 point =
665 point.add(self.control_points[span - self.degree + index].scale(*value));
666 }
667 homogeneous[k] = point;
668 }
669 } else {
670 let knot_vector = self.knot_vector()?;
671 let basis = knot_vector.basis_derivatives(span, parameter, calculated_count);
672 for (k, row) in basis.iter().take(calculated_count + 1).enumerate() {
673 let mut point = zero;
674 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
675 point =
676 point.add(self.control_points[span - self.degree + index].scale(*value));
677 }
678 homogeneous[k] = point;
679 }
680 }
681
682 let mut result = [Vec3::default(); 3];
683 for k in 0..=calculated_count {
684 let mut value = Vec3::new(homogeneous[k].x, homogeneous[k].y, homogeneous[k].z);
685 for i in 1..=k {
686 value = value.sub(result[k - i].scale(binomial(k, i) * homogeneous[i].w));
687 }
688 result[k] = value.scale(1.0 / homogeneous[0].w);
689 }
690 Ok(result)
691 }
692
693 #[inline]
696 pub(crate) fn deriv1(&self, parameter: f64) -> Result<(Vec3, Vec3), String> {
697 let d = self.derivatives_small(parameter, 1)?;
698 Ok((d[0], d[1]))
699 }
700
701 pub fn reversed(&self) -> Result<Self, String> {
702 let start = self.knots[0];
703 let end = self.knots[self.knots.len() - 1];
704 let knots = self
705 .knots
706 .iter()
707 .rev()
708 .map(|knot| start + end - knot)
709 .collect();
710 let control_points = self.control_points.iter().rev().copied().collect();
711 Self::new(self.degree, knots, control_points)
712 }
713
714 pub fn insert_knot(&self, parameter: f64, requested: usize) -> Result<Self, String> {
715 let knot_vector = self.knot_vector()?;
716 let degree = self.degree;
717 let parameter = knot_vector.clamp_param(parameter);
718 let parameter = self
723 .knots
724 .iter()
725 .copied()
726 .find(|knot| (knot - parameter).abs() <= KNOT_IDENTITY_TOL)
727 .unwrap_or(parameter);
728 let multiplicity = self
729 .knots
730 .iter()
731 .filter(|knot| (**knot - parameter).abs() <= KNOT_IDENTITY_TOL)
732 .count();
733 let insertion_count = requested.min(degree.saturating_sub(multiplicity));
734 if insertion_count == 0 {
735 return Ok(self.clone());
736 }
737 let span = knot_vector.find_span(parameter);
738 let last_control = self.control_points.len() - 1;
739 let mut knots = Vec::with_capacity(self.knots.len() + insertion_count);
740 knots.extend_from_slice(&self.knots[..=span]);
741 knots.extend(std::iter::repeat_n(parameter, insertion_count));
742 knots.extend_from_slice(&self.knots[span + 1..]);
743
744 let mut output = vec![
745 Vec4 {
746 x: 0.0,
747 y: 0.0,
748 z: 0.0,
749 w: 1.0,
750 };
751 last_control + 1 + insertion_count
752 ];
753 output[..=span - degree].copy_from_slice(&self.control_points[..=span - degree]);
754 for index in span - multiplicity..=last_control {
755 output[index + insertion_count] = self.control_points[index];
756 }
757 let mut affected = vec![
758 Vec4 {
759 x: 0.0,
760 y: 0.0,
761 z: 0.0,
762 w: 1.0,
763 };
764 degree + 1
765 ];
766 affected[..=degree - multiplicity]
767 .copy_from_slice(&self.control_points[span - degree..=span - multiplicity]);
768 let mut left = 0;
769 for insertion in 1..=insertion_count {
770 left = span - degree + insertion;
771 for index in 0..=degree - insertion - multiplicity {
772 let denominator = self.knots[index + span + 1] - self.knots[left + index];
773 let alpha = (parameter - self.knots[left + index]) / denominator;
774 affected[index] = affected[index + 1]
775 .scale(alpha)
776 .add(affected[index].scale(1.0 - alpha));
777 }
778 output[left] = affected[0];
779 output[span + insertion_count - insertion - multiplicity] =
780 affected[degree - insertion - multiplicity];
781 }
782 for index in left + 1..span - multiplicity {
783 output[index] = affected[index - left];
784 }
785 Self::new(degree, knots, output)
786 }
787
788 pub fn split(&self, parameter: f64) -> Result<(Self, Self), String> {
789 let [start, end] = self.domain()?;
790 if parameter <= start + KNOT_IDENTITY_TOL || parameter >= end - KNOT_IDENTITY_TOL {
791 return Err(format!(
792 "NurbsCurve.split: parameter {parameter} must be strictly inside domain [{start}, {end}]"
793 ));
794 }
795 let parameter = self
798 .knots
799 .iter()
800 .copied()
801 .find(|knot| (knot - parameter).abs() <= KNOT_IDENTITY_TOL)
802 .unwrap_or(parameter);
803 let multiplicity = self
804 .knots
805 .iter()
806 .filter(|knot| (**knot - parameter).abs() <= KNOT_IDENTITY_TOL)
807 .count();
808 let refined = self.insert_knot(parameter, self.degree.saturating_sub(multiplicity))?;
809 let first = refined
810 .knots
811 .iter()
812 .position(|knot| (*knot - parameter).abs() <= KNOT_IDENTITY_TOL)
813 .ok_or_else(|| "NurbsCurve.split: inserted knot not found".to_string())?;
814 let mut left_knots = refined.knots[..first + self.degree].to_vec();
815 left_knots.push(parameter);
816 let left_points = refined.control_points[..first].to_vec();
817 let mut right_knots = vec![parameter; self.degree + 1];
818 right_knots.extend_from_slice(&refined.knots[first + self.degree..]);
819 let right_points = refined.control_points[first - 1..].to_vec();
820 Ok((
821 Self::new(self.degree, left_knots, left_points)?,
822 Self::new(self.degree, right_knots, right_points)?,
823 ))
824 }
825}
826
827pub fn make_line(start: Vec3, end: Vec3) -> Result<NurbsCurve, String> {
828 NurbsCurve::new(
829 1,
830 vec![0.0, 0.0, 1.0, 1.0],
831 vec![Vec4::from_point(start, 1.0), Vec4::from_point(end, 1.0)],
832 )
833}
834
835pub fn make_arc(
836 center: Vec3,
837 x_axis: Vec3,
838 y_axis: Vec3,
839 radius: f64,
840 start_angle: f64,
841 end_angle: f64,
842) -> Result<NurbsCurve, String> {
843 if radius <= EPS {
844 return Err("makeArc: radius must be positive".into());
845 }
846 let x_axis = x_axis.normalized()?;
847 let y_axis = y_axis.normalized()?;
848 if x_axis.dot(y_axis).abs() > 1e-9 {
849 return Err("makeArc: xAxis and yAxis must be orthogonal".into());
850 }
851 let mut theta = end_angle - start_angle;
852 if theta <= EPS {
853 return Err("makeArc: endAngle must exceed startAngle".into());
854 }
855 if theta > std::f64::consts::TAU + EPS {
856 return Err("makeArc: sweep exceeds full circle".into());
857 }
858 theta = theta.min(std::f64::consts::TAU);
859 let segment_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
860 let segment_angle = theta / segment_count as f64;
861 let middle_weight = (segment_angle / 2.0).cos();
862 let point_at = |angle: f64| {
863 center
864 .add(x_axis.scale(radius * angle.cos()))
865 .add(y_axis.scale(radius * angle.sin()))
866 };
867 let tangent_at = |angle: f64| x_axis.scale(-angle.sin()).add(y_axis.scale(angle.cos()));
868
869 let mut points = Vec::with_capacity(2 * segment_count + 1);
870 let mut angle = start_angle;
871 let mut first_point = point_at(angle);
872 let mut first_tangent = tangent_at(angle);
873 points.push(Vec4::from_point(first_point, 1.0));
874 for _ in 0..segment_count {
875 angle += segment_angle;
876 let end_point = point_at(angle);
877 let end_tangent = tangent_at(angle);
878 let cross = first_tangent.cross(end_tangent);
879 let denominator = cross.length_squared();
880 if denominator <= EPS {
881 return Err("makeArc: arc tangents are parallel".into());
882 }
883 let distance = end_point.sub(first_point).cross(end_tangent).dot(cross) / denominator;
884 let middle = first_point.add(first_tangent.scale(distance));
885 points.push(Vec4::from_point(middle, middle_weight));
886 points.push(Vec4::from_point(end_point, 1.0));
887 first_point = end_point;
888 first_tangent = end_tangent;
889 }
890 let mut knots = vec![0.0, 0.0, 0.0];
891 for index in 1..segment_count {
892 let knot = index as f64 / segment_count as f64;
893 knots.extend([knot, knot]);
894 }
895 knots.extend([1.0, 1.0, 1.0]);
896 NurbsCurve::new(2, knots, points)
897}
898
899pub fn make_circle(center: Vec3, normal: Vec3, radius: f64) -> Result<NurbsCurve, String> {
900 let normal = normal.normalized()?;
901 let x_axis = normal.perpendicular()?;
902 let y_axis = normal.cross(x_axis).normalized()?;
903 make_arc(center, x_axis, y_axis, radius, 0.0, std::f64::consts::TAU)
904}
905
906fn conic_frame(fn_name: &str, primary: Vec3, hint: Vec3) -> Result<(Vec3, Vec3), String> {
912 let x_axis = primary
913 .normalized()
914 .map_err(|_| format!("{fn_name}: primary axis must be non-zero"))?;
915 if hint.length() <= EPS {
916 return Err(format!("{fn_name}: in-plane direction must be non-zero"));
917 }
918 hint.sub(x_axis.scale(hint.dot(x_axis)))
919 .normalized()
920 .map(|y_axis| (x_axis, y_axis))
921 .map_err(|_| format!("{fn_name}: frame directions must not be parallel"))
922}
923
924fn conic_range(fn_name: &str, t0: f64, t1: f64) -> Result<(), String> {
928 if !t0.is_finite() || !t1.is_finite() {
929 return Err(format!("{fn_name}: parameter range must be finite"));
930 }
931 if t1 - t0 <= KNOT_IDENTITY_TOL {
932 return Err(format!("{fn_name}: t1 ({t1}) must exceed t0 ({t0})"));
933 }
934 Ok(())
935}
936
937fn conic_points_finite(fn_name: &str, points: &[Vec4]) -> Result<(), String> {
942 if points.iter().any(|point| {
943 ![point.x, point.y, point.z, point.w]
944 .iter()
945 .all(|value| value.is_finite())
946 }) {
947 return Err(format!(
948 "{fn_name}: control points overflow f64 — parameter range too extreme"
949 ));
950 }
951 Ok(())
952}
953
954pub fn make_parabola(
962 vertex: Vec3,
963 axis: Vec3,
964 latus_direction: Vec3,
965 focal: f64,
966 t0: f64,
967 t1: f64,
968) -> Result<NurbsCurve, String> {
969 if !focal.is_finite() || focal <= EPS {
970 return Err("make_parabola: focal distance must be positive".into());
971 }
972 conic_range("make_parabola", t0, t1)?;
973 let (x_axis, y_axis) = conic_frame("make_parabola", axis, latus_direction)?;
974 let point_at = |t: f64| {
975 vertex
976 .add(x_axis.scale(focal * t * t))
977 .add(y_axis.scale(2.0 * focal * t))
978 };
979 let middle = vertex
984 .add(x_axis.scale(focal * t0 * t1))
985 .add(y_axis.scale(focal * (t0 + t1)));
986 let control_points = vec![
987 Vec4::from_point(point_at(t0), 1.0),
988 Vec4::from_point(middle, 1.0),
989 Vec4::from_point(point_at(t1), 1.0),
990 ];
991 conic_points_finite("make_parabola", &control_points)?;
992 NurbsCurve::new(2, vec![t0, t0, t0, t1, t1, t1], control_points)
993}
994
995pub fn make_hyperbola(
1005 center: Vec3,
1006 major_axis: Vec3,
1007 minor_axis: Vec3,
1008 a: f64,
1009 b: f64,
1010 t0: f64,
1011 t1: f64,
1012) -> Result<NurbsCurve, String> {
1013 if !a.is_finite() || a <= EPS {
1014 return Err("make_hyperbola: semi-axis a must be positive".into());
1015 }
1016 if !b.is_finite() || b <= EPS {
1017 return Err("make_hyperbola: semi-axis b must be positive".into());
1018 }
1019 conic_range("make_hyperbola", t0, t1)?;
1020 let (x_axis, y_axis) = conic_frame("make_hyperbola", major_axis, minor_axis)?;
1021 let point_at = |t: f64| {
1022 center
1023 .add(x_axis.scale(a * t.cosh()))
1024 .add(y_axis.scale(b * t.sinh()))
1025 };
1026 let mid = 0.5 * (t0 + t1);
1027 let middle_weight = (0.5 * (t1 - t0)).cosh();
1028 let apex = center
1035 .add(x_axis.scale(a * mid.cosh() / middle_weight))
1036 .add(y_axis.scale(b * mid.sinh() / middle_weight));
1037 let control_points = vec![
1038 Vec4::from_point(point_at(t0), 1.0),
1039 Vec4::from_point(apex, middle_weight),
1040 Vec4::from_point(point_at(t1), 1.0),
1041 ];
1042 conic_points_finite("make_hyperbola", &control_points)?;
1043 NurbsCurve::new(2, vec![t0, t0, t0, t1, t1, t1], control_points)
1044}
1045
1046pub fn uniform_clamped_knots(
1047 control_point_count: usize,
1048 degree: usize,
1049) -> Result<Vec<f64>, String> {
1050 if control_point_count == 0 || control_point_count - 1 < degree {
1051 return Err("uniformClampedKnots: need at least degree+1 control points".into());
1052 }
1053 let n = control_point_count - 1;
1054 let mut knots = vec![0.0; degree + 1];
1055 let interior = n - degree;
1056 for index in 1..=interior {
1057 knots.push(index as f64 / (interior + 1) as f64);
1058 }
1059 knots.extend(std::iter::repeat_n(1.0, degree + 1));
1060 Ok(knots)
1061}
1062
1063fn binomial(n: usize, k: usize) -> f64 {
1064 if k > n {
1065 return 0.0;
1066 }
1067 let k = k.min(n - k);
1068 (1..=k).fold(1.0, |value, index| {
1069 value * (n - k + index) as f64 / index as f64
1070 })
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::*;
1076
1077 fn close(a: Vec3, b: Vec3, tolerance: f64) {
1078 assert!(a.sub(b).length() <= tolerance, "{a:?} != {b:?}");
1079 }
1080
1081 #[test]
1082 fn line_evaluation_and_derivative_are_exact() {
1083 let curve = make_line(Vec3::new(1.0, 2.0, 3.0), Vec3::new(5.0, 8.0, 11.0)).unwrap();
1084 close(
1085 curve.evaluate(0.25).unwrap(),
1086 Vec3::new(2.0, 3.5, 5.0),
1087 1e-13,
1088 );
1089 close(
1090 curve.derivatives(0.75, 2).unwrap()[1],
1091 Vec3::new(4.0, 6.0, 8.0),
1092 1e-13,
1093 );
1094 close(
1095 curve.derivatives(0.75, 2).unwrap()[2],
1096 Vec3::default(),
1097 1e-13,
1098 );
1099 }
1100
1101 #[test]
1102 fn over_clamped_knot_vector_evaluates_without_zero_weight() {
1103 let knots = vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
1111 let control_points = (0..7)
1112 .map(|i| Vec4::from_point(Vec3::new(i as f64, 0.0, 0.0), 1.0))
1113 .collect::<Vec<_>>();
1114 let curve = NurbsCurve::new(3, knots, control_points).unwrap();
1115 close(
1118 curve.evaluate(0.0).unwrap(),
1119 Vec3::new(3.0, 0.0, 0.0),
1120 1e-12,
1121 );
1122 close(
1123 curve.evaluate(1.0).unwrap(),
1124 Vec3::new(6.0, 0.0, 0.0),
1125 1e-12,
1126 );
1127 close(
1128 curve.evaluate(0.5).unwrap(),
1129 Vec3::new(4.5, 0.0, 0.0),
1130 1e-12,
1131 );
1132 }
1133
1134 #[test]
1135 fn rational_circle_stays_on_radius() {
1136 let curve = make_arc(
1137 Vec3::new(3.0, -2.0, 5.0),
1138 Vec3::new(1.0, 0.0, 0.0),
1139 Vec3::new(0.0, 1.0, 0.0),
1140 7.0,
1141 0.0,
1142 std::f64::consts::TAU,
1143 )
1144 .unwrap();
1145 for index in 0..=64 {
1146 let point = curve.evaluate(index as f64 / 64.0).unwrap();
1147 let radial = point.sub(Vec3::new(3.0, -2.0, 5.0));
1148 assert!((radial.length() - 7.0).abs() < 1e-12);
1149 }
1150 }
1151
1152 #[test]
1153 fn quadratic_basis_derivatives_match_finite_difference() {
1154 let curve = make_arc(
1155 Vec3::default(),
1156 Vec3::new(1.0, 0.0, 0.0),
1157 Vec3::new(0.0, 1.0, 0.0),
1158 2.0,
1159 0.2,
1160 2.7,
1161 )
1162 .unwrap();
1163 let parameter = 0.37;
1164 let step = 1e-6;
1165 let finite = curve
1166 .evaluate(parameter + step)
1167 .unwrap()
1168 .sub(curve.evaluate(parameter - step).unwrap())
1169 .scale(0.5 / step);
1170 close(finite, curve.derivatives(parameter, 1).unwrap()[1], 1e-7);
1171 }
1172
1173 #[test]
1174 fn circle_constructor_and_uniform_knots_match_public_contract() {
1175 let circle = make_circle(Vec3::new(1.0, 2.0, 3.0), Vec3::new(0.0, 0.0, 1.0), 4.0).unwrap();
1176 for index in 0..=16 {
1177 assert!(
1178 (circle
1179 .evaluate(index as f64 / 16.0)
1180 .unwrap()
1181 .sub(Vec3::new(1.0, 2.0, 3.0))
1182 .length()
1183 - 4.0)
1184 .abs()
1185 < 1e-12
1186 );
1187 }
1188 assert_eq!(
1189 uniform_clamped_knots(6, 3).unwrap(),
1190 vec![0.0, 0.0, 0.0, 0.0, 1.0 / 3.0, 2.0 / 3.0, 1.0, 1.0, 1.0, 1.0]
1191 );
1192 }
1193
1194 fn orthonormal_frame(primary: Vec3, hint: Vec3) -> (Vec3, Vec3, Vec3) {
1198 let x_axis = primary.normalized().unwrap();
1199 let y_axis = hint
1200 .sub(x_axis.scale(hint.dot(x_axis)))
1201 .normalized()
1202 .unwrap();
1203 (x_axis, y_axis, x_axis.cross(y_axis))
1204 }
1205
1206 #[test]
1207 fn parabola_samples_satisfy_implicit_equation_in_local_frame() {
1208 let vertex = Vec3::new(1.0, -2.0, 3.0);
1209 let axis = Vec3::new(1.0, 1.0, 0.5);
1210 let latus = Vec3::new(-1.0, 2.0, 4.0);
1212 let focal = 1.7;
1213 let (t0, t1) = (-1.2, 2.3);
1214 let curve = make_parabola(vertex, axis, latus, focal, t0, t1).unwrap();
1215 let (x_axis, y_axis, z_axis) = orthonormal_frame(axis, latus);
1216 for index in 0..20 {
1217 let t = t0 + (t1 - t0) * index as f64 / 19.0;
1218 let local = curve.evaluate(t).unwrap().sub(vertex);
1219 let (x, y, z) = (local.dot(x_axis), local.dot(y_axis), local.dot(z_axis));
1220 let residual = y * y - 4.0 * focal * x;
1221 assert!(residual.abs() < 1e-10, "t={t}: residual {residual}");
1222 assert!(z.abs() < 1e-10, "t={t}: out of plane by {z}");
1223 }
1224 }
1225
1226 #[test]
1227 fn hyperbola_samples_satisfy_implicit_equation_in_local_frame() {
1228 let center = Vec3::new(-2.0, 1.0, 4.0);
1229 let major = Vec3::new(2.0, -1.0, 1.0);
1230 let minor = Vec3::new(0.5, 3.0, 1.0);
1231 let (a, b) = (2.0, 0.9);
1232 let (t0, t1) = (-1.5, 2.2);
1233 let curve = make_hyperbola(center, major, minor, a, b, t0, t1).unwrap();
1234 let (x_axis, y_axis, z_axis) = orthonormal_frame(major, minor);
1235 for index in 0..20 {
1236 let t = t0 + (t1 - t0) * index as f64 / 19.0;
1237 let local = curve.evaluate(t).unwrap().sub(center);
1238 let (x, y, z) = (local.dot(x_axis), local.dot(y_axis), local.dot(z_axis));
1239 let residual = x * x / (a * a) - y * y / (b * b) - 1.0;
1240 assert!(residual.abs() < 1e-10, "t={t}: residual {residual}");
1241 assert!(z.abs() < 1e-10, "t={t}: out of plane by {z}");
1242 assert!(x > 0.0, "t={t}: crossed to the wrong branch, x={x}");
1244 }
1245 }
1246
1247 #[test]
1248 fn parabola_reproduces_standard_form_and_derivatives_exactly() {
1249 let vertex = Vec3::new(0.5, 0.25, -1.0);
1250 let axis = Vec3::new(0.0, 0.0, 2.0);
1251 let latus = Vec3::new(3.0, 0.0, 1.0);
1252 let focal = 0.8;
1253 let (t0, t1) = (-2.0, 1.5);
1254 let curve = make_parabola(vertex, axis, latus, focal, t0, t1).unwrap();
1255 let (x_axis, y_axis, _) = orthonormal_frame(axis, latus);
1256 let expected = |t: f64| {
1257 vertex
1258 .add(x_axis.scale(focal * t * t))
1259 .add(y_axis.scale(2.0 * focal * t))
1260 };
1261 assert_eq!(curve.domain().unwrap(), [t0, t1]);
1265 close(curve.evaluate(t0).unwrap(), expected(t0), 1e-12);
1266 close(curve.evaluate(t1).unwrap(), expected(t1), 1e-12);
1267 let mid = 0.5 * (t0 + t1);
1268 close(curve.evaluate(mid).unwrap(), expected(mid), 1e-12);
1269 let derivatives = curve.derivatives(0.37, 2).unwrap();
1270 close(
1271 derivatives[1],
1272 x_axis
1273 .scale(2.0 * focal * 0.37)
1274 .add(y_axis.scale(2.0 * focal)),
1275 1e-10,
1276 );
1277 close(derivatives[2], x_axis.scale(2.0 * focal), 1e-10);
1278 assert!(NurbsCurve::new(
1280 curve.degree,
1281 curve.knots.clone(),
1282 curve.control_points.clone()
1283 )
1284 .is_ok());
1285 }
1286
1287 #[test]
1288 fn hyperbola_endpoints_mid_parameter_and_weight_are_exact() {
1289 let center = Vec3::new(1.0, 2.0, 3.0);
1290 let major = Vec3::new(1.0, 0.0, 0.0);
1291 let minor = Vec3::new(0.0, 1.0, 0.0);
1292 let (a, b) = (1.25, 2.5);
1293 let (t0, t1) = (-0.75, 1.8);
1294 let curve = make_hyperbola(center, major, minor, a, b, t0, t1).unwrap();
1295 let on_curve = |t: f64| {
1296 center
1297 .add(major.scale(a * t.cosh()))
1298 .add(minor.scale(b * t.sinh()))
1299 };
1300 assert_eq!(curve.domain().unwrap(), [t0, t1]);
1301 close(curve.evaluate(t0).unwrap(), on_curve(t0), 1e-12);
1302 close(curve.evaluate(t1).unwrap(), on_curve(t1), 1e-12);
1303 let mid = 0.5 * (t0 + t1);
1306 close(curve.evaluate(mid).unwrap(), on_curve(mid), 1e-12);
1307 assert!((curve.control_points[1].w - (0.5 * (t1 - t0)).cosh()).abs() < 1e-12);
1308 let derivatives = curve.derivatives(t0, 1).unwrap();
1311 let tangent = major.scale(a * t0.sinh()).add(minor.scale(b * t0.cosh()));
1312 assert!(
1313 derivatives[1].cross(tangent).length()
1314 <= 1e-9 * derivatives[1].length() * tangent.length()
1315 );
1316 assert!(derivatives[1].dot(tangent) > 0.0);
1317 assert!(NurbsCurve::new(
1318 curve.degree,
1319 curve.knots.clone(),
1320 curve.control_points.clone()
1321 )
1322 .is_ok());
1323 }
1324
1325 #[test]
1326 fn conic_constructors_reject_degenerate_inputs() {
1327 let origin = Vec3::default();
1328 let x = Vec3::new(1.0, 0.0, 0.0);
1329 let y = Vec3::new(0.0, 1.0, 0.0);
1330 let error = make_parabola(origin, x, y, 0.0, 0.0, 1.0).unwrap_err();
1333 assert!(error.starts_with("make_parabola:"), "{error}");
1334 assert!(make_parabola(origin, x, y, -1.0, 0.0, 1.0).is_err());
1335 assert!(make_parabola(origin, x, y, f64::NAN, 0.0, 1.0).is_err());
1336 assert!(make_parabola(origin, x, x.scale(3.0), 1.0, 0.0, 1.0).is_err());
1337 assert!(make_parabola(origin, origin, y, 1.0, 0.0, 1.0).is_err());
1338 assert!(make_parabola(origin, x, origin, 1.0, 0.0, 1.0).is_err());
1339 assert!(make_parabola(origin, x, y, 1.0, 1.0, 1.0).is_err());
1340 assert!(make_parabola(origin, x, y, 1.0, 2.0, -1.0).is_err());
1341 assert!(make_parabola(origin, x, y, 1.0, 0.0, f64::INFINITY).is_err());
1342 let error = make_hyperbola(origin, x, x, 1.0, 1.0, 0.0, 1.0).unwrap_err();
1344 assert!(error.starts_with("make_hyperbola:"), "{error}");
1345 assert!(make_hyperbola(origin, x, y, 0.0, 1.0, 0.0, 1.0).is_err());
1346 assert!(make_hyperbola(origin, x, y, 1.0, -2.0, 0.0, 1.0).is_err());
1347 assert!(make_hyperbola(origin, x, y, f64::NAN, 1.0, 0.0, 1.0).is_err());
1348 assert!(make_hyperbola(origin, x, y, 1.0, 1.0, 0.5, 0.5).is_err());
1349 assert!(make_hyperbola(origin, x, y, 1.0, 1.0, f64::NAN, 1.0).is_err());
1350 let error = make_hyperbola(origin, x, y, 1.0, 1.0, 0.0, 1600.0).unwrap_err();
1353 assert!(error.starts_with("make_hyperbola:"), "{error}");
1354 }
1355}