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 Curve3::Elevated(e) => crate::elevated::elevated_breaks(e, k),
398 Curve3::Banked(b) => crate::elevated::banked_breaks(b, k),
399 _ => Vec::new(),
400 }
401}
402
403fn polyline_breaks(count: usize, closed: bool) -> Vec<Scalar> {
404 let segments = if closed {
405 count
406 } else {
407 count.saturating_sub(1)
408 };
409 (1..segments).map(|k| k as Scalar).collect()
410}
411
412fn breaks_inside(breaks: &[Scalar], a: Scalar, b: Scalar) -> bool {
413 let (lo, hi) = ordered(a, b);
414 breaks.iter().any(|&t| t > lo && t < hi)
415}
416
417#[must_use]
422pub fn curve_derivative_bounds3(
423 curve: &Curve3,
424 a: Scalar,
425 b: Scalar,
426) -> Option<CurveDerivativeBounds> {
427 if !(a.is_finite() && b.is_finite()) {
428 return None;
429 }
430 match curve {
431 Curve3::Line(line) => Some(CurveDerivativeBounds {
432 first: inflate(line.direction.length()),
433 second: 0.0,
434 third: 0.0,
435 }),
436 Curve3::Circle(c) => Some(conic_bounds(c.radius, c.radius, c.frame.x, c.frame.y, a, b)),
437 Curve3::Ellipse(e) => Some(conic_bounds(
438 e.semi_axis_x,
439 e.semi_axis_y,
440 e.frame.x,
441 e.frame.y,
442 a,
443 b,
444 )),
445 Curve3::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b).map(|b| b.0),
446 Curve3::Elevated(e) => crate::elevated::elevated_derivative_bounds(e, a, b),
448 Curve3::Banked(banked) => crate::elevated::banked_derivative_bounds(banked, a, b),
449 _ => None,
450 }
451}
452
453#[must_use]
456pub fn curve_derivative_bounds2(
457 curve: &Curve2,
458 a: Scalar,
459 b: Scalar,
460) -> Option<CurveDerivativeBounds> {
461 if !(a.is_finite() && b.is_finite()) {
462 return None;
463 }
464 match curve {
465 Curve2::Line(line) => Some(CurveDerivativeBounds {
466 first: inflate(line.direction.length()),
467 second: 0.0,
468 third: 0.0,
469 }),
470 Curve2::Circle(c) => Some(conic_bounds(
471 c.radius,
472 c.radius,
473 lift2(c.frame.x),
474 lift2(c.frame.y),
475 a,
476 b,
477 )),
478 Curve2::Ellipse(e) => Some(conic_bounds(
479 e.semi_axis_x,
480 e.semi_axis_y,
481 lift2(e.frame.x),
482 lift2(e.frame.y),
483 a,
484 b,
485 )),
486 Curve2::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y], a, b).map(|b| b.0),
487 Curve2::Sinusoid(w) => {
490 let amplitude = w.cosine.hypot(w.sine);
491 Some(CurveDerivativeBounds {
492 first: inflate((1.0 + amplitude * amplitude).sqrt()),
493 second: inflate(amplitude),
494 third: inflate(amplitude),
495 })
496 }
497 _ => None,
498 }
499}
500
501fn conic_bounds(
505 rx: Scalar,
506 ry: Scalar,
507 x: Vec3,
508 y: Vec3,
509 a: Scalar,
510 b: Scalar,
511) -> CurveDerivativeBounds {
512 let speed = ((rx * rx * x.length_squared()).max(ry * ry * y.length_squared())
513 + (rx * ry * x.dot(y)).abs())
514 .sqrt();
515 CurveDerivativeBounds {
516 first: inflate(speed),
517 second: inflate(conic_second(rx, ry, x, y, a, b)),
518 third: inflate(speed),
519 }
520}
521
522#[must_use]
530pub fn chord_bound2(curve: &Curve2, a: Scalar, b: Scalar) -> Option<Scalar> {
531 if !(a.is_finite() && b.is_finite()) {
532 return None;
533 }
534 if a == b {
535 return Some(0.0);
536 }
537 if breaks_inside(&continuity_breaks2(curve, 1), a, b) {
538 return None;
539 }
540 match curve {
541 Curve2::Line(_) | Curve2::Polyline(_) => Some(0.0),
542 Curve2::Circle(c) => Some(inflate(
543 circle_sagitta(c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b).unwrap_or_else(
544 || {
545 taylor(
546 b - a,
547 conic_second(c.radius, c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b),
548 )
549 },
550 ),
551 )),
552 Curve2::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y], a, b)
553 .map(|(_, chord)| inflate(taylor(b - a, chord))),
554 Curve2::Chain(chain) => crate::chain::chain_chord_bound(chain, a, b),
555 Curve2::Implicit(c) => {
556 let (lo, hi) = ordered(a, b);
557 implicit::chord_bound(c, lo, hi).map(inflate)
558 }
559 _ => curve_derivative_bounds2(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
560 }
561}
562
563#[must_use]
565pub fn chord_bound3(curve: &Curve3, a: Scalar, b: Scalar) -> Option<Scalar> {
566 if !(a.is_finite() && b.is_finite()) {
567 return None;
568 }
569 if a == b {
570 return Some(0.0);
571 }
572 if breaks_inside(&continuity_breaks3(curve, 1), a, b) {
573 return None;
574 }
575 match curve {
576 Curve3::Line(_) | Curve3::Polyline(_) => Some(0.0),
577 Curve3::Circle(c) => Some(inflate(
578 circle_sagitta(c.radius, c.frame.x, c.frame.y, a, b).unwrap_or_else(|| {
579 taylor(
580 b - a,
581 conic_second(c.radius, c.radius, c.frame.x, c.frame.y, a, b),
582 )
583 }),
584 )),
585 Curve3::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b)
586 .map(|(_, chord)| inflate(taylor(b - a, chord))),
587 Curve3::Elevated(e) => crate::elevated::elevated_chord_bound(e, a, b),
590 Curve3::Banked(banked) => crate::elevated::banked_chord_bound(banked, a, b),
591 _ => curve_derivative_bounds3(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
592 }
593}
594
595fn taylor(h: Scalar, second: Scalar) -> Scalar {
597 h * h * 0.125 * second
598}
599
600#[must_use]
608pub fn certifies_flattening2(curve: &Curve2) -> bool {
609 match curve {
610 Curve2::Line(_)
611 | Curve2::Polyline(_)
612 | Curve2::Circle(_)
613 | Curve2::Ellipse(_)
614 | Curve2::Sinusoid(_) => true,
615 Curve2::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
616 Curve2::Chain(c) => crate::chain::chain_certifies(c),
617 _ => false,
618 }
619}
620
621#[must_use]
623pub fn certifies_flattening3(curve: &Curve3) -> bool {
624 match curve {
625 Curve3::Line(_) | Curve3::Polyline(_) | Curve3::Circle(_) | Curve3::Ellipse(_) => true,
626 Curve3::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
627 Curve3::Elevated(e) => crate::elevated::certifies_elevated(e),
633 Curve3::Banked(_) => false,
634 _ => false,
635 }
636}
637
638fn spline_weights_positive(weights: Option<&[Scalar]>) -> bool {
639 weights.is_none_or(|w| w.iter().all(|w| w.is_finite() && *w > 0.0))
640}
641
642#[derive(Debug, Clone, Copy, PartialEq)]
652pub struct SurfaceBounds {
653 pub du: Scalar,
655 pub dv: Scalar,
657 pub duu: Scalar,
659 pub duv: Scalar,
661 pub dvv: Scalar,
663 pub smooth: bool,
665 pub interpolation: [Scalar; 3],
678}
679
680impl SurfaceBounds {
681 fn inflated(self) -> Self {
682 Self {
683 du: inflate(self.du),
684 dv: inflate(self.dv),
685 duu: inflate(self.duu),
686 duv: inflate(self.duv),
687 dvv: inflate(self.dvv),
688 smooth: self.smooth,
689 interpolation: self.interpolation.map(inflate),
690 }
691 }
692
693 fn finite(self) -> Option<Self> {
694 [self.du, self.dv, self.duu, self.duv, self.dvv]
695 .iter()
696 .chain(&self.interpolation)
697 .all(|v| v.is_finite())
698 .then_some(self)
699 }
700}
701
702#[derive(Debug, Clone)]
707pub struct SurfaceBoundOracle<'a> {
708 surface: &'a Surface,
709 spline: Option<SplineNets>,
710}
711
712#[derive(Debug, Clone)]
714struct SplineNets {
715 u_knots: Vec<Scalar>,
716 v_knots: Vec<Scalar>,
717 p: usize,
718 q: usize,
719 rows: usize,
720 cols: usize,
721 points: Vec<Vec<[Scalar; 3]>>,
722 weights: Vec<Vec<Scalar>>,
723 u_breaks: Vec<Scalar>,
724 v_breaks: Vec<Scalar>,
725}
726
727impl<'a> SurfaceBoundOracle<'a> {
728 #[must_use]
731 pub fn new(surface: &'a Surface) -> Option<Self> {
732 let spline = match surface {
733 Surface::Plane(_)
734 | Surface::Cylinder(_)
735 | Surface::EllipticalCylinder(_)
736 | Surface::Cone(_)
737 | Surface::Sphere(_)
738 | Surface::Torus(_) => None,
739 Surface::BSpline(b) => Some(SplineNets::new(b)?),
740 _ => return None,
741 };
742 Some(Self { surface, spline })
743 }
744
745 #[must_use]
749 pub fn bounds(&self, u: (Scalar, Scalar), v: (Scalar, Scalar)) -> Option<SurfaceBounds> {
750 let (u0, u1) = ordered(u.0, u.1);
751 let (v0, v1) = ordered(v.0, v.1);
752 if ![u0, u1, v0, v1].iter().all(|x| x.is_finite()) {
753 return None;
754 }
755 let raw = match self.surface {
756 Surface::Plane(p) => SurfaceBounds {
757 du: p.frame.x.length(),
758 dv: p.frame.y.length(),
759 duu: 0.0,
760 duv: 0.0,
761 dvv: 0.0,
762 smooth: true,
763 interpolation: [0.0; 3],
764 },
765 Surface::Cylinder(c) => {
766 let s = frame_stretch3(&c.frame);
767 SurfaceBounds {
768 du: s * c.radius.abs(),
769 dv: c.frame.z.length(),
770 duu: s * c.radius.abs(),
771 duv: 0.0,
772 dvv: 0.0,
773 smooth: true,
774 interpolation: [0.0; 3],
775 }
776 }
777 Surface::EllipticalCylinder(c) => {
778 let s = frame_stretch3(&c.frame);
779 let r = c.semi_axis_x.abs().max(c.semi_axis_y.abs());
780 SurfaceBounds {
781 du: s * r,
782 dv: c.frame.z.length(),
783 duu: s * r,
784 duv: 0.0,
785 dvv: 0.0,
786 smooth: true,
787 interpolation: [0.0; 3],
788 }
789 }
790 Surface::Cone(c) => {
791 let s = frame_stretch3(&c.frame);
792 let slope = c.semi_angle.tan();
793 let (r0, r1) = (c.radius + v0 * slope, c.radius + v1 * slope);
794 if r0 < 0.0 || r1 < 0.0 {
795 return None;
796 }
797 let rho = r0.max(r1);
798 SurfaceBounds {
799 du: s * rho,
800 dv: s * (1.0 + slope * slope).sqrt(),
801 duu: s * rho,
802 duv: s * slope.abs(),
803 dvv: 0.0,
804 smooth: true,
805 interpolation: [0.0; 3],
806 }
807 }
808 Surface::Sphere(sphere) => {
809 let s = frame_stretch3(&sphere.frame) * sphere.radius.abs();
810 let cos = max_abs_cos(v0, v1);
811 SurfaceBounds {
812 du: s * cos,
813 dv: s,
814 duu: s * cos,
815 duv: s * max_abs_sin(v0, v1),
816 dvv: s,
817 smooth: true,
818 interpolation: [0.0; 3],
819 }
820 }
821 Surface::Torus(t) => {
822 let s = frame_stretch3(&t.frame);
823 let (cmin, cmax) = cos_range(v0, v1);
824 let r = t.minor_radius.abs();
825 let ring = (t.major_radius + r * cmin)
826 .abs()
827 .max((t.major_radius + r * cmax).abs());
828 SurfaceBounds {
829 du: s * ring,
830 dv: s * r,
831 duu: s * ring,
832 duv: s * r * max_abs_sin(v0, v1),
833 dvv: s * r,
834 smooth: true,
835 interpolation: [0.0; 3],
836 }
837 }
838 Surface::BSpline(_) => self.spline.as_ref()?.bounds(u0, u1, v0, v1)?,
839 _ => return None,
840 };
841 let raw = if matches!(self.surface, Surface::BSpline(_)) {
842 raw
843 } else {
844 SurfaceBounds {
845 interpolation: [raw.duu, raw.duv, raw.dvv],
846 ..raw
847 }
848 };
849 raw.inflated().finite()
850 }
851}
852
853impl SplineNets {
854 fn new(b: &BSplineSurface) -> Option<Self> {
855 let rows = b.control_points.len();
856 let cols = b.control_points.first()?.len();
857 if b.control_points.iter().any(|row| row.len() != cols) {
858 return None;
859 }
860 let ua = SplineAxis::new(&b.u_knots, &b.u_multiplicities, b.u_degree, rows, "u").ok()?;
861 let va = SplineAxis::new(&b.v_knots, &b.v_multiplicities, b.v_degree, cols, "v").ok()?;
862 let weights: Vec<Vec<Scalar>> = match &b.weights {
863 Some(net) => {
864 if net.len() != rows || net.iter().any(|row| row.len() != cols) {
865 return None;
866 }
867 net.clone()
868 }
869 None => vec![vec![1.0; cols]; rows],
870 };
871 if weights
872 .iter()
873 .flatten()
874 .any(|w| !(w.is_finite() && *w > 0.0))
875 {
876 return None;
877 }
878 let points: Vec<Vec<[Scalar; 3]>> = b
879 .control_points
880 .iter()
881 .map(|row| row.iter().map(|p| [p.x, p.y, p.z]).collect())
882 .collect();
883 if points.iter().flatten().flatten().any(|c| !c.is_finite()) {
884 return None;
885 }
886 let breaks = |knots: &[Scalar], mults: &[u32], degree: u16| {
887 let interior = knots.len().saturating_sub(2);
888 knots
889 .iter()
890 .zip(mults)
891 .skip(1)
892 .take(interior)
893 .filter(|(_, &m)| m as usize >= usize::from(degree))
894 .map(|(&k, _)| k)
895 .collect::<Vec<_>>()
896 };
897 Some(Self {
898 u_breaks: breaks(&b.u_knots, &b.u_multiplicities, b.u_degree),
899 v_breaks: breaks(&b.v_knots, &b.v_multiplicities, b.v_degree),
900 u_knots: ua.knots,
901 v_knots: va.knots,
902 p: ua.degree,
903 q: va.degree,
904 rows,
905 cols,
906 points,
907 weights,
908 })
909 }
910
911 #[allow(clippy::needless_range_loop)]
914 fn bounds(&self, u0: Scalar, u1: Scalar, v0: Scalar, v1: Scalar) -> Option<SurfaceBounds> {
915 let clamp = |x: Scalar, knots: &[Scalar], d: usize, n: usize| x.clamp(knots[d], knots[n]);
916 let (cu0, cu1) = (
917 clamp(u0, &self.u_knots, self.p, self.rows),
918 clamp(u1, &self.u_knots, self.p, self.rows),
919 );
920 let (cv0, cv1) = (
921 clamp(v0, &self.v_knots, self.q, self.cols),
922 clamp(v1, &self.v_knots, self.q, self.cols),
923 );
924 let su0 = span_in(&self.u_knots, self.rows, self.p, cu0);
925 let su1 = span_in(&self.u_knots, self.rows, self.p, cu1);
926 let sv0 = span_in(&self.v_knots, self.cols, self.q, cv0);
927 let sv1 = span_in(&self.v_knots, self.cols, self.q, cv1);
928 let (r0, r1) = (su0 - self.p, su1);
929 let (c0, c1) = (sv0 - self.q, sv1);
930
931 let mut low = [Scalar::INFINITY; 3];
933 let mut high = [Scalar::NEG_INFINITY; 3];
934 for row in &self.points[r0..=r1] {
935 for p in &row[c0..=c1] {
936 for k in 0..3 {
937 low[k] = low[k].min(p[k]);
938 high[k] = high[k].max(p[k]);
939 }
940 }
941 }
942 let centre: [Scalar; 3] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
943 let mut radius: Scalar = 0.0;
944 let mut w_min = Scalar::INFINITY;
945 let (wr0, wr1) = (r0, r1.min(self.rows - 1));
947 let (wc0, wc1) = (c0, c1.min(self.cols - 1));
948 let mut hom: Vec<Vec<[Scalar; 4]>> = Vec::with_capacity(wr1 + 1 - wr0);
949 for i in wr0..=wr1 {
950 let mut row = Vec::with_capacity(wc1 + 1 - wc0);
951 for j in wc0..=wc1 {
952 let p = self.points[i][j];
953 let w = self.weights[i][j];
954 let d: [Scalar; 3] = core::array::from_fn(|k| p[k] - centre[k]);
955 radius = radius.max(norm(&d));
956 w_min = w_min.min(w);
957 row.push([d[0] * w, d[1] * w, d[2] * w, w]);
958 }
959 hom.push(row);
960 }
961 let du_net =
964 |net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
965 let mut out = Vec::new();
966 for i in 0..net.len().saturating_sub(1) {
967 let width = knots[offset + i + degree + 1] - knots[offset + i + 1];
968 let f = if width > 0.0 {
969 degree as Scalar / width
970 } else {
971 0.0
972 };
973 out.push(
974 net[i]
975 .iter()
976 .zip(&net[i + 1])
977 .map(|(a, b)| core::array::from_fn(|k| (b[k] - a[k]) * f))
978 .collect::<Vec<[Scalar; 4]>>(),
979 );
980 }
981 out
982 };
983 let dv_net =
984 |net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
985 net.iter()
986 .map(|row| {
987 (0..row.len().saturating_sub(1))
988 .map(|j| {
989 let width = knots[offset + j + degree + 1] - knots[offset + j + 1];
990 let f = if width > 0.0 {
991 degree as Scalar / width
992 } else {
993 0.0
994 };
995 core::array::from_fn(|k| (row[j + 1][k] - row[j][k]) * f)
996 })
997 .collect::<Vec<[Scalar; 4]>>()
998 })
999 .collect::<Vec<_>>()
1000 };
1001 let sup = |net: &Vec<Vec<[Scalar; 4]>>| {
1002 let mut a: Scalar = 0.0;
1003 let mut w: Scalar = 0.0;
1004 for p in net.iter().flatten() {
1005 a = a.max((p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt());
1006 w = w.max(p[3].abs());
1007 }
1008 (a, w)
1009 };
1010 let (p, q) = (self.p, self.q);
1013 let nu = if p >= 1 {
1014 du_net(&hom, &self.u_knots, p, wr0)
1015 } else {
1016 Vec::new()
1017 };
1018 let nuu = if p >= 2 {
1019 du_net(&nu, &self.u_knots, p - 1, wr0 + 1)
1020 } else {
1021 Vec::new()
1022 };
1023 let nv = if q >= 1 {
1024 dv_net(&hom, &self.v_knots, q, wc0)
1025 } else {
1026 Vec::new()
1027 };
1028 let nvv = if q >= 2 {
1029 dv_net(&nv, &self.v_knots, q - 1, wc0 + 1)
1030 } else {
1031 Vec::new()
1032 };
1033 let nuv = if p >= 1 && q >= 1 {
1034 dv_net(&nu, &self.v_knots, q, wc0)
1035 } else {
1036 Vec::new()
1037 };
1038 let (au, wu) = sup(&nu);
1039 let (av, wv) = sup(&nv);
1040 let (auu, wuu) = sup(&nuu);
1041 let (avv, wvv) = sup(&nvv);
1042 let (auv, wuv) = sup(&nuv);
1043 if !(w_min.is_finite() && w_min > 0.0) {
1044 return None;
1045 }
1046 let su = (au + wu * radius) / w_min;
1049 let sv = (av + wv * radius) / w_min;
1050 let suu = (auu + 2.0 * wu * su + wuu * radius) / w_min;
1051 let svv = (avv + 2.0 * wv * sv + wvv * radius) / w_min;
1052 let suv = (auv + wu * sv + wv * su + wuv * radius) / w_min;
1053 let inside = cu0 == u0 && cu1 == u1 && cv0 == v0 && cv1 == v1;
1056 let smooth = inside
1057 && !breaks_inside(&self.u_breaks, cu0, cu1)
1058 && !breaks_inside(&self.v_breaks, cv0, cv1);
1059 Some(SurfaceBounds {
1060 du: su,
1061 dv: sv,
1062 duu: suu,
1063 duv: suv,
1064 dvv: svv,
1065 smooth,
1066 interpolation: [
1067 (auu + radius * wuu) / w_min,
1068 (auv + radius * wuv) / w_min,
1069 (avv + radius * wvv) / w_min,
1070 ],
1071 })
1072 }
1073}
1074
1075#[cfg(test)]
1076mod tests;