1use axiolid_core::{Frame2, Frame3, Scalar, Vec2, Vec3};
49use axiolid_curve::{BSplineCurve, Curve2, Curve3};
50use axiolid_surface::{BSplineSurface, Surface};
51
52use crate::curve::span_in;
53use crate::nurbs::SplineAxis;
54
55pub const ROUNDING: Scalar = 1e-12;
58
59fn inflate(value: Scalar) -> Scalar {
60 value * (1.0 + ROUNDING)
61}
62
63fn gram_stretch(columns: &[Vec3]) -> Scalar {
68 let mut off: Scalar = 0.0;
69 for (i, a) in columns.iter().enumerate() {
70 for (j, b) in columns.iter().enumerate() {
71 let target = if i == j { 1.0 } else { 0.0 };
72 let entry = a.dot(*b) - target;
73 off += entry * entry;
74 }
75 }
76 (1.0 + off.sqrt()).sqrt()
77}
78
79#[must_use]
81pub fn frame_stretch3(frame: &Frame3) -> Scalar {
82 inflate(gram_stretch(&[frame.x, frame.y, frame.z]))
83}
84
85#[must_use]
87pub fn frame_stretch2(frame: &Frame2) -> Scalar {
88 let lift = |v: Vec2| Vec3::new(v.x, v.y, 0.0);
89 inflate(gram_stretch(&[lift(frame.x), lift(frame.y)]))
90}
91
92fn contains_lattice(lo: Scalar, hi: Scalar, offset: Scalar, period: Scalar) -> bool {
96 let k = ((lo - offset) / period).ceil();
97 offset + k * period <= hi
98}
99
100fn max_cos2(lo: Scalar, hi: Scalar) -> Scalar {
102 if contains_lattice(lo, hi, 0.0, core::f64::consts::PI) {
103 1.0
104 } else {
105 lo.cos().powi(2).max(hi.cos().powi(2))
106 }
107}
108
109fn min_cos2(lo: Scalar, hi: Scalar) -> Scalar {
111 if contains_lattice(lo, hi, core::f64::consts::FRAC_PI_2, core::f64::consts::PI) {
112 0.0
113 } else {
114 lo.cos().powi(2).min(hi.cos().powi(2))
115 }
116}
117
118fn max_abs_cos(lo: Scalar, hi: Scalar) -> Scalar {
120 max_cos2(lo, hi).sqrt()
121}
122
123fn max_abs_sin(lo: Scalar, hi: Scalar) -> Scalar {
125 (1.0 - min_cos2(lo, hi)).max(0.0).sqrt()
126}
127
128fn cos_range(lo: Scalar, hi: Scalar) -> (Scalar, Scalar) {
130 let tau = core::f64::consts::TAU;
131 let max = if contains_lattice(lo, hi, 0.0, tau) {
132 1.0
133 } else {
134 lo.cos().max(hi.cos())
135 };
136 let min = if contains_lattice(lo, hi, core::f64::consts::PI, tau) {
137 -1.0
138 } else {
139 lo.cos().min(hi.cos())
140 };
141 (min, max)
142}
143
144fn ordered(a: Scalar, b: Scalar) -> (Scalar, Scalar) {
145 if a <= b {
146 (a, b)
147 } else {
148 (b, a)
149 }
150}
151
152fn conic_second(rx: Scalar, ry: Scalar, x: Vec3, y: Vec3, a: Scalar, b: Scalar) -> Scalar {
158 let (lo, hi) = ordered(a, b);
159 let alpha2 = (rx * rx) * x.length_squared();
160 let beta2 = (ry * ry) * y.length_squared();
161 let cos2 = if alpha2 >= beta2 {
162 max_cos2(lo, hi)
163 } else {
164 min_cos2(lo, hi)
165 };
166 let main = beta2 + (alpha2 - beta2) * cos2;
167 let cross = (rx * ry * x.dot(y)).abs();
168 (main.max(0.0) + cross).sqrt()
169}
170
171fn circle_sagitta(radius: Scalar, x: Vec3, y: Vec3, a: Scalar, b: Scalar) -> Option<Scalar> {
174 let h = (b - a).abs();
175 let orthonormal = (x.length_squared() - 1.0).abs() <= 1e-12
176 && (y.length_squared() - 1.0).abs() <= 1e-12
177 && x.dot(y).abs() <= 1e-12;
178 (orthonormal && h <= core::f64::consts::PI).then(|| radius.abs() * (1.0 - (0.5 * h).cos()))
179}
180
181fn lift2(v: Vec2) -> Vec3 {
182 Vec3::new(v.x, v.y, 0.0)
183}
184
185#[derive(Debug, Clone, Copy, PartialEq)]
194pub struct CurveDerivativeBounds {
195 pub first: Scalar,
197 pub second: Scalar,
199 pub third: Scalar,
201}
202
203fn hodograph<const N: usize>(
206 points: &[[Scalar; N]],
207 weights: &[Scalar],
208 knots: &[Scalar],
209 degree: usize,
210) -> (Vec<[Scalar; N]>, Vec<Scalar>) {
211 let mut out_points = Vec::with_capacity(points.len().saturating_sub(1));
212 let mut out_weights = Vec::with_capacity(weights.len().saturating_sub(1));
213 for i in 0..points.len().saturating_sub(1) {
214 let width = knots[i + degree + 1] - knots[i + 1];
215 let factor = if width > 0.0 {
216 degree as Scalar / width
217 } else {
218 0.0
219 };
220 out_points.push(core::array::from_fn(|k| {
221 (points[i + 1][k] - points[i][k]) * factor
222 }));
223 out_weights.push((weights[i + 1] - weights[i]) * factor);
224 }
225 (out_points, out_weights)
226}
227
228fn norm<const N: usize>(p: &[Scalar; N]) -> Scalar {
229 p.iter().map(|c| c * c).sum::<Scalar>().sqrt()
230}
231
232fn quotient_bounds(a: [Scalar; 4], w: [Scalar; 4], radius: Scalar, w_min: Scalar) -> [Scalar; 3] {
236 let first = (a[1] + w[1] * radius) / w_min;
237 let second = (a[2] + 2.0 * w[1] * first + w[2] * radius) / w_min;
238 let third = (a[3] + 3.0 * w[2] * first + 3.0 * w[1] * second + w[3] * radius) / w_min;
239 [first, second, third]
240}
241
242fn bspline_bounds<P, const N: usize>(
255 curve: &BSplineCurve<P>,
256 to: impl Fn(&P) -> [Scalar; N],
257 a: Scalar,
258 b: Scalar,
259) -> Option<(CurveDerivativeBounds, Scalar)> {
260 let count = curve.control_points.len();
261 let axis = SplineAxis::new(
262 &curve.knots,
263 &curve.multiplicities,
264 curve.degree,
265 count,
266 "B-spline curve",
267 )
268 .ok()?;
269 let weights: Vec<Scalar> = match &curve.weights {
270 Some(w) if w.len() == count => w.clone(),
271 Some(_) => return None,
272 None => vec![1.0; count],
273 };
274 if weights.iter().any(|w| !(w.is_finite() && *w > 0.0)) {
275 return None;
276 }
277 let degree = axis.degree;
278 let (lo, hi) = ordered(axis.clamp(a), axis.clamp(b));
279 let first_span = span_in(&axis.knots, count, degree, lo);
280 let last_span = span_in(&axis.knots, count, degree, hi);
281 let window = first_span - degree..=last_span;
284 let points: Vec<[Scalar; N]> = curve.control_points.iter().map(&to).collect();
285 if points.iter().flatten().any(|c| !c.is_finite()) {
286 return None;
287 }
288 let mut low = [Scalar::INFINITY; N];
291 let mut high = [Scalar::NEG_INFINITY; N];
292 for p in &points[window.clone()] {
293 for k in 0..N {
294 low[k] = low[k].min(p[k]);
295 high[k] = high[k].max(p[k]);
296 }
297 }
298 let centre: [Scalar; N] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
299 let radius = points[window.clone()]
300 .iter()
301 .map(|p| norm(&core::array::from_fn::<Scalar, N, _>(|k| p[k] - centre[k])))
302 .fold(0.0, Scalar::max);
303 let w_min = weights[window.clone()]
304 .iter()
305 .copied()
306 .fold(Scalar::INFINITY, Scalar::min);
307 let mut hom: Vec<[Scalar; N]> = points
308 .iter()
309 .zip(&weights)
310 .map(|(p, w)| core::array::from_fn(|k| (p[k] - centre[k]) * w))
311 .collect();
312 let mut hw = weights.clone();
313 let mut knots: &[Scalar] = &axis.knots;
314 let mut a_sup = [0.0; 4];
315 let mut w_sup = [0.0; 4];
316 for order in 1..=3 {
317 let current_degree = degree + 1 - order;
318 if current_degree == 0 || hom.len() < 2 {
319 break;
320 }
321 let (next, next_w) = hodograph(&hom, &hw, knots, current_degree);
322 knots = &knots[1..knots.len() - 1];
323 let end = (*window.end()).min(next.len().saturating_sub(1));
324 let start = (*window.start()).min(end);
325 a_sup[order] = next[start..=end].iter().map(norm).fold(0.0, Scalar::max);
326 w_sup[order] = next_w[start..=end]
327 .iter()
328 .map(|w| w.abs())
329 .fold(0.0, Scalar::max);
330 hom = next;
331 hw = next_w;
332 }
333 let [first, second, third] = quotient_bounds(a_sup, w_sup, radius, w_min);
334 let bounds = CurveDerivativeBounds {
335 first: inflate(first),
336 second: inflate(second),
337 third: inflate(third),
338 };
339 let chord = inflate((a_sup[2] + radius * w_sup[2]) / w_min).min(bounds.second);
340 (bounds.first.is_finite()
341 && bounds.second.is_finite()
342 && bounds.third.is_finite()
343 && chord.is_finite())
344 .then_some((bounds, chord))
345}
346
347fn bspline_breaks<P>(curve: &BSplineCurve<P>, k: usize) -> Vec<Scalar> {
350 let degree = usize::from(curve.degree);
351 let interior = curve.knots.len().saturating_sub(2);
352 curve
353 .knots
354 .iter()
355 .zip(&curve.multiplicities)
356 .skip(1)
357 .take(interior)
358 .filter(|(_, &m)| (m as usize) + k > degree)
359 .map(|(&knot, _)| knot)
360 .collect()
361}
362
363#[must_use]
367pub fn continuity_breaks2(curve: &Curve2, k: usize) -> Vec<Scalar> {
368 match curve {
369 Curve2::BSpline(b) => bspline_breaks(b, k),
370 Curve2::Polyline(p) if k >= 1 => polyline_breaks(p.points.len(), p.closed),
371 _ => Vec::new(),
372 }
373}
374
375#[must_use]
377pub fn continuity_breaks3(curve: &Curve3, k: usize) -> Vec<Scalar> {
378 match curve {
379 Curve3::BSpline(b) => bspline_breaks(b, k),
380 Curve3::Polyline(p) if k >= 1 => polyline_breaks(p.points.len(), p.closed),
381 _ => Vec::new(),
382 }
383}
384
385fn polyline_breaks(count: usize, closed: bool) -> Vec<Scalar> {
386 let segments = if closed {
387 count
388 } else {
389 count.saturating_sub(1)
390 };
391 (1..segments).map(|k| k as Scalar).collect()
392}
393
394fn breaks_inside(breaks: &[Scalar], a: Scalar, b: Scalar) -> bool {
395 let (lo, hi) = ordered(a, b);
396 breaks.iter().any(|&t| t > lo && t < hi)
397}
398
399#[must_use]
404pub fn curve_derivative_bounds3(
405 curve: &Curve3,
406 a: Scalar,
407 b: Scalar,
408) -> Option<CurveDerivativeBounds> {
409 if !(a.is_finite() && b.is_finite()) {
410 return None;
411 }
412 match curve {
413 Curve3::Line(line) => Some(CurveDerivativeBounds {
414 first: inflate(line.direction.length()),
415 second: 0.0,
416 third: 0.0,
417 }),
418 Curve3::Circle(c) => Some(conic_bounds(c.radius, c.radius, c.frame.x, c.frame.y, a, b)),
419 Curve3::Ellipse(e) => Some(conic_bounds(
420 e.semi_axis_x,
421 e.semi_axis_y,
422 e.frame.x,
423 e.frame.y,
424 a,
425 b,
426 )),
427 Curve3::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b).map(|b| b.0),
428 _ => None,
429 }
430}
431
432#[must_use]
435pub fn curve_derivative_bounds2(
436 curve: &Curve2,
437 a: Scalar,
438 b: Scalar,
439) -> Option<CurveDerivativeBounds> {
440 if !(a.is_finite() && b.is_finite()) {
441 return None;
442 }
443 match curve {
444 Curve2::Line(line) => Some(CurveDerivativeBounds {
445 first: inflate(line.direction.length()),
446 second: 0.0,
447 third: 0.0,
448 }),
449 Curve2::Circle(c) => Some(conic_bounds(
450 c.radius,
451 c.radius,
452 lift2(c.frame.x),
453 lift2(c.frame.y),
454 a,
455 b,
456 )),
457 Curve2::Ellipse(e) => Some(conic_bounds(
458 e.semi_axis_x,
459 e.semi_axis_y,
460 lift2(e.frame.x),
461 lift2(e.frame.y),
462 a,
463 b,
464 )),
465 Curve2::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y], a, b).map(|b| b.0),
466 Curve2::Sinusoid(w) => {
469 let amplitude = w.cosine.hypot(w.sine);
470 Some(CurveDerivativeBounds {
471 first: inflate((1.0 + amplitude * amplitude).sqrt()),
472 second: inflate(amplitude),
473 third: inflate(amplitude),
474 })
475 }
476 _ => None,
477 }
478}
479
480fn conic_bounds(
484 rx: Scalar,
485 ry: Scalar,
486 x: Vec3,
487 y: Vec3,
488 a: Scalar,
489 b: Scalar,
490) -> CurveDerivativeBounds {
491 let speed = ((rx * rx * x.length_squared()).max(ry * ry * y.length_squared())
492 + (rx * ry * x.dot(y)).abs())
493 .sqrt();
494 CurveDerivativeBounds {
495 first: inflate(speed),
496 second: inflate(conic_second(rx, ry, x, y, a, b)),
497 third: inflate(speed),
498 }
499}
500
501#[must_use]
509pub fn chord_bound2(curve: &Curve2, a: Scalar, b: Scalar) -> Option<Scalar> {
510 if !(a.is_finite() && b.is_finite()) {
511 return None;
512 }
513 if a == b {
514 return Some(0.0);
515 }
516 if breaks_inside(&continuity_breaks2(curve, 1), a, b) {
517 return None;
518 }
519 match curve {
520 Curve2::Line(_) | Curve2::Polyline(_) => Some(0.0),
521 Curve2::Circle(c) => Some(inflate(
522 circle_sagitta(c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b).unwrap_or_else(
523 || {
524 taylor(
525 b - a,
526 conic_second(c.radius, c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b),
527 )
528 },
529 ),
530 )),
531 Curve2::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y], a, b)
532 .map(|(_, chord)| inflate(taylor(b - a, chord))),
533 _ => curve_derivative_bounds2(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
534 }
535}
536
537#[must_use]
539pub fn chord_bound3(curve: &Curve3, a: Scalar, b: Scalar) -> Option<Scalar> {
540 if !(a.is_finite() && b.is_finite()) {
541 return None;
542 }
543 if a == b {
544 return Some(0.0);
545 }
546 if breaks_inside(&continuity_breaks3(curve, 1), a, b) {
547 return None;
548 }
549 match curve {
550 Curve3::Line(_) | Curve3::Polyline(_) => Some(0.0),
551 Curve3::Circle(c) => Some(inflate(
552 circle_sagitta(c.radius, c.frame.x, c.frame.y, a, b).unwrap_or_else(|| {
553 taylor(
554 b - a,
555 conic_second(c.radius, c.radius, c.frame.x, c.frame.y, a, b),
556 )
557 }),
558 )),
559 Curve3::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b)
560 .map(|(_, chord)| inflate(taylor(b - a, chord))),
561 _ => curve_derivative_bounds3(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
562 }
563}
564
565fn taylor(h: Scalar, second: Scalar) -> Scalar {
567 h * h * 0.125 * second
568}
569
570#[must_use]
577pub fn certifies_flattening2(curve: &Curve2) -> bool {
578 match curve {
579 Curve2::Line(_)
580 | Curve2::Polyline(_)
581 | Curve2::Circle(_)
582 | Curve2::Ellipse(_)
583 | Curve2::Sinusoid(_) => true,
584 Curve2::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
585 _ => false,
586 }
587}
588
589#[must_use]
591pub fn certifies_flattening3(curve: &Curve3) -> bool {
592 match curve {
593 Curve3::Line(_) | Curve3::Polyline(_) | Curve3::Circle(_) | Curve3::Ellipse(_) => true,
594 Curve3::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
595 _ => false,
596 }
597}
598
599fn spline_weights_positive(weights: Option<&[Scalar]>) -> bool {
600 weights.is_none_or(|w| w.iter().all(|w| w.is_finite() && *w > 0.0))
601}
602
603#[derive(Debug, Clone, Copy, PartialEq)]
613pub struct SurfaceBounds {
614 pub du: Scalar,
616 pub dv: Scalar,
618 pub duu: Scalar,
620 pub duv: Scalar,
622 pub dvv: Scalar,
624 pub smooth: bool,
626 pub interpolation: [Scalar; 3],
639}
640
641impl SurfaceBounds {
642 fn inflated(self) -> Self {
643 Self {
644 du: inflate(self.du),
645 dv: inflate(self.dv),
646 duu: inflate(self.duu),
647 duv: inflate(self.duv),
648 dvv: inflate(self.dvv),
649 smooth: self.smooth,
650 interpolation: self.interpolation.map(inflate),
651 }
652 }
653
654 fn finite(self) -> Option<Self> {
655 [self.du, self.dv, self.duu, self.duv, self.dvv]
656 .iter()
657 .chain(&self.interpolation)
658 .all(|v| v.is_finite())
659 .then_some(self)
660 }
661}
662
663#[derive(Debug, Clone)]
668pub struct SurfaceBoundOracle<'a> {
669 surface: &'a Surface,
670 spline: Option<SplineNets>,
671}
672
673#[derive(Debug, Clone)]
675struct SplineNets {
676 u_knots: Vec<Scalar>,
677 v_knots: Vec<Scalar>,
678 p: usize,
679 q: usize,
680 rows: usize,
681 cols: usize,
682 points: Vec<Vec<[Scalar; 3]>>,
683 weights: Vec<Vec<Scalar>>,
684 u_breaks: Vec<Scalar>,
685 v_breaks: Vec<Scalar>,
686}
687
688impl<'a> SurfaceBoundOracle<'a> {
689 #[must_use]
692 pub fn new(surface: &'a Surface) -> Option<Self> {
693 let spline = match surface {
694 Surface::Plane(_)
695 | Surface::Cylinder(_)
696 | Surface::EllipticalCylinder(_)
697 | Surface::Cone(_)
698 | Surface::Sphere(_)
699 | Surface::Torus(_) => None,
700 Surface::BSpline(b) => Some(SplineNets::new(b)?),
701 _ => return None,
702 };
703 Some(Self { surface, spline })
704 }
705
706 #[must_use]
710 pub fn bounds(&self, u: (Scalar, Scalar), v: (Scalar, Scalar)) -> Option<SurfaceBounds> {
711 let (u0, u1) = ordered(u.0, u.1);
712 let (v0, v1) = ordered(v.0, v.1);
713 if ![u0, u1, v0, v1].iter().all(|x| x.is_finite()) {
714 return None;
715 }
716 let raw = match self.surface {
717 Surface::Plane(p) => SurfaceBounds {
718 du: p.frame.x.length(),
719 dv: p.frame.y.length(),
720 duu: 0.0,
721 duv: 0.0,
722 dvv: 0.0,
723 smooth: true,
724 interpolation: [0.0; 3],
725 },
726 Surface::Cylinder(c) => {
727 let s = frame_stretch3(&c.frame);
728 SurfaceBounds {
729 du: s * c.radius.abs(),
730 dv: c.frame.z.length(),
731 duu: s * c.radius.abs(),
732 duv: 0.0,
733 dvv: 0.0,
734 smooth: true,
735 interpolation: [0.0; 3],
736 }
737 }
738 Surface::EllipticalCylinder(c) => {
739 let s = frame_stretch3(&c.frame);
740 let r = c.semi_axis_x.abs().max(c.semi_axis_y.abs());
741 SurfaceBounds {
742 du: s * r,
743 dv: c.frame.z.length(),
744 duu: s * r,
745 duv: 0.0,
746 dvv: 0.0,
747 smooth: true,
748 interpolation: [0.0; 3],
749 }
750 }
751 Surface::Cone(c) => {
752 let s = frame_stretch3(&c.frame);
753 let slope = c.semi_angle.tan();
754 let (r0, r1) = (c.radius + v0 * slope, c.radius + v1 * slope);
755 if r0 < 0.0 || r1 < 0.0 {
756 return None;
757 }
758 let rho = r0.max(r1);
759 SurfaceBounds {
760 du: s * rho,
761 dv: s * (1.0 + slope * slope).sqrt(),
762 duu: s * rho,
763 duv: s * slope.abs(),
764 dvv: 0.0,
765 smooth: true,
766 interpolation: [0.0; 3],
767 }
768 }
769 Surface::Sphere(sphere) => {
770 let s = frame_stretch3(&sphere.frame) * sphere.radius.abs();
771 let cos = max_abs_cos(v0, v1);
772 SurfaceBounds {
773 du: s * cos,
774 dv: s,
775 duu: s * cos,
776 duv: s * max_abs_sin(v0, v1),
777 dvv: s,
778 smooth: true,
779 interpolation: [0.0; 3],
780 }
781 }
782 Surface::Torus(t) => {
783 let s = frame_stretch3(&t.frame);
784 let (cmin, cmax) = cos_range(v0, v1);
785 let r = t.minor_radius.abs();
786 let ring = (t.major_radius + r * cmin)
787 .abs()
788 .max((t.major_radius + r * cmax).abs());
789 SurfaceBounds {
790 du: s * ring,
791 dv: s * r,
792 duu: s * ring,
793 duv: s * r * max_abs_sin(v0, v1),
794 dvv: s * r,
795 smooth: true,
796 interpolation: [0.0; 3],
797 }
798 }
799 Surface::BSpline(_) => self.spline.as_ref()?.bounds(u0, u1, v0, v1)?,
800 _ => return None,
801 };
802 let raw = if matches!(self.surface, Surface::BSpline(_)) {
803 raw
804 } else {
805 SurfaceBounds {
806 interpolation: [raw.duu, raw.duv, raw.dvv],
807 ..raw
808 }
809 };
810 raw.inflated().finite()
811 }
812}
813
814impl SplineNets {
815 fn new(b: &BSplineSurface) -> Option<Self> {
816 let rows = b.control_points.len();
817 let cols = b.control_points.first()?.len();
818 if b.control_points.iter().any(|row| row.len() != cols) {
819 return None;
820 }
821 let ua = SplineAxis::new(&b.u_knots, &b.u_multiplicities, b.u_degree, rows, "u").ok()?;
822 let va = SplineAxis::new(&b.v_knots, &b.v_multiplicities, b.v_degree, cols, "v").ok()?;
823 let weights: Vec<Vec<Scalar>> = match &b.weights {
824 Some(net) => {
825 if net.len() != rows || net.iter().any(|row| row.len() != cols) {
826 return None;
827 }
828 net.clone()
829 }
830 None => vec![vec![1.0; cols]; rows],
831 };
832 if weights
833 .iter()
834 .flatten()
835 .any(|w| !(w.is_finite() && *w > 0.0))
836 {
837 return None;
838 }
839 let points: Vec<Vec<[Scalar; 3]>> = b
840 .control_points
841 .iter()
842 .map(|row| row.iter().map(|p| [p.x, p.y, p.z]).collect())
843 .collect();
844 if points.iter().flatten().flatten().any(|c| !c.is_finite()) {
845 return None;
846 }
847 let breaks = |knots: &[Scalar], mults: &[u32], degree: u16| {
848 let interior = knots.len().saturating_sub(2);
849 knots
850 .iter()
851 .zip(mults)
852 .skip(1)
853 .take(interior)
854 .filter(|(_, &m)| m as usize >= usize::from(degree))
855 .map(|(&k, _)| k)
856 .collect::<Vec<_>>()
857 };
858 Some(Self {
859 u_breaks: breaks(&b.u_knots, &b.u_multiplicities, b.u_degree),
860 v_breaks: breaks(&b.v_knots, &b.v_multiplicities, b.v_degree),
861 u_knots: ua.knots,
862 v_knots: va.knots,
863 p: ua.degree,
864 q: va.degree,
865 rows,
866 cols,
867 points,
868 weights,
869 })
870 }
871
872 #[allow(clippy::needless_range_loop)]
875 fn bounds(&self, u0: Scalar, u1: Scalar, v0: Scalar, v1: Scalar) -> Option<SurfaceBounds> {
876 let clamp = |x: Scalar, knots: &[Scalar], d: usize, n: usize| x.clamp(knots[d], knots[n]);
877 let (cu0, cu1) = (
878 clamp(u0, &self.u_knots, self.p, self.rows),
879 clamp(u1, &self.u_knots, self.p, self.rows),
880 );
881 let (cv0, cv1) = (
882 clamp(v0, &self.v_knots, self.q, self.cols),
883 clamp(v1, &self.v_knots, self.q, self.cols),
884 );
885 let su0 = span_in(&self.u_knots, self.rows, self.p, cu0);
886 let su1 = span_in(&self.u_knots, self.rows, self.p, cu1);
887 let sv0 = span_in(&self.v_knots, self.cols, self.q, cv0);
888 let sv1 = span_in(&self.v_knots, self.cols, self.q, cv1);
889 let (r0, r1) = (su0 - self.p, su1);
890 let (c0, c1) = (sv0 - self.q, sv1);
891
892 let mut low = [Scalar::INFINITY; 3];
894 let mut high = [Scalar::NEG_INFINITY; 3];
895 for row in &self.points[r0..=r1] {
896 for p in &row[c0..=c1] {
897 for k in 0..3 {
898 low[k] = low[k].min(p[k]);
899 high[k] = high[k].max(p[k]);
900 }
901 }
902 }
903 let centre: [Scalar; 3] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
904 let mut radius: Scalar = 0.0;
905 let mut w_min = Scalar::INFINITY;
906 let (wr0, wr1) = (r0, r1.min(self.rows - 1));
908 let (wc0, wc1) = (c0, c1.min(self.cols - 1));
909 let mut hom: Vec<Vec<[Scalar; 4]>> = Vec::with_capacity(wr1 + 1 - wr0);
910 for i in wr0..=wr1 {
911 let mut row = Vec::with_capacity(wc1 + 1 - wc0);
912 for j in wc0..=wc1 {
913 let p = self.points[i][j];
914 let w = self.weights[i][j];
915 let d: [Scalar; 3] = core::array::from_fn(|k| p[k] - centre[k]);
916 radius = radius.max(norm(&d));
917 w_min = w_min.min(w);
918 row.push([d[0] * w, d[1] * w, d[2] * w, w]);
919 }
920 hom.push(row);
921 }
922 let du_net =
925 |net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
926 let mut out = Vec::new();
927 for i in 0..net.len().saturating_sub(1) {
928 let width = knots[offset + i + degree + 1] - knots[offset + i + 1];
929 let f = if width > 0.0 {
930 degree as Scalar / width
931 } else {
932 0.0
933 };
934 out.push(
935 net[i]
936 .iter()
937 .zip(&net[i + 1])
938 .map(|(a, b)| core::array::from_fn(|k| (b[k] - a[k]) * f))
939 .collect::<Vec<[Scalar; 4]>>(),
940 );
941 }
942 out
943 };
944 let dv_net =
945 |net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
946 net.iter()
947 .map(|row| {
948 (0..row.len().saturating_sub(1))
949 .map(|j| {
950 let width = knots[offset + j + degree + 1] - knots[offset + j + 1];
951 let f = if width > 0.0 {
952 degree as Scalar / width
953 } else {
954 0.0
955 };
956 core::array::from_fn(|k| (row[j + 1][k] - row[j][k]) * f)
957 })
958 .collect::<Vec<[Scalar; 4]>>()
959 })
960 .collect::<Vec<_>>()
961 };
962 let sup = |net: &Vec<Vec<[Scalar; 4]>>| {
963 let mut a: Scalar = 0.0;
964 let mut w: Scalar = 0.0;
965 for p in net.iter().flatten() {
966 a = a.max((p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt());
967 w = w.max(p[3].abs());
968 }
969 (a, w)
970 };
971 let (p, q) = (self.p, self.q);
974 let nu = if p >= 1 {
975 du_net(&hom, &self.u_knots, p, wr0)
976 } else {
977 Vec::new()
978 };
979 let nuu = if p >= 2 {
980 du_net(&nu, &self.u_knots, p - 1, wr0 + 1)
981 } else {
982 Vec::new()
983 };
984 let nv = if q >= 1 {
985 dv_net(&hom, &self.v_knots, q, wc0)
986 } else {
987 Vec::new()
988 };
989 let nvv = if q >= 2 {
990 dv_net(&nv, &self.v_knots, q - 1, wc0 + 1)
991 } else {
992 Vec::new()
993 };
994 let nuv = if p >= 1 && q >= 1 {
995 dv_net(&nu, &self.v_knots, q, wc0)
996 } else {
997 Vec::new()
998 };
999 let (au, wu) = sup(&nu);
1000 let (av, wv) = sup(&nv);
1001 let (auu, wuu) = sup(&nuu);
1002 let (avv, wvv) = sup(&nvv);
1003 let (auv, wuv) = sup(&nuv);
1004 if !(w_min.is_finite() && w_min > 0.0) {
1005 return None;
1006 }
1007 let su = (au + wu * radius) / w_min;
1010 let sv = (av + wv * radius) / w_min;
1011 let suu = (auu + 2.0 * wu * su + wuu * radius) / w_min;
1012 let svv = (avv + 2.0 * wv * sv + wvv * radius) / w_min;
1013 let suv = (auv + wu * sv + wv * su + wuv * radius) / w_min;
1014 let inside = cu0 == u0 && cu1 == u1 && cv0 == v0 && cv1 == v1;
1017 let smooth = inside
1018 && !breaks_inside(&self.u_breaks, cu0, cu1)
1019 && !breaks_inside(&self.v_breaks, cv0, cv1);
1020 Some(SurfaceBounds {
1021 du: su,
1022 dv: sv,
1023 duu: suu,
1024 duv: suv,
1025 dvv: svv,
1026 smooth,
1027 interpolation: [
1028 (auu + radius * wuu) / w_min,
1029 (auv + radius * wuv) / w_min,
1030 (avv + radius * wvv) / w_min,
1031 ],
1032 })
1033 }
1034}
1035
1036#[cfg(test)]
1037mod tests;