1use axiolid_core::{Frame2, Frame3, Scalar, Vec2, Vec3};
84use axiolid_curve::{BSplineCurve, Carrier, Curve2, Curve3};
85use axiolid_surface::{BSplineSurface, Surface};
86
87use crate::curve::span_in;
88use crate::nurbs::SplineAxis;
89
90mod carrier;
91mod chain_positions;
92mod continuation;
93mod elevated_positions;
94mod enclosure;
95mod graph;
96mod implicit;
97mod interval;
98mod intrinsic;
99mod lifted;
100mod offset;
101mod pair;
102mod position_table;
103mod positions;
104mod section;
105mod separation;
106
107pub(crate) use chain_positions::evaluated_chain_chord_bound;
108pub use chain_positions::{ChainPositionTable, SeamSide};
109pub use elevated_positions::ElevatedPositionTable;
110pub use intrinsic::{intrinsic_chord_bound2, intrinsic_chord_bound3, intrinsic_derivative_bounds2};
111pub use offset::{offset_chord_bound2, offset_chord_bound3, offset_point2, offset_point3};
112pub use position_table::{IntrinsicPositionTable, MAX_TABLE_PANELS};
113pub use positions::{IntrinsicPositions, MAX_POSITION_PANELS};
114pub use separation::ParameterSeparation;
115
116pub const ROUNDING: Scalar = 1e-12;
119
120fn inflate(value: Scalar) -> Scalar {
121 value * (1.0 + ROUNDING)
122}
123
124fn gram_stretch(columns: &[Vec3]) -> Scalar {
129 let mut off: Scalar = 0.0;
130 for (i, a) in columns.iter().enumerate() {
131 for (j, b) in columns.iter().enumerate() {
132 let target = if i == j { 1.0 } else { 0.0 };
133 let entry = a.dot(*b) - target;
134 off += entry * entry;
135 }
136 }
137 (1.0 + off.sqrt()).sqrt()
138}
139
140#[must_use]
142pub fn frame_stretch3(frame: &Frame3) -> Scalar {
143 inflate(gram_stretch(&[frame.x, frame.y, frame.z]))
144}
145
146#[must_use]
148pub fn frame_stretch2(frame: &Frame2) -> Scalar {
149 let lift = |v: Vec2| Vec3::new(v.x, v.y, 0.0);
150 inflate(gram_stretch(&[lift(frame.x), lift(frame.y)]))
151}
152
153fn contains_lattice(lo: Scalar, hi: Scalar, offset: Scalar, period: Scalar) -> bool {
157 let k = ((lo - offset) / period).ceil();
158 offset + k * period <= hi
159}
160
161fn max_cos2(lo: Scalar, hi: Scalar) -> Scalar {
163 if contains_lattice(lo, hi, 0.0, core::f64::consts::PI) {
164 1.0
165 } else {
166 lo.cos().powi(2).max(hi.cos().powi(2))
167 }
168}
169
170fn min_cos2(lo: Scalar, hi: Scalar) -> Scalar {
172 if contains_lattice(lo, hi, core::f64::consts::FRAC_PI_2, core::f64::consts::PI) {
173 0.0
174 } else {
175 lo.cos().powi(2).min(hi.cos().powi(2))
176 }
177}
178
179fn max_abs_cos(lo: Scalar, hi: Scalar) -> Scalar {
181 max_cos2(lo, hi).sqrt()
182}
183
184fn max_abs_sin(lo: Scalar, hi: Scalar) -> Scalar {
186 (1.0 - min_cos2(lo, hi)).max(0.0).sqrt()
187}
188
189fn cos_range(lo: Scalar, hi: Scalar) -> (Scalar, Scalar) {
191 let tau = core::f64::consts::TAU;
192 let max = if contains_lattice(lo, hi, 0.0, tau) {
193 1.0
194 } else {
195 lo.cos().max(hi.cos())
196 };
197 let min = if contains_lattice(lo, hi, core::f64::consts::PI, tau) {
198 -1.0
199 } else {
200 lo.cos().min(hi.cos())
201 };
202 (min, max)
203}
204
205fn ordered(a: Scalar, b: Scalar) -> (Scalar, Scalar) {
206 if a <= b {
207 (a, b)
208 } else {
209 (b, a)
210 }
211}
212
213fn conic_second(rx: Scalar, ry: Scalar, x: Vec3, y: Vec3, a: Scalar, b: Scalar) -> Scalar {
219 let (lo, hi) = ordered(a, b);
220 let alpha2 = (rx * rx) * x.length_squared();
221 let beta2 = (ry * ry) * y.length_squared();
222 let cos2 = if alpha2 >= beta2 {
223 max_cos2(lo, hi)
224 } else {
225 min_cos2(lo, hi)
226 };
227 let main = beta2 + (alpha2 - beta2) * cos2;
228 let cross = (rx * ry * x.dot(y)).abs();
229 (main.max(0.0) + cross).sqrt()
230}
231
232fn circle_sagitta(radius: Scalar, x: Vec3, y: Vec3, a: Scalar, b: Scalar) -> Option<Scalar> {
235 let h = (b - a).abs();
236 let orthonormal = (x.length_squared() - 1.0).abs() <= 1e-12
237 && (y.length_squared() - 1.0).abs() <= 1e-12
238 && x.dot(y).abs() <= 1e-12;
239 (orthonormal && h <= core::f64::consts::PI).then(|| radius.abs() * (1.0 - (0.5 * h).cos()))
240}
241
242fn lift2(v: Vec2) -> Vec3 {
243 Vec3::new(v.x, v.y, 0.0)
244}
245
246#[derive(Debug, Clone, Copy, PartialEq)]
255pub struct CurveDerivativeBounds {
256 pub first: Scalar,
258 pub second: Scalar,
260 pub third: Scalar,
262}
263
264fn hodograph<const N: usize>(
267 points: &[[Scalar; N]],
268 weights: &[Scalar],
269 knots: &[Scalar],
270 degree: usize,
271) -> (Vec<[Scalar; N]>, Vec<Scalar>) {
272 let mut out_points = Vec::with_capacity(points.len().saturating_sub(1));
273 let mut out_weights = Vec::with_capacity(weights.len().saturating_sub(1));
274 for i in 0..points.len().saturating_sub(1) {
275 let width = knots[i + degree + 1] - knots[i + 1];
276 let factor = if width > 0.0 {
277 degree as Scalar / width
278 } else {
279 0.0
280 };
281 out_points.push(core::array::from_fn(|k| {
282 (points[i + 1][k] - points[i][k]) * factor
283 }));
284 out_weights.push((weights[i + 1] - weights[i]) * factor);
285 }
286 (out_points, out_weights)
287}
288
289fn norm<const N: usize>(p: &[Scalar; N]) -> Scalar {
290 p.iter().map(|c| c * c).sum::<Scalar>().sqrt()
291}
292
293fn quotient_bounds(a: [Scalar; 5], w: [Scalar; 5], radius: Scalar, w_min: Scalar) -> [Scalar; 4] {
298 let first = (a[1] + w[1] * radius) / w_min;
299 let second = (a[2] + 2.0 * w[1] * first + w[2] * radius) / w_min;
300 let third = (a[3] + 3.0 * w[2] * first + 3.0 * w[1] * second + w[3] * radius) / w_min;
301 let fourth =
302 (a[4] + 4.0 * w[3] * first + 6.0 * w[2] * second + 4.0 * w[1] * third + w[4] * radius)
303 / w_min;
304 [first, second, third, fourth]
305}
306
307fn bspline_bounds<P, const N: usize>(
321 curve: &BSplineCurve<P>,
322 to: impl Fn(&P) -> [Scalar; N],
323 a: Scalar,
324 b: Scalar,
325) -> Option<(CurveDerivativeBounds, Scalar, Scalar)> {
326 let count = curve.control_points.len();
327 let axis = SplineAxis::new(
328 &curve.knots,
329 &curve.multiplicities,
330 curve.degree,
331 count,
332 "B-spline curve",
333 )
334 .ok()?;
335 let weights: Vec<Scalar> = match &curve.weights {
336 Some(w) if w.len() == count => w.clone(),
337 Some(_) => return None,
338 None => vec![1.0; count],
339 };
340 if weights.iter().any(|w| !(w.is_finite() && *w > 0.0)) {
341 return None;
342 }
343 let degree = axis.degree;
344 let (lo, hi) = ordered(axis.clamp(a), axis.clamp(b));
345 let first_span = span_in(&axis.knots, count, degree, lo);
346 let last_span = span_in(&axis.knots, count, degree, hi);
347 let window = first_span - degree..=last_span;
350 let points: Vec<[Scalar; N]> = curve.control_points.iter().map(&to).collect();
351 if points.iter().flatten().any(|c| !c.is_finite()) {
352 return None;
353 }
354 let mut low = [Scalar::INFINITY; N];
357 let mut high = [Scalar::NEG_INFINITY; N];
358 for p in &points[window.clone()] {
359 for k in 0..N {
360 low[k] = low[k].min(p[k]);
361 high[k] = high[k].max(p[k]);
362 }
363 }
364 let centre: [Scalar; N] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
365 let radius = points[window.clone()]
366 .iter()
367 .map(|p| norm(&core::array::from_fn::<Scalar, N, _>(|k| p[k] - centre[k])))
368 .fold(0.0, Scalar::max);
369 let w_min = weights[window.clone()]
370 .iter()
371 .copied()
372 .fold(Scalar::INFINITY, Scalar::min);
373 let mut hom: Vec<[Scalar; N]> = points
374 .iter()
375 .zip(&weights)
376 .map(|(p, w)| core::array::from_fn(|k| (p[k] - centre[k]) * w))
377 .collect();
378 let mut hw = weights.clone();
379 let mut knots: &[Scalar] = &axis.knots;
380 let mut a_sup = [0.0; 5];
381 let mut w_sup = [0.0; 5];
382 for order in 1..=4 {
383 let current_degree = degree + 1 - order;
384 if current_degree == 0 || hom.len() < 2 {
385 break;
386 }
387 let (next, next_w) = hodograph(&hom, &hw, knots, current_degree);
388 knots = &knots[1..knots.len() - 1];
389 let end = (*window.end()).min(next.len().saturating_sub(1));
390 let start = (*window.start()).min(end);
391 a_sup[order] = next[start..=end].iter().map(norm).fold(0.0, Scalar::max);
392 w_sup[order] = next_w[start..=end]
393 .iter()
394 .map(|w| w.abs())
395 .fold(0.0, Scalar::max);
396 hom = next;
397 hw = next_w;
398 }
399 let [first, second, third, fourth] = quotient_bounds(a_sup, w_sup, radius, w_min);
400 let bounds = CurveDerivativeBounds {
401 first: inflate(first),
402 second: inflate(second),
403 third: inflate(third),
404 };
405 let chord = inflate((a_sup[2] + radius * w_sup[2]) / w_min).min(bounds.second);
406 (bounds.first.is_finite()
407 && bounds.second.is_finite()
408 && bounds.third.is_finite()
409 && chord.is_finite())
410 .then_some((bounds, chord, inflate(fourth)))
411}
412
413fn bspline_breaks<P>(curve: &BSplineCurve<P>, k: usize) -> Vec<Scalar> {
416 let degree = usize::from(curve.degree);
417 let interior = curve.knots.len().saturating_sub(2);
418 curve
419 .knots
420 .iter()
421 .zip(&curve.multiplicities)
422 .skip(1)
423 .take(interior)
424 .filter(|(_, &m)| (m as usize) + k > degree)
425 .map(|(&knot, _)| knot)
426 .collect()
427}
428
429#[must_use]
436pub fn continuity_breaks2(curve: &Curve2, k: usize) -> Vec<Scalar> {
437 match curve {
438 Curve2::BSpline(b) => bspline_breaks(b, k),
439 Curve2::Polyline(p) if k >= 1 => polyline_breaks(p.points.len(), p.closed),
440 Curve2::Chain(c) if k >= 1 => c.joins().unwrap_or_default(),
444 Curve2::Implicit(c) if k >= 1 => implicit::joins(c),
446 Curve2::Lifted(l) => continuity_breaks3(&l.curve, k),
448 _ => Vec::new(),
449 }
450}
451
452#[must_use]
455pub fn continuity_breaks3(curve: &Curve3, k: usize) -> Vec<Scalar> {
456 match curve {
457 Curve3::BSpline(b) => bspline_breaks(b, k),
458 Curve3::Polyline(p) if k >= 1 => polyline_breaks(p.points.len(), p.closed),
459 Curve3::Elevated(e) => crate::elevated::elevated_breaks(e, k),
462 Curve3::Banked(b) => crate::elevated::banked_breaks(b, k),
463 Curve3::ImplicitSection(s) if k >= 1 => implicit::joins(&s.curve),
466 Curve3::PairSection(p) if k >= 1 => (1..p.nodes.len().saturating_sub(1))
467 .map(|k| k as Scalar)
468 .collect(),
469 _ => Vec::new(),
470 }
471}
472
473fn polyline_breaks(count: usize, closed: bool) -> Vec<Scalar> {
474 let segments = if closed {
475 count
476 } else {
477 count.saturating_sub(1)
478 };
479 (1..segments).map(|k| k as Scalar).collect()
480}
481
482fn breaks_inside(breaks: &[Scalar], a: Scalar, b: Scalar) -> bool {
483 let (lo, hi) = ordered(a, b);
484 breaks.iter().any(|&t| t > lo && t < hi)
485}
486
487#[must_use]
492pub fn curve_derivative_bounds3(
493 curve: &Curve3,
494 a: Scalar,
495 b: Scalar,
496) -> Option<CurveDerivativeBounds> {
497 if !(a.is_finite() && b.is_finite()) {
498 return None;
499 }
500 match curve {
501 Curve3::Line(line) => Some(CurveDerivativeBounds {
502 first: inflate(line.direction.length()),
503 second: 0.0,
504 third: 0.0,
505 }),
506 Curve3::Circle(c) => Some(conic_bounds(c.radius, c.radius, c.frame.x, c.frame.y, a, b)),
507 Curve3::Ellipse(e) => Some(conic_bounds(
508 e.semi_axis_x,
509 e.semi_axis_y,
510 e.frame.x,
511 e.frame.y,
512 a,
513 b,
514 )),
515 Curve3::BSpline(spline) => {
516 bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b).map(|(bounds, ..)| bounds)
517 }
518 Curve3::Elevated(e) => crate::elevated::elevated_derivative_bounds(e, a, b),
520 Curve3::Banked(banked) => crate::elevated::banked_derivative_bounds(banked, a, b),
521 _ => None,
522 }
523}
524
525#[must_use]
528pub fn curve_derivative_bounds2(
529 curve: &Curve2,
530 a: Scalar,
531 b: Scalar,
532) -> Option<CurveDerivativeBounds> {
533 if !(a.is_finite() && b.is_finite()) {
534 return None;
535 }
536 match curve {
537 Curve2::Line(line) => Some(CurveDerivativeBounds {
538 first: inflate(line.direction.length()),
539 second: 0.0,
540 third: 0.0,
541 }),
542 Curve2::Circle(c) => Some(conic_bounds(
543 c.radius,
544 c.radius,
545 lift2(c.frame.x),
546 lift2(c.frame.y),
547 a,
548 b,
549 )),
550 Curve2::Ellipse(e) => Some(conic_bounds(
551 e.semi_axis_x,
552 e.semi_axis_y,
553 lift2(e.frame.x),
554 lift2(e.frame.y),
555 a,
556 b,
557 )),
558 Curve2::BSpline(spline) => {
559 bspline_bounds(spline, |p| [p.x, p.y], a, b).map(|(bounds, ..)| bounds)
560 }
561 Curve2::Sinusoid(w) => {
564 let amplitude = w.cosine.hypot(w.sine);
565 Some(CurveDerivativeBounds {
566 first: inflate((1.0 + amplitude * amplitude).sqrt()),
567 second: inflate(amplitude),
568 third: inflate(amplitude),
569 })
570 }
571 _ => None,
572 }
573}
574
575#[must_use]
580pub(crate) fn curve_fourth_bound3(curve: &Curve3, a: Scalar, b: Scalar) -> Option<Scalar> {
581 if !(a.is_finite() && b.is_finite()) {
582 return None;
583 }
584 match curve {
585 Curve3::Line(_) => Some(0.0),
586 Curve3::Circle(c) => Some(inflate(conic_second(
587 c.radius, c.radius, c.frame.x, c.frame.y, a, b,
588 ))),
589 Curve3::Ellipse(e) => Some(inflate(conic_second(
590 e.semi_axis_x,
591 e.semi_axis_y,
592 e.frame.x,
593 e.frame.y,
594 a,
595 b,
596 ))),
597 Curve3::BSpline(spline) => {
598 bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b).map(|(.., fourth)| fourth)
599 }
600 _ => None,
601 }
602}
603
604#[must_use]
606pub(crate) fn curve_fourth_bound2(curve: &Curve2, a: Scalar, b: Scalar) -> Option<Scalar> {
607 if !(a.is_finite() && b.is_finite()) {
608 return None;
609 }
610 match curve {
611 Curve2::Line(_) => Some(0.0),
612 Curve2::Circle(c) => Some(inflate(conic_second(
613 c.radius,
614 c.radius,
615 lift2(c.frame.x),
616 lift2(c.frame.y),
617 a,
618 b,
619 ))),
620 Curve2::Ellipse(e) => Some(inflate(conic_second(
621 e.semi_axis_x,
622 e.semi_axis_y,
623 lift2(e.frame.x),
624 lift2(e.frame.y),
625 a,
626 b,
627 ))),
628 Curve2::BSpline(spline) => {
629 bspline_bounds(spline, |p| [p.x, p.y], a, b).map(|(.., fourth)| fourth)
630 }
631 _ => None,
632 }
633}
634
635fn conic_bounds(
639 rx: Scalar,
640 ry: Scalar,
641 x: Vec3,
642 y: Vec3,
643 a: Scalar,
644 b: Scalar,
645) -> CurveDerivativeBounds {
646 let speed = ((rx * rx * x.length_squared()).max(ry * ry * y.length_squared())
647 + (rx * ry * x.dot(y)).abs())
648 .sqrt();
649 CurveDerivativeBounds {
650 first: inflate(speed),
651 second: inflate(conic_second(rx, ry, x, y, a, b)),
652 third: inflate(speed),
653 }
654}
655
656#[must_use]
669pub fn chord_bound2(curve: &Curve2, a: Scalar, b: Scalar) -> Option<Scalar> {
670 if !(a.is_finite() && b.is_finite()) {
671 return None;
672 }
673 if a == b {
674 if matches!(
678 curve,
679 Curve2::Lifted(_) | Curve2::Intrinsic(_) | Curve2::Chain(_)
680 ) {
681 return chord_bound2(curve, a, a.next_up())
682 .or_else(|| chord_bound2(curve, a.next_down(), a));
683 }
684 return Some(0.0);
685 }
686 if breaks_inside(&continuity_breaks2(curve, 1), a, b) {
687 return None;
688 }
689 match curve {
690 Curve2::Line(_) | Curve2::Polyline(_) => Some(0.0),
691 Curve2::Circle(c) => Some(inflate(
692 circle_sagitta(c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b).unwrap_or_else(
693 || {
694 taylor(
695 b - a,
696 conic_second(c.radius, c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b),
697 )
698 },
699 ),
700 )),
701 Curve2::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y], a, b)
702 .map(|(_, chord, _)| inflate(taylor(b - a, chord))),
703 Curve2::Chain(chain) => chain_positions::evaluated_chain_chord_bound(chain, a, b),
704 Curve2::Implicit(c) => {
705 let (lo, hi) = ordered(a, b);
706 implicit::chord_bound(c, lo, hi).map(inflate)
707 }
708 Curve2::QuadraticGraph(g) => {
709 let (lo, hi) = ordered(a, b);
710 graph::quadratic_chord_bound(g, lo, hi).map(inflate)
711 }
712 Curve2::AngleGraph(g) => {
713 let (lo, hi) = ordered(a, b);
714 graph::angle_chord_bound(g, lo, hi).map(inflate)
715 }
716 Curve2::Lifted(l) => {
717 let (lo, hi) = ordered(a, b);
718 lifted::chord_bound(l, lo, hi).map(inflate)
719 }
720 Curve2::Intrinsic(i) => {
721 let (lo, hi) = ordered(a, b);
722 intrinsic::evaluated_chord_bound2(i, lo, hi)
723 }
724 _ => curve_derivative_bounds2(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
725 }
726}
727
728#[must_use]
735pub fn chord_bound3(curve: &Curve3, a: Scalar, b: Scalar) -> Option<Scalar> {
736 if !(a.is_finite() && b.is_finite()) {
737 return None;
738 }
739 if a == b {
740 if over_measured_plan(curve) {
744 return chord_bound3(curve, a, a.next_up())
745 .or_else(|| chord_bound3(curve, a.next_down(), a));
746 }
747 return Some(0.0);
748 }
749 if breaks_inside(&continuity_breaks3(curve, 1), a, b) {
750 return None;
751 }
752 match curve {
753 Curve3::Line(_) | Curve3::Polyline(_) => Some(0.0),
754 Curve3::Circle(c) => Some(inflate(
755 circle_sagitta(c.radius, c.frame.x, c.frame.y, a, b).unwrap_or_else(|| {
756 taylor(
757 b - a,
758 conic_second(c.radius, c.radius, c.frame.x, c.frame.y, a, b),
759 )
760 }),
761 )),
762 Curve3::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b)
763 .map(|(_, chord, _)| inflate(taylor(b - a, chord))),
764 Curve3::Elevated(_) | Curve3::Banked(_) => {
769 elevated_positions::evaluated_chord_bound3(curve, a, b)
770 }
771 _ => curve_derivative_bounds3(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
772 }
773}
774
775fn over_measured_plan(curve: &Curve3) -> bool {
778 let plan = match curve {
779 Curve3::Elevated(e) => &e.plan,
780 Curve3::Banked(b) => &b.base.plan,
781 _ => return false,
782 };
783 matches!(plan.as_ref(), Curve2::Intrinsic(_) | Curve2::Chain(_))
784}
785
786fn taylor(h: Scalar, second: Scalar) -> Scalar {
788 h * h * 0.125 * second
789}
790
791#[must_use]
799pub fn certifies_flattening2(curve: &Curve2) -> bool {
800 match curve {
801 Curve2::Line(_)
802 | Curve2::Polyline(_)
803 | Curve2::Circle(_)
804 | Curve2::Ellipse(_)
805 | Curve2::Sinusoid(_) => true,
806 Curve2::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
807 Curve2::Chain(c) => crate::chain::chain_certifies(c),
808 _ => false,
809 }
810}
811
812#[must_use]
814pub fn certifies_flattening3(curve: &Curve3) -> bool {
815 match curve {
816 Curve3::Line(_) | Curve3::Polyline(_) | Curve3::Circle(_) | Curve3::Ellipse(_) => true,
817 Curve3::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
818 Curve3::Elevated(e) => crate::elevated::certifies_elevated(e),
824 Curve3::Banked(_) => false,
825 _ => false,
826 }
827}
828
829fn spline_weights_positive(weights: Option<&[Scalar]>) -> bool {
830 weights.is_none_or(|w| w.iter().all(|w| w.is_finite() && *w > 0.0))
831}
832
833#[derive(Debug, Clone, Copy, PartialEq)]
843pub struct SurfaceBounds {
844 pub du: Scalar,
846 pub dv: Scalar,
848 pub duu: Scalar,
850 pub duv: Scalar,
852 pub dvv: Scalar,
854 pub smooth: bool,
856 pub interpolation: [Scalar; 3],
869}
870
871impl SurfaceBounds {
872 fn inflated(self) -> Self {
873 Self {
874 du: inflate(self.du),
875 dv: inflate(self.dv),
876 duu: inflate(self.duu),
877 duv: inflate(self.duv),
878 dvv: inflate(self.dvv),
879 smooth: self.smooth,
880 interpolation: self.interpolation.map(inflate),
881 }
882 }
883
884 fn finite(self) -> Option<Self> {
885 [self.du, self.dv, self.duu, self.duv, self.dvv]
886 .iter()
887 .chain(&self.interpolation)
888 .all(|v| v.is_finite())
889 .then_some(self)
890 }
891}
892
893#[derive(Debug, Clone)]
898pub struct SurfaceBoundOracle<'a> {
899 source: Source<'a>,
900 spline: Option<SplineNets>,
901}
902
903#[derive(Debug, Clone, Copy)]
905enum Source<'a> {
906 Surface(&'a Surface),
907 Carrier(&'a Carrier),
908}
909
910#[derive(Debug, Clone)]
912struct SplineNets {
913 u_knots: Vec<Scalar>,
914 v_knots: Vec<Scalar>,
915 p: usize,
916 q: usize,
917 rows: usize,
918 cols: usize,
919 points: Vec<Vec<[Scalar; 3]>>,
920 weights: Vec<Vec<Scalar>>,
921 u_breaks: Vec<Scalar>,
922 v_breaks: Vec<Scalar>,
923}
924
925impl<'a> SurfaceBoundOracle<'a> {
926 #[must_use]
929 pub fn new(surface: &'a Surface) -> Option<Self> {
930 let spline = match surface {
931 Surface::Plane(_)
932 | Surface::Cylinder(_)
933 | Surface::EllipticalCylinder(_)
934 | Surface::Cone(_)
935 | Surface::Sphere(_)
936 | Surface::Torus(_) => None,
937 Surface::BSpline(b) => Some(SplineNets::new(b)?),
938 _ => return None,
939 };
940 Some(Self {
941 source: Source::Surface(surface),
942 spline,
943 })
944 }
945
946 #[must_use]
949 pub fn for_carrier(carrier: &'a Carrier) -> Option<Self> {
950 if !carrier.is_finite() {
951 return None;
952 }
953 let spline = match carrier {
954 Carrier::Spline(b) => Some(SplineNets::new(b)?),
955 _ => None,
956 };
957 Some(Self {
958 source: Source::Carrier(carrier),
959 spline,
960 })
961 }
962
963 #[must_use]
967 pub fn bounds(&self, u: (Scalar, Scalar), v: (Scalar, Scalar)) -> Option<SurfaceBounds> {
968 let (u0, u1) = ordered(u.0, u.1);
969 let (v0, v1) = ordered(v.0, v.1);
970 if ![u0, u1, v0, v1].iter().all(|x| x.is_finite()) {
971 return None;
972 }
973 let raw = match self.source {
974 Source::Surface(surface) => match surface {
975 Surface::Plane(p) => carrier::plane(&p.frame),
976 Surface::Cylinder(c) => {
977 let s = frame_stretch3(&c.frame);
978 SurfaceBounds {
979 du: s * c.radius.abs(),
980 dv: c.frame.z.length(),
981 duu: s * c.radius.abs(),
982 duv: 0.0,
983 dvv: 0.0,
984 smooth: true,
985 interpolation: [0.0; 3],
986 }
987 }
988 Surface::EllipticalCylinder(c) => {
989 let s = frame_stretch3(&c.frame);
990 let r = c.semi_axis_x.abs().max(c.semi_axis_y.abs());
991 SurfaceBounds {
992 du: s * r,
993 dv: c.frame.z.length(),
994 duu: s * r,
995 duv: 0.0,
996 dvv: 0.0,
997 smooth: true,
998 interpolation: [0.0; 3],
999 }
1000 }
1001 Surface::Cone(c) => {
1002 let s = frame_stretch3(&c.frame);
1003 let slope = c.semi_angle.tan();
1004 let (r0, r1) = (c.radius + v0 * slope, c.radius + v1 * slope);
1005 if r0 < 0.0 || r1 < 0.0 {
1006 return None;
1007 }
1008 let rho = r0.max(r1);
1009 SurfaceBounds {
1010 du: s * rho,
1011 dv: s * (1.0 + slope * slope).sqrt(),
1012 duu: s * rho,
1013 duv: s * slope.abs(),
1014 dvv: 0.0,
1015 smooth: true,
1016 interpolation: [0.0; 3],
1017 }
1018 }
1019 Surface::Sphere(sphere) => carrier::sphere(&sphere.frame, sphere.radius, v0, v1),
1020 Surface::Torus(t) => {
1021 carrier::torus(&t.frame, t.major_radius, t.minor_radius, v0, v1)
1022 }
1023 Surface::BSpline(_) => self.spline.as_ref()?.bounds(u0, u1, v0, v1)?,
1024 _ => return None,
1025 },
1026 Source::Carrier(c) => match c {
1027 Carrier::Plane(f) => carrier::plane(f),
1028 Carrier::Ruled(k) => carrier::ruled(k, v0, v1),
1029 Carrier::Sphere { frame, radius } => carrier::sphere(frame, *radius, v0, v1),
1030 Carrier::Torus(t) => {
1031 carrier::torus(&t.frame, t.major_radius, t.minor_radius, v0, v1)
1032 }
1033 Carrier::Spline(_) => self.spline.as_ref()?.bounds(u0, u1, v0, v1)?,
1034 },
1035 };
1036 let raw = if self.spline.is_some() {
1037 raw
1038 } else {
1039 SurfaceBounds {
1040 interpolation: [raw.duu, raw.duv, raw.dvv],
1041 ..raw
1042 }
1043 };
1044 raw.inflated().finite()
1045 }
1046}
1047
1048impl SplineNets {
1049 fn new(b: &BSplineSurface) -> Option<Self> {
1050 let rows = b.control_points.len();
1051 let cols = b.control_points.first()?.len();
1052 if b.control_points.iter().any(|row| row.len() != cols) {
1053 return None;
1054 }
1055 let ua = SplineAxis::new(&b.u_knots, &b.u_multiplicities, b.u_degree, rows, "u").ok()?;
1056 let va = SplineAxis::new(&b.v_knots, &b.v_multiplicities, b.v_degree, cols, "v").ok()?;
1057 let weights: Vec<Vec<Scalar>> = match &b.weights {
1058 Some(net) => {
1059 if net.len() != rows || net.iter().any(|row| row.len() != cols) {
1060 return None;
1061 }
1062 net.clone()
1063 }
1064 None => vec![vec![1.0; cols]; rows],
1065 };
1066 if weights
1067 .iter()
1068 .flatten()
1069 .any(|w| !(w.is_finite() && *w > 0.0))
1070 {
1071 return None;
1072 }
1073 let points: Vec<Vec<[Scalar; 3]>> = b
1074 .control_points
1075 .iter()
1076 .map(|row| row.iter().map(|p| [p.x, p.y, p.z]).collect())
1077 .collect();
1078 if points.iter().flatten().flatten().any(|c| !c.is_finite()) {
1079 return None;
1080 }
1081 let breaks = |knots: &[Scalar], mults: &[u32], degree: u16| {
1082 let interior = knots.len().saturating_sub(2);
1083 knots
1084 .iter()
1085 .zip(mults)
1086 .skip(1)
1087 .take(interior)
1088 .filter(|(_, &m)| m as usize >= usize::from(degree))
1089 .map(|(&k, _)| k)
1090 .collect::<Vec<_>>()
1091 };
1092 Some(Self {
1093 u_breaks: breaks(&b.u_knots, &b.u_multiplicities, b.u_degree),
1094 v_breaks: breaks(&b.v_knots, &b.v_multiplicities, b.v_degree),
1095 u_knots: ua.knots,
1096 v_knots: va.knots,
1097 p: ua.degree,
1098 q: va.degree,
1099 rows,
1100 cols,
1101 points,
1102 weights,
1103 })
1104 }
1105
1106 #[allow(clippy::needless_range_loop)]
1109 fn bounds(&self, u0: Scalar, u1: Scalar, v0: Scalar, v1: Scalar) -> Option<SurfaceBounds> {
1110 let clamp = |x: Scalar, knots: &[Scalar], d: usize, n: usize| x.clamp(knots[d], knots[n]);
1111 let (cu0, cu1) = (
1112 clamp(u0, &self.u_knots, self.p, self.rows),
1113 clamp(u1, &self.u_knots, self.p, self.rows),
1114 );
1115 let (cv0, cv1) = (
1116 clamp(v0, &self.v_knots, self.q, self.cols),
1117 clamp(v1, &self.v_knots, self.q, self.cols),
1118 );
1119 let su0 = span_in(&self.u_knots, self.rows, self.p, cu0);
1120 let su1 = span_in(&self.u_knots, self.rows, self.p, cu1);
1121 let sv0 = span_in(&self.v_knots, self.cols, self.q, cv0);
1122 let sv1 = span_in(&self.v_knots, self.cols, self.q, cv1);
1123 let (r0, r1) = (su0 - self.p, su1);
1124 let (c0, c1) = (sv0 - self.q, sv1);
1125
1126 let mut low = [Scalar::INFINITY; 3];
1128 let mut high = [Scalar::NEG_INFINITY; 3];
1129 for row in &self.points[r0..=r1] {
1130 for p in &row[c0..=c1] {
1131 for k in 0..3 {
1132 low[k] = low[k].min(p[k]);
1133 high[k] = high[k].max(p[k]);
1134 }
1135 }
1136 }
1137 let centre: [Scalar; 3] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
1138 let mut radius: Scalar = 0.0;
1139 let mut w_min = Scalar::INFINITY;
1140 let (wr0, wr1) = (r0, r1.min(self.rows - 1));
1142 let (wc0, wc1) = (c0, c1.min(self.cols - 1));
1143 let mut hom: Vec<Vec<[Scalar; 4]>> = Vec::with_capacity(wr1 + 1 - wr0);
1144 for i in wr0..=wr1 {
1145 let mut row = Vec::with_capacity(wc1 + 1 - wc0);
1146 for j in wc0..=wc1 {
1147 let p = self.points[i][j];
1148 let w = self.weights[i][j];
1149 let d: [Scalar; 3] = core::array::from_fn(|k| p[k] - centre[k]);
1150 radius = radius.max(norm(&d));
1151 w_min = w_min.min(w);
1152 row.push([d[0] * w, d[1] * w, d[2] * w, w]);
1153 }
1154 hom.push(row);
1155 }
1156 let du_net =
1159 |net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
1160 let mut out = Vec::new();
1161 for i in 0..net.len().saturating_sub(1) {
1162 let width = knots[offset + i + degree + 1] - knots[offset + i + 1];
1163 let f = if width > 0.0 {
1164 degree as Scalar / width
1165 } else {
1166 0.0
1167 };
1168 out.push(
1169 net[i]
1170 .iter()
1171 .zip(&net[i + 1])
1172 .map(|(a, b)| core::array::from_fn(|k| (b[k] - a[k]) * f))
1173 .collect::<Vec<[Scalar; 4]>>(),
1174 );
1175 }
1176 out
1177 };
1178 let dv_net =
1179 |net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
1180 net.iter()
1181 .map(|row| {
1182 (0..row.len().saturating_sub(1))
1183 .map(|j| {
1184 let width = knots[offset + j + degree + 1] - knots[offset + j + 1];
1185 let f = if width > 0.0 {
1186 degree as Scalar / width
1187 } else {
1188 0.0
1189 };
1190 core::array::from_fn(|k| (row[j + 1][k] - row[j][k]) * f)
1191 })
1192 .collect::<Vec<[Scalar; 4]>>()
1193 })
1194 .collect::<Vec<_>>()
1195 };
1196 let sup = |net: &Vec<Vec<[Scalar; 4]>>| {
1197 let mut a: Scalar = 0.0;
1198 let mut w: Scalar = 0.0;
1199 for p in net.iter().flatten() {
1200 a = a.max((p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt());
1201 w = w.max(p[3].abs());
1202 }
1203 (a, w)
1204 };
1205 let (p, q) = (self.p, self.q);
1208 let nu = if p >= 1 {
1209 du_net(&hom, &self.u_knots, p, wr0)
1210 } else {
1211 Vec::new()
1212 };
1213 let nuu = if p >= 2 {
1214 du_net(&nu, &self.u_knots, p - 1, wr0 + 1)
1215 } else {
1216 Vec::new()
1217 };
1218 let nv = if q >= 1 {
1219 dv_net(&hom, &self.v_knots, q, wc0)
1220 } else {
1221 Vec::new()
1222 };
1223 let nvv = if q >= 2 {
1224 dv_net(&nv, &self.v_knots, q - 1, wc0 + 1)
1225 } else {
1226 Vec::new()
1227 };
1228 let nuv = if p >= 1 && q >= 1 {
1229 dv_net(&nu, &self.v_knots, q, wc0)
1230 } else {
1231 Vec::new()
1232 };
1233 let (au, wu) = sup(&nu);
1234 let (av, wv) = sup(&nv);
1235 let (auu, wuu) = sup(&nuu);
1236 let (avv, wvv) = sup(&nvv);
1237 let (auv, wuv) = sup(&nuv);
1238 if !(w_min.is_finite() && w_min > 0.0) {
1239 return None;
1240 }
1241 let su = (au + wu * radius) / w_min;
1244 let sv = (av + wv * radius) / w_min;
1245 let suu = (auu + 2.0 * wu * su + wuu * radius) / w_min;
1246 let svv = (avv + 2.0 * wv * sv + wvv * radius) / w_min;
1247 let suv = (auv + wu * sv + wv * su + wuv * radius) / w_min;
1248 let inside = cu0 == u0 && cu1 == u1 && cv0 == v0 && cv1 == v1;
1251 let smooth = inside
1252 && !breaks_inside(&self.u_breaks, cu0, cu1)
1253 && !breaks_inside(&self.v_breaks, cv0, cv1);
1254 Some(SurfaceBounds {
1255 du: su,
1256 dv: sv,
1257 duu: suu,
1258 duv: suv,
1259 dvv: svv,
1260 smooth,
1261 interpolation: [
1262 (auu + radius * wuu) / w_min,
1263 (auv + radius * wuv) / w_min,
1264 (avv + radius * wvv) / w_min,
1265 ],
1266 })
1267 }
1268}
1269
1270#[cfg(test)]
1271mod tests;