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