1use axiolid_core::{Frame2, Frame3, Scalar, Vec2, Vec3};
60use axiolid_curve::{BSplineCurve, Curve2, Curve3};
61use axiolid_surface::{BSplineSurface, Surface};
62
63use crate::curve::span_in;
64use crate::nurbs::SplineAxis;
65
66mod implicit;
67mod intrinsic;
68mod offset;
69mod positions;
70
71pub use intrinsic::{intrinsic_chord_bound2, intrinsic_chord_bound3, intrinsic_derivative_bounds2};
72pub use offset::{offset_chord_bound2, offset_chord_bound3, offset_point2, offset_point3};
73pub use positions::{IntrinsicPositions, MAX_POSITION_PANELS};
74
75pub const ROUNDING: Scalar = 1e-12;
78
79fn inflate(value: Scalar) -> Scalar {
80 value * (1.0 + ROUNDING)
81}
82
83fn gram_stretch(columns: &[Vec3]) -> Scalar {
88 let mut off: Scalar = 0.0;
89 for (i, a) in columns.iter().enumerate() {
90 for (j, b) in columns.iter().enumerate() {
91 let target = if i == j { 1.0 } else { 0.0 };
92 let entry = a.dot(*b) - target;
93 off += entry * entry;
94 }
95 }
96 (1.0 + off.sqrt()).sqrt()
97}
98
99#[must_use]
101pub fn frame_stretch3(frame: &Frame3) -> Scalar {
102 inflate(gram_stretch(&[frame.x, frame.y, frame.z]))
103}
104
105#[must_use]
107pub fn frame_stretch2(frame: &Frame2) -> Scalar {
108 let lift = |v: Vec2| Vec3::new(v.x, v.y, 0.0);
109 inflate(gram_stretch(&[lift(frame.x), lift(frame.y)]))
110}
111
112fn contains_lattice(lo: Scalar, hi: Scalar, offset: Scalar, period: Scalar) -> bool {
116 let k = ((lo - offset) / period).ceil();
117 offset + k * period <= hi
118}
119
120fn max_cos2(lo: Scalar, hi: Scalar) -> Scalar {
122 if contains_lattice(lo, hi, 0.0, core::f64::consts::PI) {
123 1.0
124 } else {
125 lo.cos().powi(2).max(hi.cos().powi(2))
126 }
127}
128
129fn min_cos2(lo: Scalar, hi: Scalar) -> Scalar {
131 if contains_lattice(lo, hi, core::f64::consts::FRAC_PI_2, core::f64::consts::PI) {
132 0.0
133 } else {
134 lo.cos().powi(2).min(hi.cos().powi(2))
135 }
136}
137
138fn max_abs_cos(lo: Scalar, hi: Scalar) -> Scalar {
140 max_cos2(lo, hi).sqrt()
141}
142
143fn max_abs_sin(lo: Scalar, hi: Scalar) -> Scalar {
145 (1.0 - min_cos2(lo, hi)).max(0.0).sqrt()
146}
147
148fn cos_range(lo: Scalar, hi: Scalar) -> (Scalar, Scalar) {
150 let tau = core::f64::consts::TAU;
151 let max = if contains_lattice(lo, hi, 0.0, tau) {
152 1.0
153 } else {
154 lo.cos().max(hi.cos())
155 };
156 let min = if contains_lattice(lo, hi, core::f64::consts::PI, tau) {
157 -1.0
158 } else {
159 lo.cos().min(hi.cos())
160 };
161 (min, max)
162}
163
164fn ordered(a: Scalar, b: Scalar) -> (Scalar, Scalar) {
165 if a <= b {
166 (a, b)
167 } else {
168 (b, a)
169 }
170}
171
172fn conic_second(rx: Scalar, ry: Scalar, x: Vec3, y: Vec3, a: Scalar, b: Scalar) -> Scalar {
178 let (lo, hi) = ordered(a, b);
179 let alpha2 = (rx * rx) * x.length_squared();
180 let beta2 = (ry * ry) * y.length_squared();
181 let cos2 = if alpha2 >= beta2 {
182 max_cos2(lo, hi)
183 } else {
184 min_cos2(lo, hi)
185 };
186 let main = beta2 + (alpha2 - beta2) * cos2;
187 let cross = (rx * ry * x.dot(y)).abs();
188 (main.max(0.0) + cross).sqrt()
189}
190
191fn circle_sagitta(radius: Scalar, x: Vec3, y: Vec3, a: Scalar, b: Scalar) -> Option<Scalar> {
194 let h = (b - a).abs();
195 let orthonormal = (x.length_squared() - 1.0).abs() <= 1e-12
196 && (y.length_squared() - 1.0).abs() <= 1e-12
197 && x.dot(y).abs() <= 1e-12;
198 (orthonormal && h <= core::f64::consts::PI).then(|| radius.abs() * (1.0 - (0.5 * h).cos()))
199}
200
201fn lift2(v: Vec2) -> Vec3 {
202 Vec3::new(v.x, v.y, 0.0)
203}
204
205#[derive(Debug, Clone, Copy, PartialEq)]
214pub struct CurveDerivativeBounds {
215 pub first: Scalar,
217 pub second: Scalar,
219 pub third: Scalar,
221}
222
223fn hodograph<const N: usize>(
226 points: &[[Scalar; N]],
227 weights: &[Scalar],
228 knots: &[Scalar],
229 degree: usize,
230) -> (Vec<[Scalar; N]>, Vec<Scalar>) {
231 let mut out_points = Vec::with_capacity(points.len().saturating_sub(1));
232 let mut out_weights = Vec::with_capacity(weights.len().saturating_sub(1));
233 for i in 0..points.len().saturating_sub(1) {
234 let width = knots[i + degree + 1] - knots[i + 1];
235 let factor = if width > 0.0 {
236 degree as Scalar / width
237 } else {
238 0.0
239 };
240 out_points.push(core::array::from_fn(|k| {
241 (points[i + 1][k] - points[i][k]) * factor
242 }));
243 out_weights.push((weights[i + 1] - weights[i]) * factor);
244 }
245 (out_points, out_weights)
246}
247
248fn norm<const N: usize>(p: &[Scalar; N]) -> Scalar {
249 p.iter().map(|c| c * c).sum::<Scalar>().sqrt()
250}
251
252fn quotient_bounds(a: [Scalar; 5], w: [Scalar; 5], radius: Scalar, w_min: Scalar) -> [Scalar; 4] {
257 let first = (a[1] + w[1] * radius) / w_min;
258 let second = (a[2] + 2.0 * w[1] * first + w[2] * radius) / w_min;
259 let third = (a[3] + 3.0 * w[2] * first + 3.0 * w[1] * second + w[3] * radius) / w_min;
260 let fourth =
261 (a[4] + 4.0 * w[3] * first + 6.0 * w[2] * second + 4.0 * w[1] * third + w[4] * radius)
262 / w_min;
263 [first, second, third, fourth]
264}
265
266fn bspline_bounds<P, const N: usize>(
280 curve: &BSplineCurve<P>,
281 to: impl Fn(&P) -> [Scalar; N],
282 a: Scalar,
283 b: Scalar,
284) -> Option<(CurveDerivativeBounds, Scalar, Scalar)> {
285 let count = curve.control_points.len();
286 let axis = SplineAxis::new(
287 &curve.knots,
288 &curve.multiplicities,
289 curve.degree,
290 count,
291 "B-spline curve",
292 )
293 .ok()?;
294 let weights: Vec<Scalar> = match &curve.weights {
295 Some(w) if w.len() == count => w.clone(),
296 Some(_) => return None,
297 None => vec![1.0; count],
298 };
299 if weights.iter().any(|w| !(w.is_finite() && *w > 0.0)) {
300 return None;
301 }
302 let degree = axis.degree;
303 let (lo, hi) = ordered(axis.clamp(a), axis.clamp(b));
304 let first_span = span_in(&axis.knots, count, degree, lo);
305 let last_span = span_in(&axis.knots, count, degree, hi);
306 let window = first_span - degree..=last_span;
309 let points: Vec<[Scalar; N]> = curve.control_points.iter().map(&to).collect();
310 if points.iter().flatten().any(|c| !c.is_finite()) {
311 return None;
312 }
313 let mut low = [Scalar::INFINITY; N];
316 let mut high = [Scalar::NEG_INFINITY; N];
317 for p in &points[window.clone()] {
318 for k in 0..N {
319 low[k] = low[k].min(p[k]);
320 high[k] = high[k].max(p[k]);
321 }
322 }
323 let centre: [Scalar; N] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
324 let radius = points[window.clone()]
325 .iter()
326 .map(|p| norm(&core::array::from_fn::<Scalar, N, _>(|k| p[k] - centre[k])))
327 .fold(0.0, Scalar::max);
328 let w_min = weights[window.clone()]
329 .iter()
330 .copied()
331 .fold(Scalar::INFINITY, Scalar::min);
332 let mut hom: Vec<[Scalar; N]> = points
333 .iter()
334 .zip(&weights)
335 .map(|(p, w)| core::array::from_fn(|k| (p[k] - centre[k]) * w))
336 .collect();
337 let mut hw = weights.clone();
338 let mut knots: &[Scalar] = &axis.knots;
339 let mut a_sup = [0.0; 5];
340 let mut w_sup = [0.0; 5];
341 for order in 1..=4 {
342 let current_degree = degree + 1 - order;
343 if current_degree == 0 || hom.len() < 2 {
344 break;
345 }
346 let (next, next_w) = hodograph(&hom, &hw, knots, current_degree);
347 knots = &knots[1..knots.len() - 1];
348 let end = (*window.end()).min(next.len().saturating_sub(1));
349 let start = (*window.start()).min(end);
350 a_sup[order] = next[start..=end].iter().map(norm).fold(0.0, Scalar::max);
351 w_sup[order] = next_w[start..=end]
352 .iter()
353 .map(|w| w.abs())
354 .fold(0.0, Scalar::max);
355 hom = next;
356 hw = next_w;
357 }
358 let [first, second, third, fourth] = quotient_bounds(a_sup, w_sup, radius, w_min);
359 let bounds = CurveDerivativeBounds {
360 first: inflate(first),
361 second: inflate(second),
362 third: inflate(third),
363 };
364 let chord = inflate((a_sup[2] + radius * w_sup[2]) / w_min).min(bounds.second);
365 (bounds.first.is_finite()
366 && bounds.second.is_finite()
367 && bounds.third.is_finite()
368 && chord.is_finite())
369 .then_some((bounds, chord, inflate(fourth)))
370}
371
372fn bspline_breaks<P>(curve: &BSplineCurve<P>, k: usize) -> Vec<Scalar> {
375 let degree = usize::from(curve.degree);
376 let interior = curve.knots.len().saturating_sub(2);
377 curve
378 .knots
379 .iter()
380 .zip(&curve.multiplicities)
381 .skip(1)
382 .take(interior)
383 .filter(|(_, &m)| (m as usize) + k > degree)
384 .map(|(&knot, _)| knot)
385 .collect()
386}
387
388#[must_use]
394pub fn continuity_breaks2(curve: &Curve2, k: usize) -> Vec<Scalar> {
395 match curve {
396 Curve2::BSpline(b) => bspline_breaks(b, k),
397 Curve2::Polyline(p) if k >= 1 => polyline_breaks(p.points.len(), p.closed),
398 Curve2::Chain(c) if k >= 1 => c.joins().unwrap_or_default(),
402 Curve2::Implicit(c) if k >= 1 => implicit::joins(c),
404 _ => Vec::new(),
405 }
406}
407
408#[must_use]
410pub fn continuity_breaks3(curve: &Curve3, k: usize) -> Vec<Scalar> {
411 match curve {
412 Curve3::BSpline(b) => bspline_breaks(b, k),
413 Curve3::Polyline(p) if k >= 1 => polyline_breaks(p.points.len(), p.closed),
414 Curve3::Elevated(e) => crate::elevated::elevated_breaks(e, k),
417 Curve3::Banked(b) => crate::elevated::banked_breaks(b, k),
418 _ => Vec::new(),
419 }
420}
421
422fn polyline_breaks(count: usize, closed: bool) -> Vec<Scalar> {
423 let segments = if closed {
424 count
425 } else {
426 count.saturating_sub(1)
427 };
428 (1..segments).map(|k| k as Scalar).collect()
429}
430
431fn breaks_inside(breaks: &[Scalar], a: Scalar, b: Scalar) -> bool {
432 let (lo, hi) = ordered(a, b);
433 breaks.iter().any(|&t| t > lo && t < hi)
434}
435
436#[must_use]
441pub fn curve_derivative_bounds3(
442 curve: &Curve3,
443 a: Scalar,
444 b: Scalar,
445) -> Option<CurveDerivativeBounds> {
446 if !(a.is_finite() && b.is_finite()) {
447 return None;
448 }
449 match curve {
450 Curve3::Line(line) => Some(CurveDerivativeBounds {
451 first: inflate(line.direction.length()),
452 second: 0.0,
453 third: 0.0,
454 }),
455 Curve3::Circle(c) => Some(conic_bounds(c.radius, c.radius, c.frame.x, c.frame.y, a, b)),
456 Curve3::Ellipse(e) => Some(conic_bounds(
457 e.semi_axis_x,
458 e.semi_axis_y,
459 e.frame.x,
460 e.frame.y,
461 a,
462 b,
463 )),
464 Curve3::BSpline(spline) => {
465 bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b).map(|(bounds, ..)| bounds)
466 }
467 Curve3::Elevated(e) => crate::elevated::elevated_derivative_bounds(e, a, b),
469 Curve3::Banked(banked) => crate::elevated::banked_derivative_bounds(banked, a, b),
470 _ => None,
471 }
472}
473
474#[must_use]
477pub fn curve_derivative_bounds2(
478 curve: &Curve2,
479 a: Scalar,
480 b: Scalar,
481) -> Option<CurveDerivativeBounds> {
482 if !(a.is_finite() && b.is_finite()) {
483 return None;
484 }
485 match curve {
486 Curve2::Line(line) => Some(CurveDerivativeBounds {
487 first: inflate(line.direction.length()),
488 second: 0.0,
489 third: 0.0,
490 }),
491 Curve2::Circle(c) => Some(conic_bounds(
492 c.radius,
493 c.radius,
494 lift2(c.frame.x),
495 lift2(c.frame.y),
496 a,
497 b,
498 )),
499 Curve2::Ellipse(e) => Some(conic_bounds(
500 e.semi_axis_x,
501 e.semi_axis_y,
502 lift2(e.frame.x),
503 lift2(e.frame.y),
504 a,
505 b,
506 )),
507 Curve2::BSpline(spline) => {
508 bspline_bounds(spline, |p| [p.x, p.y], a, b).map(|(bounds, ..)| bounds)
509 }
510 Curve2::Sinusoid(w) => {
513 let amplitude = w.cosine.hypot(w.sine);
514 Some(CurveDerivativeBounds {
515 first: inflate((1.0 + amplitude * amplitude).sqrt()),
516 second: inflate(amplitude),
517 third: inflate(amplitude),
518 })
519 }
520 _ => None,
521 }
522}
523
524#[must_use]
529pub(crate) fn curve_fourth_bound3(curve: &Curve3, a: Scalar, b: Scalar) -> Option<Scalar> {
530 if !(a.is_finite() && b.is_finite()) {
531 return None;
532 }
533 match curve {
534 Curve3::Line(_) => Some(0.0),
535 Curve3::Circle(c) => Some(inflate(conic_second(
536 c.radius, c.radius, c.frame.x, c.frame.y, a, b,
537 ))),
538 Curve3::Ellipse(e) => Some(inflate(conic_second(
539 e.semi_axis_x,
540 e.semi_axis_y,
541 e.frame.x,
542 e.frame.y,
543 a,
544 b,
545 ))),
546 Curve3::BSpline(spline) => {
547 bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b).map(|(.., fourth)| fourth)
548 }
549 _ => None,
550 }
551}
552
553#[must_use]
555pub(crate) fn curve_fourth_bound2(curve: &Curve2, a: Scalar, b: Scalar) -> Option<Scalar> {
556 if !(a.is_finite() && b.is_finite()) {
557 return None;
558 }
559 match curve {
560 Curve2::Line(_) => Some(0.0),
561 Curve2::Circle(c) => Some(inflate(conic_second(
562 c.radius,
563 c.radius,
564 lift2(c.frame.x),
565 lift2(c.frame.y),
566 a,
567 b,
568 ))),
569 Curve2::Ellipse(e) => Some(inflate(conic_second(
570 e.semi_axis_x,
571 e.semi_axis_y,
572 lift2(e.frame.x),
573 lift2(e.frame.y),
574 a,
575 b,
576 ))),
577 Curve2::BSpline(spline) => {
578 bspline_bounds(spline, |p| [p.x, p.y], a, b).map(|(.., fourth)| fourth)
579 }
580 _ => None,
581 }
582}
583
584fn conic_bounds(
588 rx: Scalar,
589 ry: Scalar,
590 x: Vec3,
591 y: Vec3,
592 a: Scalar,
593 b: Scalar,
594) -> CurveDerivativeBounds {
595 let speed = ((rx * rx * x.length_squared()).max(ry * ry * y.length_squared())
596 + (rx * ry * x.dot(y)).abs())
597 .sqrt();
598 CurveDerivativeBounds {
599 first: inflate(speed),
600 second: inflate(conic_second(rx, ry, x, y, a, b)),
601 third: inflate(speed),
602 }
603}
604
605#[must_use]
613pub fn chord_bound2(curve: &Curve2, a: Scalar, b: Scalar) -> Option<Scalar> {
614 if !(a.is_finite() && b.is_finite()) {
615 return None;
616 }
617 if a == b {
618 return Some(0.0);
619 }
620 if breaks_inside(&continuity_breaks2(curve, 1), a, b) {
621 return None;
622 }
623 match curve {
624 Curve2::Line(_) | Curve2::Polyline(_) => Some(0.0),
625 Curve2::Circle(c) => Some(inflate(
626 circle_sagitta(c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b).unwrap_or_else(
627 || {
628 taylor(
629 b - a,
630 conic_second(c.radius, c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b),
631 )
632 },
633 ),
634 )),
635 Curve2::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y], a, b)
636 .map(|(_, chord, _)| inflate(taylor(b - a, chord))),
637 Curve2::Chain(chain) => crate::chain::chain_chord_bound(chain, a, b),
638 Curve2::Implicit(c) => {
639 let (lo, hi) = ordered(a, b);
640 implicit::chord_bound(c, lo, hi).map(inflate)
641 }
642 _ => curve_derivative_bounds2(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
643 }
644}
645
646#[must_use]
648pub fn chord_bound3(curve: &Curve3, a: Scalar, b: Scalar) -> Option<Scalar> {
649 if !(a.is_finite() && b.is_finite()) {
650 return None;
651 }
652 if a == b {
653 return Some(0.0);
654 }
655 if breaks_inside(&continuity_breaks3(curve, 1), a, b) {
656 return None;
657 }
658 match curve {
659 Curve3::Line(_) | Curve3::Polyline(_) => Some(0.0),
660 Curve3::Circle(c) => Some(inflate(
661 circle_sagitta(c.radius, c.frame.x, c.frame.y, a, b).unwrap_or_else(|| {
662 taylor(
663 b - a,
664 conic_second(c.radius, c.radius, c.frame.x, c.frame.y, a, b),
665 )
666 }),
667 )),
668 Curve3::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b)
669 .map(|(_, chord, _)| inflate(taylor(b - a, chord))),
670 Curve3::Elevated(e) => crate::elevated::elevated_chord_bound(e, a, b),
673 Curve3::Banked(banked) => crate::elevated::banked_chord_bound(banked, a, b),
674 _ => curve_derivative_bounds3(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
675 }
676}
677
678fn taylor(h: Scalar, second: Scalar) -> Scalar {
680 h * h * 0.125 * second
681}
682
683#[must_use]
691pub fn certifies_flattening2(curve: &Curve2) -> bool {
692 match curve {
693 Curve2::Line(_)
694 | Curve2::Polyline(_)
695 | Curve2::Circle(_)
696 | Curve2::Ellipse(_)
697 | Curve2::Sinusoid(_) => true,
698 Curve2::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
699 Curve2::Chain(c) => crate::chain::chain_certifies(c),
700 _ => false,
701 }
702}
703
704#[must_use]
706pub fn certifies_flattening3(curve: &Curve3) -> bool {
707 match curve {
708 Curve3::Line(_) | Curve3::Polyline(_) | Curve3::Circle(_) | Curve3::Ellipse(_) => true,
709 Curve3::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
710 Curve3::Elevated(e) => crate::elevated::certifies_elevated(e),
716 Curve3::Banked(_) => false,
717 _ => false,
718 }
719}
720
721fn spline_weights_positive(weights: Option<&[Scalar]>) -> bool {
722 weights.is_none_or(|w| w.iter().all(|w| w.is_finite() && *w > 0.0))
723}
724
725#[derive(Debug, Clone, Copy, PartialEq)]
735pub struct SurfaceBounds {
736 pub du: Scalar,
738 pub dv: Scalar,
740 pub duu: Scalar,
742 pub duv: Scalar,
744 pub dvv: Scalar,
746 pub smooth: bool,
748 pub interpolation: [Scalar; 3],
761}
762
763impl SurfaceBounds {
764 fn inflated(self) -> Self {
765 Self {
766 du: inflate(self.du),
767 dv: inflate(self.dv),
768 duu: inflate(self.duu),
769 duv: inflate(self.duv),
770 dvv: inflate(self.dvv),
771 smooth: self.smooth,
772 interpolation: self.interpolation.map(inflate),
773 }
774 }
775
776 fn finite(self) -> Option<Self> {
777 [self.du, self.dv, self.duu, self.duv, self.dvv]
778 .iter()
779 .chain(&self.interpolation)
780 .all(|v| v.is_finite())
781 .then_some(self)
782 }
783}
784
785#[derive(Debug, Clone)]
790pub struct SurfaceBoundOracle<'a> {
791 surface: &'a Surface,
792 spline: Option<SplineNets>,
793}
794
795#[derive(Debug, Clone)]
797struct SplineNets {
798 u_knots: Vec<Scalar>,
799 v_knots: Vec<Scalar>,
800 p: usize,
801 q: usize,
802 rows: usize,
803 cols: usize,
804 points: Vec<Vec<[Scalar; 3]>>,
805 weights: Vec<Vec<Scalar>>,
806 u_breaks: Vec<Scalar>,
807 v_breaks: Vec<Scalar>,
808}
809
810impl<'a> SurfaceBoundOracle<'a> {
811 #[must_use]
814 pub fn new(surface: &'a Surface) -> Option<Self> {
815 let spline = match surface {
816 Surface::Plane(_)
817 | Surface::Cylinder(_)
818 | Surface::EllipticalCylinder(_)
819 | Surface::Cone(_)
820 | Surface::Sphere(_)
821 | Surface::Torus(_) => None,
822 Surface::BSpline(b) => Some(SplineNets::new(b)?),
823 _ => return None,
824 };
825 Some(Self { surface, spline })
826 }
827
828 #[must_use]
832 pub fn bounds(&self, u: (Scalar, Scalar), v: (Scalar, Scalar)) -> Option<SurfaceBounds> {
833 let (u0, u1) = ordered(u.0, u.1);
834 let (v0, v1) = ordered(v.0, v.1);
835 if ![u0, u1, v0, v1].iter().all(|x| x.is_finite()) {
836 return None;
837 }
838 let raw = match self.surface {
839 Surface::Plane(p) => SurfaceBounds {
840 du: p.frame.x.length(),
841 dv: p.frame.y.length(),
842 duu: 0.0,
843 duv: 0.0,
844 dvv: 0.0,
845 smooth: true,
846 interpolation: [0.0; 3],
847 },
848 Surface::Cylinder(c) => {
849 let s = frame_stretch3(&c.frame);
850 SurfaceBounds {
851 du: s * c.radius.abs(),
852 dv: c.frame.z.length(),
853 duu: s * c.radius.abs(),
854 duv: 0.0,
855 dvv: 0.0,
856 smooth: true,
857 interpolation: [0.0; 3],
858 }
859 }
860 Surface::EllipticalCylinder(c) => {
861 let s = frame_stretch3(&c.frame);
862 let r = c.semi_axis_x.abs().max(c.semi_axis_y.abs());
863 SurfaceBounds {
864 du: s * r,
865 dv: c.frame.z.length(),
866 duu: s * r,
867 duv: 0.0,
868 dvv: 0.0,
869 smooth: true,
870 interpolation: [0.0; 3],
871 }
872 }
873 Surface::Cone(c) => {
874 let s = frame_stretch3(&c.frame);
875 let slope = c.semi_angle.tan();
876 let (r0, r1) = (c.radius + v0 * slope, c.radius + v1 * slope);
877 if r0 < 0.0 || r1 < 0.0 {
878 return None;
879 }
880 let rho = r0.max(r1);
881 SurfaceBounds {
882 du: s * rho,
883 dv: s * (1.0 + slope * slope).sqrt(),
884 duu: s * rho,
885 duv: s * slope.abs(),
886 dvv: 0.0,
887 smooth: true,
888 interpolation: [0.0; 3],
889 }
890 }
891 Surface::Sphere(sphere) => {
892 let s = frame_stretch3(&sphere.frame) * sphere.radius.abs();
893 let cos = max_abs_cos(v0, v1);
894 SurfaceBounds {
895 du: s * cos,
896 dv: s,
897 duu: s * cos,
898 duv: s * max_abs_sin(v0, v1),
899 dvv: s,
900 smooth: true,
901 interpolation: [0.0; 3],
902 }
903 }
904 Surface::Torus(t) => {
905 let s = frame_stretch3(&t.frame);
906 let (cmin, cmax) = cos_range(v0, v1);
907 let r = t.minor_radius.abs();
908 let ring = (t.major_radius + r * cmin)
909 .abs()
910 .max((t.major_radius + r * cmax).abs());
911 SurfaceBounds {
912 du: s * ring,
913 dv: s * r,
914 duu: s * ring,
915 duv: s * r * max_abs_sin(v0, v1),
916 dvv: s * r,
917 smooth: true,
918 interpolation: [0.0; 3],
919 }
920 }
921 Surface::BSpline(_) => self.spline.as_ref()?.bounds(u0, u1, v0, v1)?,
922 _ => return None,
923 };
924 let raw = if matches!(self.surface, Surface::BSpline(_)) {
925 raw
926 } else {
927 SurfaceBounds {
928 interpolation: [raw.duu, raw.duv, raw.dvv],
929 ..raw
930 }
931 };
932 raw.inflated().finite()
933 }
934}
935
936impl SplineNets {
937 fn new(b: &BSplineSurface) -> Option<Self> {
938 let rows = b.control_points.len();
939 let cols = b.control_points.first()?.len();
940 if b.control_points.iter().any(|row| row.len() != cols) {
941 return None;
942 }
943 let ua = SplineAxis::new(&b.u_knots, &b.u_multiplicities, b.u_degree, rows, "u").ok()?;
944 let va = SplineAxis::new(&b.v_knots, &b.v_multiplicities, b.v_degree, cols, "v").ok()?;
945 let weights: Vec<Vec<Scalar>> = match &b.weights {
946 Some(net) => {
947 if net.len() != rows || net.iter().any(|row| row.len() != cols) {
948 return None;
949 }
950 net.clone()
951 }
952 None => vec![vec![1.0; cols]; rows],
953 };
954 if weights
955 .iter()
956 .flatten()
957 .any(|w| !(w.is_finite() && *w > 0.0))
958 {
959 return None;
960 }
961 let points: Vec<Vec<[Scalar; 3]>> = b
962 .control_points
963 .iter()
964 .map(|row| row.iter().map(|p| [p.x, p.y, p.z]).collect())
965 .collect();
966 if points.iter().flatten().flatten().any(|c| !c.is_finite()) {
967 return None;
968 }
969 let breaks = |knots: &[Scalar], mults: &[u32], degree: u16| {
970 let interior = knots.len().saturating_sub(2);
971 knots
972 .iter()
973 .zip(mults)
974 .skip(1)
975 .take(interior)
976 .filter(|(_, &m)| m as usize >= usize::from(degree))
977 .map(|(&k, _)| k)
978 .collect::<Vec<_>>()
979 };
980 Some(Self {
981 u_breaks: breaks(&b.u_knots, &b.u_multiplicities, b.u_degree),
982 v_breaks: breaks(&b.v_knots, &b.v_multiplicities, b.v_degree),
983 u_knots: ua.knots,
984 v_knots: va.knots,
985 p: ua.degree,
986 q: va.degree,
987 rows,
988 cols,
989 points,
990 weights,
991 })
992 }
993
994 #[allow(clippy::needless_range_loop)]
997 fn bounds(&self, u0: Scalar, u1: Scalar, v0: Scalar, v1: Scalar) -> Option<SurfaceBounds> {
998 let clamp = |x: Scalar, knots: &[Scalar], d: usize, n: usize| x.clamp(knots[d], knots[n]);
999 let (cu0, cu1) = (
1000 clamp(u0, &self.u_knots, self.p, self.rows),
1001 clamp(u1, &self.u_knots, self.p, self.rows),
1002 );
1003 let (cv0, cv1) = (
1004 clamp(v0, &self.v_knots, self.q, self.cols),
1005 clamp(v1, &self.v_knots, self.q, self.cols),
1006 );
1007 let su0 = span_in(&self.u_knots, self.rows, self.p, cu0);
1008 let su1 = span_in(&self.u_knots, self.rows, self.p, cu1);
1009 let sv0 = span_in(&self.v_knots, self.cols, self.q, cv0);
1010 let sv1 = span_in(&self.v_knots, self.cols, self.q, cv1);
1011 let (r0, r1) = (su0 - self.p, su1);
1012 let (c0, c1) = (sv0 - self.q, sv1);
1013
1014 let mut low = [Scalar::INFINITY; 3];
1016 let mut high = [Scalar::NEG_INFINITY; 3];
1017 for row in &self.points[r0..=r1] {
1018 for p in &row[c0..=c1] {
1019 for k in 0..3 {
1020 low[k] = low[k].min(p[k]);
1021 high[k] = high[k].max(p[k]);
1022 }
1023 }
1024 }
1025 let centre: [Scalar; 3] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
1026 let mut radius: Scalar = 0.0;
1027 let mut w_min = Scalar::INFINITY;
1028 let (wr0, wr1) = (r0, r1.min(self.rows - 1));
1030 let (wc0, wc1) = (c0, c1.min(self.cols - 1));
1031 let mut hom: Vec<Vec<[Scalar; 4]>> = Vec::with_capacity(wr1 + 1 - wr0);
1032 for i in wr0..=wr1 {
1033 let mut row = Vec::with_capacity(wc1 + 1 - wc0);
1034 for j in wc0..=wc1 {
1035 let p = self.points[i][j];
1036 let w = self.weights[i][j];
1037 let d: [Scalar; 3] = core::array::from_fn(|k| p[k] - centre[k]);
1038 radius = radius.max(norm(&d));
1039 w_min = w_min.min(w);
1040 row.push([d[0] * w, d[1] * w, d[2] * w, w]);
1041 }
1042 hom.push(row);
1043 }
1044 let du_net =
1047 |net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
1048 let mut out = Vec::new();
1049 for i in 0..net.len().saturating_sub(1) {
1050 let width = knots[offset + i + degree + 1] - knots[offset + i + 1];
1051 let f = if width > 0.0 {
1052 degree as Scalar / width
1053 } else {
1054 0.0
1055 };
1056 out.push(
1057 net[i]
1058 .iter()
1059 .zip(&net[i + 1])
1060 .map(|(a, b)| core::array::from_fn(|k| (b[k] - a[k]) * f))
1061 .collect::<Vec<[Scalar; 4]>>(),
1062 );
1063 }
1064 out
1065 };
1066 let dv_net =
1067 |net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
1068 net.iter()
1069 .map(|row| {
1070 (0..row.len().saturating_sub(1))
1071 .map(|j| {
1072 let width = knots[offset + j + degree + 1] - knots[offset + j + 1];
1073 let f = if width > 0.0 {
1074 degree as Scalar / width
1075 } else {
1076 0.0
1077 };
1078 core::array::from_fn(|k| (row[j + 1][k] - row[j][k]) * f)
1079 })
1080 .collect::<Vec<[Scalar; 4]>>()
1081 })
1082 .collect::<Vec<_>>()
1083 };
1084 let sup = |net: &Vec<Vec<[Scalar; 4]>>| {
1085 let mut a: Scalar = 0.0;
1086 let mut w: Scalar = 0.0;
1087 for p in net.iter().flatten() {
1088 a = a.max((p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt());
1089 w = w.max(p[3].abs());
1090 }
1091 (a, w)
1092 };
1093 let (p, q) = (self.p, self.q);
1096 let nu = if p >= 1 {
1097 du_net(&hom, &self.u_knots, p, wr0)
1098 } else {
1099 Vec::new()
1100 };
1101 let nuu = if p >= 2 {
1102 du_net(&nu, &self.u_knots, p - 1, wr0 + 1)
1103 } else {
1104 Vec::new()
1105 };
1106 let nv = if q >= 1 {
1107 dv_net(&hom, &self.v_knots, q, wc0)
1108 } else {
1109 Vec::new()
1110 };
1111 let nvv = if q >= 2 {
1112 dv_net(&nv, &self.v_knots, q - 1, wc0 + 1)
1113 } else {
1114 Vec::new()
1115 };
1116 let nuv = if p >= 1 && q >= 1 {
1117 dv_net(&nu, &self.v_knots, q, wc0)
1118 } else {
1119 Vec::new()
1120 };
1121 let (au, wu) = sup(&nu);
1122 let (av, wv) = sup(&nv);
1123 let (auu, wuu) = sup(&nuu);
1124 let (avv, wvv) = sup(&nvv);
1125 let (auv, wuv) = sup(&nuv);
1126 if !(w_min.is_finite() && w_min > 0.0) {
1127 return None;
1128 }
1129 let su = (au + wu * radius) / w_min;
1132 let sv = (av + wv * radius) / w_min;
1133 let suu = (auu + 2.0 * wu * su + wuu * radius) / w_min;
1134 let svv = (avv + 2.0 * wv * sv + wvv * radius) / w_min;
1135 let suv = (auv + wu * sv + wv * su + wuv * radius) / w_min;
1136 let inside = cu0 == u0 && cu1 == u1 && cv0 == v0 && cv1 == v1;
1139 let smooth = inside
1140 && !breaks_inside(&self.u_breaks, cu0, cu1)
1141 && !breaks_inside(&self.v_breaks, cv0, cv1);
1142 Some(SurfaceBounds {
1143 du: su,
1144 dv: sv,
1145 duu: suu,
1146 duv: suv,
1147 dvv: svv,
1148 smooth,
1149 interpolation: [
1150 (auu + radius * wuu) / w_min,
1151 (auv + radius * wuv) / w_min,
1152 (avv + radius * wvv) / w_min,
1153 ],
1154 })
1155 }
1156}
1157
1158#[cfg(test)]
1159mod tests;