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
22pub(crate) fn interior_knot_count(knots: &[f64], degree: usize) -> usize {
26 distinct_interior_knots(knots, degree).count()
27}
28
29fn distinct_interior_knots(knots: &[f64], degree: usize) -> impl Iterator<Item = f64> + '_ {
30 let start = knots[degree];
31 let end = knots[knots.len() - 1 - degree];
32 let mut previous = None;
33 knots.iter().copied().filter(move |&knot| {
34 if knot <= start + KNOT_DEDUP_EPS || knot >= end - KNOT_DEDUP_EPS {
35 return false;
36 }
37 if previous.is_none_or(|value: f64| (value - knot).abs() > KNOT_DEDUP_EPS) {
38 previous = Some(knot);
39 true
40 } else {
41 false
42 }
43 })
44}
45
46#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
47pub struct Vec4 {
48 pub x: f64,
49 pub y: f64,
50 pub z: f64,
51 pub w: f64,
52}
53
54pub(crate) fn parameter_line(u0: f64, v0: f64, u1: f64, v1: f64) -> Result<NurbsCurve, String> {
56 make_line(Vec3::new(u0, v0, 0.0), Vec3::new(u1, v1, 0.0))
57}
58
59pub(crate) fn curve_to_plane_parameters(
61 curve: &NurbsCurve,
62 origin: Vec3,
63 x_axis: Vec3,
64 y_axis: Vec3,
65) -> Result<NurbsCurve, String> {
66 NurbsCurve::new(
67 curve.degree,
68 curve.knots.clone(),
69 curve
70 .control_points
71 .iter()
72 .map(|point| {
73 let euclidean = Vec3::new(point.x / point.w, point.y / point.w, point.z / point.w);
74 let delta = euclidean.sub(origin);
75 Vec4 {
76 x: delta.dot(x_axis) * point.w,
77 y: delta.dot(y_axis) * point.w,
78 z: 0.0,
79 w: point.w,
80 }
81 })
82 .collect(),
83 )
84}
85
86impl Vec4 {
87 pub fn from_point(point: Vec3, weight: f64) -> Self {
88 Self {
89 x: point.x * weight,
90 y: point.y * weight,
91 z: point.z * weight,
92 w: weight,
93 }
94 }
95
96 pub(crate) fn add(self, rhs: Self) -> Self {
97 Self {
98 x: self.x + rhs.x,
99 y: self.y + rhs.y,
100 z: self.z + rhs.z,
101 w: self.w + rhs.w,
102 }
103 }
104
105 pub(crate) fn scale(self, factor: f64) -> Self {
106 Self {
107 x: self.x * factor,
108 y: self.y * factor,
109 z: self.z * factor,
110 w: self.w * factor,
111 }
112 }
113
114 pub(crate) fn point(self) -> Result<Vec3, String> {
115 if self.w.abs() <= EPS {
116 return Err("cannot project a homogeneous point with zero weight".into());
117 }
118 Ok(Vec3::new(self.x / self.w, self.y / self.w, self.z / self.w))
119 }
120}
121
122pub(crate) const MAX_STACK_DEGREE: usize = 7;
126pub(crate) const MAX_STACK_ORDER: usize = MAX_STACK_DEGREE + 1;
127
128fn knot_reject(reason: &str, knots: &[f64], degree: usize) -> String {
134 if std::env::var("BREP_DEBUG_KNOTS").is_err() {
135 return reason.to_string();
136 }
137 let mut worst_descent = f64::NEG_INFINITY;
138 let mut worst_index = 0usize;
139 for (index, pair) in knots.windows(2).enumerate() {
140 let descent = pair[0] - pair[1];
141 if descent > worst_descent {
142 worst_descent = descent;
143 worst_index = index;
144 }
145 }
146 let detail = format!(
147 "{reason} [degree={degree} count={} worst_descent={worst_descent:.6e} at index \
148 {worst_index} knots={knots:?}]",
149 knots.len()
150 );
151 eprintln!(
152 "KNOT-REJECT {detail}\n{}",
153 std::backtrace::Backtrace::force_capture()
154 );
155 detail
156}
157
158pub(crate) fn validate_knots(knots: &[f64], degree: usize) -> Result<(), String> {
162 if degree < 1 {
163 return Err("KnotVector: degree must be >= 1".into());
164 }
165 if knots.len() < 2 * (degree + 1) {
166 return Err(format!(
167 "KnotVector: need at least {} knots for degree {}, got {}",
168 2 * (degree + 1),
169 degree,
170 knots.len()
171 ));
172 }
173 if knots.iter().any(|value| !value.is_finite()) {
174 return Err("KnotVector: knots must be finite".into());
175 }
176 if knots
177 .windows(2)
178 .any(|pair| pair[1] < pair[0] - KNOT_IDENTITY_TOL)
179 {
180 return Err(knot_reject(
181 "KnotVector: knots must be non-decreasing",
182 knots,
183 degree,
184 ));
185 }
186 let first = knots[0];
187 let last = knots[knots.len() - 1];
188 for index in 0..=degree {
189 if (knots[index] - first).abs() > KNOT_IDENTITY_TOL {
190 return Err(knot_reject(
191 "KnotVector: expected clamped start",
192 knots,
193 degree,
194 ));
195 }
196 if (knots[knots.len() - 1 - index] - last).abs() > KNOT_IDENTITY_TOL {
197 return Err(knot_reject("KnotVector: expected clamped end", knots, degree));
198 }
199 }
200 if last - first <= KNOT_IDENTITY_TOL {
201 return Err(knot_reject(
202 "KnotVector: degenerate parameter range",
203 knots,
204 degree,
205 ));
206 }
207 Ok(())
208}
209
210pub(crate) fn knot_domain(knots: &[f64], degree: usize) -> [f64; 2] {
211 [knots[degree], knots[knots.len() - 1 - degree]]
212}
213
214pub(crate) fn knot_clamp(knots: &[f64], degree: usize, parameter: f64) -> f64 {
215 let [start, end] = knot_domain(knots, degree);
216 parameter.clamp(start, end)
217}
218
219pub(crate) fn knot_find_span(knots: &[f64], degree: usize, parameter: f64) -> usize {
220 let parameter = knot_clamp(knots, degree, parameter);
221 let n = knots.len() - degree - 2;
222 if parameter >= knots[n + 1] {
223 let mut span = n;
230 while span > degree && knots[span] >= knots[span + 1] {
231 span -= 1;
232 }
233 return span;
234 }
235 if parameter <= knots[degree] {
236 let mut span = degree;
243 while span < n && knots[span + 1] <= parameter {
244 span += 1;
245 }
246 return span;
247 }
248 let mut low = degree;
249 let mut high = n + 1;
250 let mut middle = (low + high) / 2;
251 while parameter < knots[middle] || parameter >= knots[middle + 1] {
252 if parameter < knots[middle] {
253 high = middle;
254 } else {
255 low = middle;
256 }
257 middle = (low + high) / 2;
258 }
259 middle
260}
261
262pub(crate) fn basis_functions_into(
265 knots: &[f64],
266 degree: usize,
267 span: usize,
268 parameter: f64,
269 basis: &mut [f64; MAX_STACK_ORDER],
270) {
271 debug_assert!(degree <= MAX_STACK_DEGREE);
272 let mut left = [0.0f64; MAX_STACK_ORDER];
273 let mut right = [0.0f64; MAX_STACK_ORDER];
274 basis[0] = 1.0;
275 for j in 1..=degree {
276 left[j] = parameter - knots[span + 1 - j];
277 right[j] = knots[span + j] - parameter;
278 let mut saved = 0.0;
279 for r in 0..j {
280 let denominator = right[r + 1] + left[j - r];
281 let temporary = if denominator.abs() <= EPS {
282 0.0
283 } else {
284 basis[r] / denominator
285 };
286 basis[r] = saved + right[r + 1] * temporary;
287 saved = left[j - r] * temporary;
288 }
289 basis[j] = saved;
290 }
291}
292
293pub(crate) fn basis_derivatives_into(
299 knots: &[f64],
300 degree: usize,
301 span: usize,
302 parameter: f64,
303 derivative_count: usize,
304 out: &mut [[f64; MAX_STACK_ORDER]],
305) {
306 debug_assert!(degree <= MAX_STACK_DEGREE);
307 debug_assert!(out.len() > derivative_count);
308 let p = degree;
309 let n = derivative_count.min(p);
310 let mut ndu = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
311 let mut left = [0.0f64; MAX_STACK_ORDER];
312 let mut right = [0.0f64; MAX_STACK_ORDER];
313 ndu[0][0] = 1.0;
314
315 for j in 1..=p {
316 left[j] = parameter - knots[span + 1 - j];
317 right[j] = knots[span + j] - parameter;
318 let mut saved = 0.0;
319 for r in 0..j {
320 ndu[j][r] = right[r + 1] + left[j - r];
321 let temporary = if ndu[j][r].abs() <= EPS {
322 0.0
323 } else {
324 ndu[r][j - 1] / ndu[j][r]
325 };
326 ndu[r][j] = saved + right[r + 1] * temporary;
327 saved = left[j - r] * temporary;
328 }
329 ndu[j][j] = saved;
330 }
331
332 for j in 0..=p {
333 out[0][j] = ndu[j][p];
334 }
335 let mut a = [[0.0f64; MAX_STACK_ORDER]; 2];
336 for r in 0..=p {
337 let mut s1 = 0;
338 let mut s2 = 1;
339 a[0][0] = 1.0;
340 for k in 1..=n {
341 a[s2] = [0.0; MAX_STACK_ORDER];
342 let mut value = 0.0;
343 let rk = r as isize - k as isize;
344 let pk = p - k;
345 if r >= k {
346 let denominator = ndu[pk + 1][rk as usize];
347 a[s2][0] = a[s1][0] / denominator;
348 value = a[s2][0] * ndu[rk as usize][pk];
349 }
350 let j1 = if rk >= -1 { 1 } else { (-rk) as usize };
351 let j2 = if r <= pk + 1 { k - 1 } else { p - r };
352 if j1 <= j2 {
353 for j in j1..=j2 {
354 let index = (rk + j as isize) as usize;
355 a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][index];
356 value += a[s2][j] * ndu[index][pk];
357 }
358 }
359 if r <= pk {
360 a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r];
361 value += a[s2][k] * ndu[r][pk];
362 }
363 out[k][r] = value;
364 std::mem::swap(&mut s1, &mut s2);
365 }
366 }
367 let mut factor = p as f64;
368 for k in 1..=n {
369 for value in out[k][..=p].iter_mut() {
370 *value *= factor;
371 }
372 factor *= (p - k) as f64;
373 }
374}
375
376#[derive(Clone, Debug, Deserialize, Serialize)]
377pub struct KnotVector {
378 pub knots: Vec<f64>,
379 pub degree: usize,
380}
381
382pub(crate) fn interior_knots(knots: &[f64], degree: usize) -> Vec<f64> {
384 distinct_interior_knots(knots, degree).collect()
385}
386
387impl KnotVector {
388 pub fn new(knots: Vec<f64>, degree: usize) -> Result<Self, String> {
389 validate_knots(&knots, degree)?;
390 Ok(Self { knots, degree })
391 }
392
393 pub fn control_point_count(&self) -> usize {
394 self.knots.len() - self.degree - 1
395 }
396
397 pub fn domain(&self) -> [f64; 2] {
398 [
399 self.knots[self.degree],
400 self.knots[self.knots.len() - 1 - self.degree],
401 ]
402 }
403
404 pub fn clamp_param(&self, parameter: f64) -> f64 {
405 knot_clamp(&self.knots, self.degree, parameter)
406 }
407
408 pub fn find_span(&self, parameter: f64) -> usize {
409 knot_find_span(&self.knots, self.degree, parameter)
410 }
411
412 pub fn basis_functions(&self, span: usize, parameter: f64) -> Vec<f64> {
413 if self.degree <= MAX_STACK_DEGREE {
414 let mut basis = [0.0f64; MAX_STACK_ORDER];
415 basis_functions_into(&self.knots, self.degree, span, parameter, &mut basis);
416 return basis[..=self.degree].to_vec();
417 }
418 let mut basis = vec![0.0; self.degree + 1];
419 let mut left = vec![0.0; self.degree + 1];
420 let mut right = vec![0.0; self.degree + 1];
421 basis[0] = 1.0;
422 for j in 1..=self.degree {
423 left[j] = parameter - self.knots[span + 1 - j];
424 right[j] = self.knots[span + j] - parameter;
425 let mut saved = 0.0;
426 for r in 0..j {
427 let denominator = right[r + 1] + left[j - r];
428 let temporary = if denominator.abs() <= EPS {
429 0.0
430 } else {
431 basis[r] / denominator
432 };
433 basis[r] = saved + right[r + 1] * temporary;
434 saved = left[j - r] * temporary;
435 }
436 basis[j] = saved;
437 }
438 basis
439 }
440
441 pub fn basis_derivatives(
442 &self,
443 span: usize,
444 parameter: f64,
445 derivative_count: usize,
446 ) -> Vec<Vec<f64>> {
447 if self.degree <= MAX_STACK_DEGREE && derivative_count <= MAX_STACK_DEGREE {
448 let mut rows = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
449 basis_derivatives_into(
450 &self.knots,
451 self.degree,
452 span,
453 parameter,
454 derivative_count,
455 &mut rows[..=derivative_count],
456 );
457 return rows[..=derivative_count]
458 .iter()
459 .map(|row| row[..=self.degree].to_vec())
460 .collect();
461 }
462 let p = self.degree;
463 let n = derivative_count.min(p);
464 let mut ndu = vec![vec![0.0; p + 1]; p + 1];
465 let mut left = vec![0.0; p + 1];
466 let mut right = vec![0.0; p + 1];
467 ndu[0][0] = 1.0;
468
469 for j in 1..=p {
470 left[j] = parameter - self.knots[span + 1 - j];
471 right[j] = self.knots[span + j] - parameter;
472 let mut saved = 0.0;
473 for r in 0..j {
474 ndu[j][r] = right[r + 1] + left[j - r];
475 let temporary = if ndu[j][r].abs() <= EPS {
476 0.0
477 } else {
478 ndu[r][j - 1] / ndu[j][r]
479 };
480 ndu[r][j] = saved + right[r + 1] * temporary;
481 saved = left[j - r] * temporary;
482 }
483 ndu[j][j] = saved;
484 }
485
486 let mut derivatives = vec![vec![0.0; p + 1]; derivative_count + 1];
487 for j in 0..=p {
488 derivatives[0][j] = ndu[j][p];
489 }
490 let mut a = vec![vec![0.0; p + 1]; 2];
491 for r in 0..=p {
492 let mut s1 = 0;
493 let mut s2 = 1;
494 a[0][0] = 1.0;
495 for k in 1..=n {
496 a[s2].fill(0.0);
497 let mut value = 0.0;
498 let rk = r as isize - k as isize;
499 let pk = p - k;
500 if r >= k {
501 let denominator = ndu[pk + 1][rk as usize];
502 a[s2][0] = a[s1][0] / denominator;
503 value = a[s2][0] * ndu[rk as usize][pk];
504 }
505 let j1 = if rk >= -1 { 1 } else { (-rk) as usize };
506 let j2 = if r <= pk + 1 { k - 1 } else { p - r };
507 if j1 <= j2 {
508 for j in j1..=j2 {
509 let index = (rk + j as isize) as usize;
510 a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][index];
511 value += a[s2][j] * ndu[index][pk];
512 }
513 }
514 if r <= pk {
515 a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r];
516 value += a[s2][k] * ndu[r][pk];
517 }
518 derivatives[k][r] = value;
519 std::mem::swap(&mut s1, &mut s2);
520 }
521 }
522 let mut factor = p as f64;
523 for (k, row) in derivatives.iter_mut().enumerate().take(n + 1).skip(1) {
524 for value in row {
525 *value *= factor;
526 }
527 factor *= (p - k) as f64;
528 }
529 derivatives
530 }
531}
532
533#[derive(Clone, Debug, Deserialize, Serialize)]
534pub struct NurbsCurve {
535 pub degree: usize,
536 pub knots: Vec<f64>,
537 pub control_points: Vec<Vec4>,
538 #[serde(skip, default)]
542 validated: std::cell::Cell<bool>,
543}
544
545impl NurbsCurve {
546 pub fn new(degree: usize, knots: Vec<f64>, control_points: Vec<Vec4>) -> Result<Self, String> {
547 let curve = Self {
548 degree,
549 knots,
550 control_points,
551 validated: std::cell::Cell::new(false),
552 };
553 curve.ensure_valid()?;
554 Ok(curve)
555 }
556
557 fn ensure_valid(&self) -> Result<(), String> {
559 if self.validated.get() {
560 return Ok(());
561 }
562 validate_knots(&self.knots, self.degree)?;
563 let expected = self.knots.len() - self.degree - 1;
564 if self.control_points.len() != expected {
565 return Err(format!(
566 "NurbsCurve: knot vector implies {} control points, got {}",
567 expected,
568 self.control_points.len()
569 ));
570 }
571 if self.control_points.iter().any(|point| {
572 point.w <= EPS
573 || ![point.x, point.y, point.z, point.w]
574 .iter()
575 .all(|value| value.is_finite())
576 }) {
577 return Err("NurbsCurve: control points must be finite with positive weights".into());
578 }
579 self.validated.set(true);
580 Ok(())
581 }
582
583 fn knot_vector(&self) -> Result<KnotVector, String> {
584 KnotVector::new(self.knots.clone(), self.degree)
585 }
586
587 pub fn domain(&self) -> Result<[f64; 2], String> {
588 self.ensure_valid()?;
589 Ok(knot_domain(&self.knots, self.degree))
590 }
591
592 pub fn evaluate_homogeneous(&self, parameter: f64) -> Result<Vec4, String> {
593 self.ensure_valid()?;
594 let span = knot_find_span(&self.knots, self.degree, parameter);
595 let parameter = knot_clamp(&self.knots, self.degree, parameter);
596 let mut point = Vec4 {
597 x: 0.0,
598 y: 0.0,
599 z: 0.0,
600 w: 0.0,
601 };
602 if self.degree <= MAX_STACK_DEGREE {
603 let mut basis = [0.0f64; MAX_STACK_ORDER];
604 basis_functions_into(&self.knots, self.degree, span, parameter, &mut basis);
605 for (index, value) in basis[..=self.degree].iter().enumerate() {
606 point = point.add(self.control_points[span - self.degree + index].scale(*value));
607 }
608 } else {
609 let knot_vector = self.knot_vector()?;
610 let basis = knot_vector.basis_functions(span, parameter);
611 for (index, value) in basis.iter().enumerate() {
612 point = point.add(self.control_points[span - self.degree + index].scale(*value));
613 }
614 }
615 Ok(point)
616 }
617
618 pub fn evaluate(&self, parameter: f64) -> Result<Vec3, String> {
619 self.evaluate_homogeneous(parameter)?.point()
620 }
621
622 pub fn evaluate_extended(&self, parameter: f64) -> Result<Vec3, String> {
627 Ok(self.derivatives_extended(parameter, 0)?[0])
628 }
629
630 pub fn derivatives_extended(
634 &self,
635 parameter: f64,
636 derivative_count: usize,
637 ) -> Result<Vec<Vec3>, String> {
638 let [start, end] = self.domain()?;
639 if parameter >= start && parameter <= end {
640 return self.derivatives(parameter, derivative_count);
641 }
642 let period = end - start;
643 if period > 0.0 {
644 let closed =
645 self.evaluate(start)?.sub(self.evaluate(end)?).length() <= 1e-9 * (1.0 + period);
646 if closed {
647 let wrapped = start + (parameter - start).rem_euclid(period);
648 return self.derivatives(wrapped, derivative_count);
649 }
650 }
651 let boundary = if parameter < start { start } else { end };
652 let base = self.derivatives(boundary, derivative_count.max(1))?;
653 let mut result = Vec::with_capacity(derivative_count + 1);
654 result.push(base[0].add(base[1].scale(parameter - boundary)));
655 if derivative_count >= 1 {
656 result.push(base[1]);
657 }
658 for _ in 2..=derivative_count {
659 result.push(Vec3::default());
660 }
661 Ok(result)
662 }
663
664 pub fn derivatives(
665 &self,
666 parameter: f64,
667 derivative_count: usize,
668 ) -> Result<Vec<Vec3>, String> {
669 self.ensure_valid()?;
670 let parameter = knot_clamp(&self.knots, self.degree, parameter);
671 let span = knot_find_span(&self.knots, self.degree, parameter);
672 let calculated_count = derivative_count.min(self.degree);
673 let mut homogeneous: Vec<Vec4> = Vec::with_capacity(calculated_count + 1);
674 if self.degree <= MAX_STACK_DEGREE {
675 let mut rows = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
676 basis_derivatives_into(
677 &self.knots,
678 self.degree,
679 span,
680 parameter,
681 calculated_count,
682 &mut rows[..=calculated_count],
683 );
684 for row in rows.iter().take(calculated_count + 1) {
685 let mut point = Vec4 {
686 x: 0.0,
687 y: 0.0,
688 z: 0.0,
689 w: 0.0,
690 };
691 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
692 point =
693 point.add(self.control_points[span - self.degree + index].scale(*value));
694 }
695 homogeneous.push(point);
696 }
697 } else {
698 let knot_vector = self.knot_vector()?;
699 let basis = knot_vector.basis_derivatives(span, parameter, calculated_count);
700 for row in basis.iter().take(calculated_count + 1) {
701 let mut point = Vec4 {
702 x: 0.0,
703 y: 0.0,
704 z: 0.0,
705 w: 0.0,
706 };
707 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
708 point =
709 point.add(self.control_points[span - self.degree + index].scale(*value));
710 }
711 homogeneous.push(point);
712 }
713 }
714
715 let mut result: Vec<Vec3> = Vec::with_capacity(derivative_count + 1);
716 for k in 0..=calculated_count {
717 let mut value = Vec3::new(homogeneous[k].x, homogeneous[k].y, homogeneous[k].z);
718 for i in 1..=k {
719 value = value.sub(result[k - i].scale(binomial(k, i) * homogeneous[i].w));
720 }
721 result.push(value.scale(1.0 / homogeneous[0].w));
722 }
723 result.resize(derivative_count + 1, Vec3::default());
724 Ok(result)
725 }
726
727 pub(crate) fn derivatives_small(
738 &self,
739 parameter: f64,
740 derivative_count: usize,
741 ) -> Result<[Vec3; 3], String> {
742 debug_assert!(derivative_count <= 2);
743 self.ensure_valid()?;
744 let parameter = knot_clamp(&self.knots, self.degree, parameter);
745 let span = knot_find_span(&self.knots, self.degree, parameter);
746 let calculated_count = derivative_count.min(self.degree);
747 let zero = Vec4 {
748 x: 0.0,
749 y: 0.0,
750 z: 0.0,
751 w: 0.0,
752 };
753 let mut homogeneous = [zero; 3];
754 if self.degree <= MAX_STACK_DEGREE {
755 let mut rows = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
756 basis_derivatives_into(
757 &self.knots,
758 self.degree,
759 span,
760 parameter,
761 calculated_count,
762 &mut rows[..=calculated_count],
763 );
764 for (k, row) in rows.iter().take(calculated_count + 1).enumerate() {
765 let mut point = zero;
766 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
767 point =
768 point.add(self.control_points[span - self.degree + index].scale(*value));
769 }
770 homogeneous[k] = point;
771 }
772 } else {
773 let knot_vector = self.knot_vector()?;
774 let basis = knot_vector.basis_derivatives(span, parameter, calculated_count);
775 for (k, row) in basis.iter().take(calculated_count + 1).enumerate() {
776 let mut point = zero;
777 for (index, value) in row.iter().enumerate().take(self.degree + 1) {
778 point =
779 point.add(self.control_points[span - self.degree + index].scale(*value));
780 }
781 homogeneous[k] = point;
782 }
783 }
784
785 let mut result = [Vec3::default(); 3];
786 for k in 0..=calculated_count {
787 let mut value = Vec3::new(homogeneous[k].x, homogeneous[k].y, homogeneous[k].z);
788 for i in 1..=k {
789 value = value.sub(result[k - i].scale(binomial(k, i) * homogeneous[i].w));
790 }
791 result[k] = value.scale(1.0 / homogeneous[0].w);
792 }
793 Ok(result)
794 }
795
796 #[inline]
799 pub(crate) fn deriv1(&self, parameter: f64) -> Result<(Vec3, Vec3), String> {
800 let d = self.derivatives_small(parameter, 1)?;
801 Ok((d[0], d[1]))
802 }
803
804 pub fn reversed(&self) -> Result<Self, String> {
805 let start = self.knots[0];
806 let end = self.knots[self.knots.len() - 1];
807 let knots = self
808 .knots
809 .iter()
810 .rev()
811 .map(|knot| start + end - knot)
812 .collect();
813 let control_points = self.control_points.iter().rev().copied().collect();
814 Self::new(self.degree, knots, control_points)
815 }
816
817 pub fn insert_knot(&self, parameter: f64, requested: usize) -> Result<Self, String> {
818 let knot_vector = self.knot_vector()?;
819 let degree = self.degree;
820 let parameter = knot_vector.clamp_param(parameter);
821 let parameter = self
826 .knots
827 .iter()
828 .copied()
829 .find(|knot| (knot - parameter).abs() <= KNOT_IDENTITY_TOL)
830 .unwrap_or(parameter);
831 let multiplicity = self
832 .knots
833 .iter()
834 .filter(|knot| (**knot - parameter).abs() <= KNOT_IDENTITY_TOL)
835 .count();
836 let insertion_count = requested.min(degree.saturating_sub(multiplicity));
837 if insertion_count == 0 {
838 return Ok(self.clone());
839 }
840 let span = knot_vector.find_span(parameter);
841 let last_control = self.control_points.len() - 1;
842 let mut knots = Vec::with_capacity(self.knots.len() + insertion_count);
843 knots.extend_from_slice(&self.knots[..=span]);
844 knots.extend(std::iter::repeat_n(parameter, insertion_count));
845 knots.extend_from_slice(&self.knots[span + 1..]);
846
847 let mut output = vec![
848 Vec4 {
849 x: 0.0,
850 y: 0.0,
851 z: 0.0,
852 w: 1.0,
853 };
854 last_control + 1 + insertion_count
855 ];
856 output[..=span - degree].copy_from_slice(&self.control_points[..=span - degree]);
857 for index in span - multiplicity..=last_control {
858 output[index + insertion_count] = self.control_points[index];
859 }
860 let mut affected = vec![
861 Vec4 {
862 x: 0.0,
863 y: 0.0,
864 z: 0.0,
865 w: 1.0,
866 };
867 degree + 1
868 ];
869 affected[..=degree - multiplicity]
870 .copy_from_slice(&self.control_points[span - degree..=span - multiplicity]);
871 let mut left = 0;
872 for insertion in 1..=insertion_count {
873 left = span - degree + insertion;
874 for index in 0..=degree - insertion - multiplicity {
875 let denominator = self.knots[index + span + 1] - self.knots[left + index];
876 let alpha = (parameter - self.knots[left + index]) / denominator;
877 affected[index] = affected[index + 1]
878 .scale(alpha)
879 .add(affected[index].scale(1.0 - alpha));
880 }
881 output[left] = affected[0];
882 output[span + insertion_count - insertion - multiplicity] =
883 affected[degree - insertion - multiplicity];
884 }
885 for index in left + 1..span - multiplicity {
886 output[index] = affected[index - left];
887 }
888 Self::new(degree, knots, output)
889 }
890
891 pub fn split(&self, parameter: f64) -> Result<(Self, Self), String> {
892 let [start, end] = self.domain()?;
893 if parameter <= start + KNOT_IDENTITY_TOL || parameter >= end - KNOT_IDENTITY_TOL {
894 return Err(format!(
895 "NurbsCurve.split: parameter {parameter} must be strictly inside domain [{start}, {end}]"
896 ));
897 }
898 let parameter = self
901 .knots
902 .iter()
903 .copied()
904 .find(|knot| (knot - parameter).abs() <= KNOT_IDENTITY_TOL)
905 .unwrap_or(parameter);
906 let multiplicity = self
907 .knots
908 .iter()
909 .filter(|knot| (**knot - parameter).abs() <= KNOT_IDENTITY_TOL)
910 .count();
911 let refined = self.insert_knot(parameter, self.degree.saturating_sub(multiplicity))?;
912 let first = refined
913 .knots
914 .iter()
915 .position(|knot| (*knot - parameter).abs() <= KNOT_IDENTITY_TOL)
916 .ok_or_else(|| "NurbsCurve.split: inserted knot not found".to_string())?;
917 let mut left_knots = refined.knots[..first + self.degree].to_vec();
918 left_knots.push(parameter);
919 let left_points = refined.control_points[..first].to_vec();
920 let mut right_knots = vec![parameter; self.degree + 1];
921 right_knots.extend_from_slice(&refined.knots[first + self.degree..]);
922 let right_points = refined.control_points[first - 1..].to_vec();
923 Ok((
924 Self::new(self.degree, left_knots, left_points)?,
925 Self::new(self.degree, right_knots, right_points)?,
926 ))
927 }
928}
929
930pub fn make_line(start: Vec3, end: Vec3) -> Result<NurbsCurve, String> {
931 NurbsCurve::new(
932 1,
933 vec![0.0, 0.0, 1.0, 1.0],
934 vec![Vec4::from_point(start, 1.0), Vec4::from_point(end, 1.0)],
935 )
936}
937
938pub fn make_arc(
939 center: Vec3,
940 x_axis: Vec3,
941 y_axis: Vec3,
942 radius: f64,
943 start_angle: f64,
944 end_angle: f64,
945) -> Result<NurbsCurve, String> {
946 if radius <= EPS {
947 return Err("makeArc: radius must be positive".into());
948 }
949 let x_axis = x_axis.normalized()?;
950 let y_axis = y_axis.normalized()?;
951 if x_axis.dot(y_axis).abs() > 1e-9 {
952 return Err("makeArc: xAxis and yAxis must be orthogonal".into());
953 }
954 let mut theta = end_angle - start_angle;
955 if theta <= EPS {
956 return Err("makeArc: endAngle must exceed startAngle".into());
957 }
958 if theta > std::f64::consts::TAU + EPS {
959 return Err("makeArc: sweep exceeds full circle".into());
960 }
961 theta = theta.min(std::f64::consts::TAU);
962 let segment_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
963 let segment_angle = theta / segment_count as f64;
964 let middle_weight = (segment_angle / 2.0).cos();
965 let point_at = |angle: f64| {
966 center
967 .add(x_axis.scale(radius * angle.cos()))
968 .add(y_axis.scale(radius * angle.sin()))
969 };
970 let tangent_at = |angle: f64| x_axis.scale(-angle.sin()).add(y_axis.scale(angle.cos()));
971
972 let mut points = Vec::with_capacity(2 * segment_count + 1);
973 let mut angle = start_angle;
974 let mut first_point = point_at(angle);
975 let mut first_tangent = tangent_at(angle);
976 points.push(Vec4::from_point(first_point, 1.0));
977 for _ in 0..segment_count {
978 angle += segment_angle;
979 let end_point = point_at(angle);
980 let end_tangent = tangent_at(angle);
981 let cross = first_tangent.cross(end_tangent);
982 let denominator = cross.length_squared();
983 if denominator <= EPS {
984 return Err("makeArc: arc tangents are parallel".into());
985 }
986 let distance = end_point.sub(first_point).cross(end_tangent).dot(cross) / denominator;
987 let middle = first_point.add(first_tangent.scale(distance));
988 points.push(Vec4::from_point(middle, middle_weight));
989 points.push(Vec4::from_point(end_point, 1.0));
990 first_point = end_point;
991 first_tangent = end_tangent;
992 }
993 let mut knots = vec![0.0, 0.0, 0.0];
994 for index in 1..segment_count {
995 let knot = index as f64 / segment_count as f64;
996 knots.extend([knot, knot]);
997 }
998 knots.extend([1.0, 1.0, 1.0]);
999 NurbsCurve::new(2, knots, points)
1000}
1001
1002pub fn make_circle(center: Vec3, normal: Vec3, radius: f64) -> Result<NurbsCurve, String> {
1003 let normal = normal.normalized()?;
1004 let x_axis = normal.perpendicular()?;
1005 let y_axis = normal.cross(x_axis).normalized()?;
1006 make_arc(center, x_axis, y_axis, radius, 0.0, std::f64::consts::TAU)
1007}
1008
1009fn conic_frame(fn_name: &str, primary: Vec3, hint: Vec3) -> Result<(Vec3, Vec3), String> {
1015 let x_axis = primary
1016 .normalized()
1017 .map_err(|_| format!("{fn_name}: primary axis must be non-zero"))?;
1018 if hint.length() <= EPS {
1019 return Err(format!("{fn_name}: in-plane direction must be non-zero"));
1020 }
1021 hint.sub(x_axis.scale(hint.dot(x_axis)))
1022 .normalized()
1023 .map(|y_axis| (x_axis, y_axis))
1024 .map_err(|_| format!("{fn_name}: frame directions must not be parallel"))
1025}
1026
1027fn conic_range(fn_name: &str, t0: f64, t1: f64) -> Result<(), String> {
1031 if !t0.is_finite() || !t1.is_finite() {
1032 return Err(format!("{fn_name}: parameter range must be finite"));
1033 }
1034 if t1 - t0 <= KNOT_IDENTITY_TOL {
1035 return Err(format!("{fn_name}: t1 ({t1}) must exceed t0 ({t0})"));
1036 }
1037 Ok(())
1038}
1039
1040fn conic_points_finite(fn_name: &str, points: &[Vec4]) -> Result<(), String> {
1045 if points.iter().any(|point| {
1046 ![point.x, point.y, point.z, point.w]
1047 .iter()
1048 .all(|value| value.is_finite())
1049 }) {
1050 return Err(format!(
1051 "{fn_name}: control points overflow f64 — parameter range too extreme"
1052 ));
1053 }
1054 Ok(())
1055}
1056
1057pub fn make_parabola(
1065 vertex: Vec3,
1066 axis: Vec3,
1067 latus_direction: Vec3,
1068 focal: f64,
1069 t0: f64,
1070 t1: f64,
1071) -> Result<NurbsCurve, String> {
1072 if !focal.is_finite() || focal <= EPS {
1073 return Err("make_parabola: focal distance must be positive".into());
1074 }
1075 conic_range("make_parabola", t0, t1)?;
1076 let (x_axis, y_axis) = conic_frame("make_parabola", axis, latus_direction)?;
1077 let point_at = |t: f64| {
1078 vertex
1079 .add(x_axis.scale(focal * t * t))
1080 .add(y_axis.scale(2.0 * focal * t))
1081 };
1082 let middle = vertex
1087 .add(x_axis.scale(focal * t0 * t1))
1088 .add(y_axis.scale(focal * (t0 + t1)));
1089 let control_points = vec![
1090 Vec4::from_point(point_at(t0), 1.0),
1091 Vec4::from_point(middle, 1.0),
1092 Vec4::from_point(point_at(t1), 1.0),
1093 ];
1094 conic_points_finite("make_parabola", &control_points)?;
1095 NurbsCurve::new(2, vec![t0, t0, t0, t1, t1, t1], control_points)
1096}
1097
1098pub fn make_hyperbola(
1108 center: Vec3,
1109 major_axis: Vec3,
1110 minor_axis: Vec3,
1111 a: f64,
1112 b: f64,
1113 t0: f64,
1114 t1: f64,
1115) -> Result<NurbsCurve, String> {
1116 if !a.is_finite() || a <= EPS {
1117 return Err("make_hyperbola: semi-axis a must be positive".into());
1118 }
1119 if !b.is_finite() || b <= EPS {
1120 return Err("make_hyperbola: semi-axis b must be positive".into());
1121 }
1122 conic_range("make_hyperbola", t0, t1)?;
1123 let (x_axis, y_axis) = conic_frame("make_hyperbola", major_axis, minor_axis)?;
1124 let point_at = |t: f64| {
1125 center
1126 .add(x_axis.scale(a * t.cosh()))
1127 .add(y_axis.scale(b * t.sinh()))
1128 };
1129 let mid = 0.5 * (t0 + t1);
1130 let middle_weight = (0.5 * (t1 - t0)).cosh();
1131 let apex = center
1138 .add(x_axis.scale(a * mid.cosh() / middle_weight))
1139 .add(y_axis.scale(b * mid.sinh() / middle_weight));
1140 let control_points = vec![
1141 Vec4::from_point(point_at(t0), 1.0),
1142 Vec4::from_point(apex, middle_weight),
1143 Vec4::from_point(point_at(t1), 1.0),
1144 ];
1145 conic_points_finite("make_hyperbola", &control_points)?;
1146 NurbsCurve::new(2, vec![t0, t0, t0, t1, t1, t1], control_points)
1147}
1148
1149pub fn uniform_clamped_knots(
1150 control_point_count: usize,
1151 degree: usize,
1152) -> Result<Vec<f64>, String> {
1153 if control_point_count == 0 || control_point_count - 1 < degree {
1154 return Err("uniformClampedKnots: need at least degree+1 control points".into());
1155 }
1156 let n = control_point_count - 1;
1157 let mut knots = vec![0.0; degree + 1];
1158 let interior = n - degree;
1159 for index in 1..=interior {
1160 knots.push(index as f64 / (interior + 1) as f64);
1161 }
1162 knots.extend(std::iter::repeat_n(1.0, degree + 1));
1163 Ok(knots)
1164}
1165
1166fn binomial(n: usize, k: usize) -> f64 {
1167 if k > n {
1168 return 0.0;
1169 }
1170 let k = k.min(n - k);
1171 (1..=k).fold(1.0, |value, index| {
1172 value * (n - k + index) as f64 / index as f64
1173 })
1174}
1175
1176