use axiolid_core::{Frame2, Frame3, Scalar, Vec2, Vec3};
use axiolid_curve::{BSplineCurve, Curve2, Curve3};
use axiolid_surface::{BSplineSurface, Surface};
use crate::curve::span_in;
use crate::nurbs::SplineAxis;
mod implicit;
mod intrinsic;
mod offset;
mod positions;
pub use intrinsic::{intrinsic_chord_bound2, intrinsic_chord_bound3, intrinsic_derivative_bounds2};
pub use offset::{offset_chord_bound2, offset_chord_bound3, offset_point2, offset_point3};
pub use positions::{IntrinsicPositions, MAX_POSITION_PANELS};
pub const ROUNDING: Scalar = 1e-12;
fn inflate(value: Scalar) -> Scalar {
value * (1.0 + ROUNDING)
}
fn gram_stretch(columns: &[Vec3]) -> Scalar {
let mut off: Scalar = 0.0;
for (i, a) in columns.iter().enumerate() {
for (j, b) in columns.iter().enumerate() {
let target = if i == j { 1.0 } else { 0.0 };
let entry = a.dot(*b) - target;
off += entry * entry;
}
}
(1.0 + off.sqrt()).sqrt()
}
#[must_use]
pub fn frame_stretch3(frame: &Frame3) -> Scalar {
inflate(gram_stretch(&[frame.x, frame.y, frame.z]))
}
#[must_use]
pub fn frame_stretch2(frame: &Frame2) -> Scalar {
let lift = |v: Vec2| Vec3::new(v.x, v.y, 0.0);
inflate(gram_stretch(&[lift(frame.x), lift(frame.y)]))
}
fn contains_lattice(lo: Scalar, hi: Scalar, offset: Scalar, period: Scalar) -> bool {
let k = ((lo - offset) / period).ceil();
offset + k * period <= hi
}
fn max_cos2(lo: Scalar, hi: Scalar) -> Scalar {
if contains_lattice(lo, hi, 0.0, core::f64::consts::PI) {
1.0
} else {
lo.cos().powi(2).max(hi.cos().powi(2))
}
}
fn min_cos2(lo: Scalar, hi: Scalar) -> Scalar {
if contains_lattice(lo, hi, core::f64::consts::FRAC_PI_2, core::f64::consts::PI) {
0.0
} else {
lo.cos().powi(2).min(hi.cos().powi(2))
}
}
fn max_abs_cos(lo: Scalar, hi: Scalar) -> Scalar {
max_cos2(lo, hi).sqrt()
}
fn max_abs_sin(lo: Scalar, hi: Scalar) -> Scalar {
(1.0 - min_cos2(lo, hi)).max(0.0).sqrt()
}
fn cos_range(lo: Scalar, hi: Scalar) -> (Scalar, Scalar) {
let tau = core::f64::consts::TAU;
let max = if contains_lattice(lo, hi, 0.0, tau) {
1.0
} else {
lo.cos().max(hi.cos())
};
let min = if contains_lattice(lo, hi, core::f64::consts::PI, tau) {
-1.0
} else {
lo.cos().min(hi.cos())
};
(min, max)
}
fn ordered(a: Scalar, b: Scalar) -> (Scalar, Scalar) {
if a <= b {
(a, b)
} else {
(b, a)
}
}
fn conic_second(rx: Scalar, ry: Scalar, x: Vec3, y: Vec3, a: Scalar, b: Scalar) -> Scalar {
let (lo, hi) = ordered(a, b);
let alpha2 = (rx * rx) * x.length_squared();
let beta2 = (ry * ry) * y.length_squared();
let cos2 = if alpha2 >= beta2 {
max_cos2(lo, hi)
} else {
min_cos2(lo, hi)
};
let main = beta2 + (alpha2 - beta2) * cos2;
let cross = (rx * ry * x.dot(y)).abs();
(main.max(0.0) + cross).sqrt()
}
fn circle_sagitta(radius: Scalar, x: Vec3, y: Vec3, a: Scalar, b: Scalar) -> Option<Scalar> {
let h = (b - a).abs();
let orthonormal = (x.length_squared() - 1.0).abs() <= 1e-12
&& (y.length_squared() - 1.0).abs() <= 1e-12
&& x.dot(y).abs() <= 1e-12;
(orthonormal && h <= core::f64::consts::PI).then(|| radius.abs() * (1.0 - (0.5 * h).cos()))
}
fn lift2(v: Vec2) -> Vec3 {
Vec3::new(v.x, v.y, 0.0)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CurveDerivativeBounds {
pub first: Scalar,
pub second: Scalar,
pub third: Scalar,
}
fn hodograph<const N: usize>(
points: &[[Scalar; N]],
weights: &[Scalar],
knots: &[Scalar],
degree: usize,
) -> (Vec<[Scalar; N]>, Vec<Scalar>) {
let mut out_points = Vec::with_capacity(points.len().saturating_sub(1));
let mut out_weights = Vec::with_capacity(weights.len().saturating_sub(1));
for i in 0..points.len().saturating_sub(1) {
let width = knots[i + degree + 1] - knots[i + 1];
let factor = if width > 0.0 {
degree as Scalar / width
} else {
0.0
};
out_points.push(core::array::from_fn(|k| {
(points[i + 1][k] - points[i][k]) * factor
}));
out_weights.push((weights[i + 1] - weights[i]) * factor);
}
(out_points, out_weights)
}
fn norm<const N: usize>(p: &[Scalar; N]) -> Scalar {
p.iter().map(|c| c * c).sum::<Scalar>().sqrt()
}
fn quotient_bounds(a: [Scalar; 5], w: [Scalar; 5], radius: Scalar, w_min: Scalar) -> [Scalar; 4] {
let first = (a[1] + w[1] * radius) / w_min;
let second = (a[2] + 2.0 * w[1] * first + w[2] * radius) / w_min;
let third = (a[3] + 3.0 * w[2] * first + 3.0 * w[1] * second + w[3] * radius) / w_min;
let fourth =
(a[4] + 4.0 * w[3] * first + 6.0 * w[2] * second + 4.0 * w[1] * third + w[4] * radius)
/ w_min;
[first, second, third, fourth]
}
fn bspline_bounds<P, const N: usize>(
curve: &BSplineCurve<P>,
to: impl Fn(&P) -> [Scalar; N],
a: Scalar,
b: Scalar,
) -> Option<(CurveDerivativeBounds, Scalar, Scalar)> {
let count = curve.control_points.len();
let axis = SplineAxis::new(
&curve.knots,
&curve.multiplicities,
curve.degree,
count,
"B-spline curve",
)
.ok()?;
let weights: Vec<Scalar> = match &curve.weights {
Some(w) if w.len() == count => w.clone(),
Some(_) => return None,
None => vec![1.0; count],
};
if weights.iter().any(|w| !(w.is_finite() && *w > 0.0)) {
return None;
}
let degree = axis.degree;
let (lo, hi) = ordered(axis.clamp(a), axis.clamp(b));
let first_span = span_in(&axis.knots, count, degree, lo);
let last_span = span_in(&axis.knots, count, degree, hi);
let window = first_span - degree..=last_span;
let points: Vec<[Scalar; N]> = curve.control_points.iter().map(&to).collect();
if points.iter().flatten().any(|c| !c.is_finite()) {
return None;
}
let mut low = [Scalar::INFINITY; N];
let mut high = [Scalar::NEG_INFINITY; N];
for p in &points[window.clone()] {
for k in 0..N {
low[k] = low[k].min(p[k]);
high[k] = high[k].max(p[k]);
}
}
let centre: [Scalar; N] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
let radius = points[window.clone()]
.iter()
.map(|p| norm(&core::array::from_fn::<Scalar, N, _>(|k| p[k] - centre[k])))
.fold(0.0, Scalar::max);
let w_min = weights[window.clone()]
.iter()
.copied()
.fold(Scalar::INFINITY, Scalar::min);
let mut hom: Vec<[Scalar; N]> = points
.iter()
.zip(&weights)
.map(|(p, w)| core::array::from_fn(|k| (p[k] - centre[k]) * w))
.collect();
let mut hw = weights.clone();
let mut knots: &[Scalar] = &axis.knots;
let mut a_sup = [0.0; 5];
let mut w_sup = [0.0; 5];
for order in 1..=4 {
let current_degree = degree + 1 - order;
if current_degree == 0 || hom.len() < 2 {
break;
}
let (next, next_w) = hodograph(&hom, &hw, knots, current_degree);
knots = &knots[1..knots.len() - 1];
let end = (*window.end()).min(next.len().saturating_sub(1));
let start = (*window.start()).min(end);
a_sup[order] = next[start..=end].iter().map(norm).fold(0.0, Scalar::max);
w_sup[order] = next_w[start..=end]
.iter()
.map(|w| w.abs())
.fold(0.0, Scalar::max);
hom = next;
hw = next_w;
}
let [first, second, third, fourth] = quotient_bounds(a_sup, w_sup, radius, w_min);
let bounds = CurveDerivativeBounds {
first: inflate(first),
second: inflate(second),
third: inflate(third),
};
let chord = inflate((a_sup[2] + radius * w_sup[2]) / w_min).min(bounds.second);
(bounds.first.is_finite()
&& bounds.second.is_finite()
&& bounds.third.is_finite()
&& chord.is_finite())
.then_some((bounds, chord, inflate(fourth)))
}
fn bspline_breaks<P>(curve: &BSplineCurve<P>, k: usize) -> Vec<Scalar> {
let degree = usize::from(curve.degree);
let interior = curve.knots.len().saturating_sub(2);
curve
.knots
.iter()
.zip(&curve.multiplicities)
.skip(1)
.take(interior)
.filter(|(_, &m)| (m as usize) + k > degree)
.map(|(&knot, _)| knot)
.collect()
}
#[must_use]
pub fn continuity_breaks2(curve: &Curve2, k: usize) -> Vec<Scalar> {
match curve {
Curve2::BSpline(b) => bspline_breaks(b, k),
Curve2::Polyline(p) if k >= 1 => polyline_breaks(p.points.len(), p.closed),
Curve2::Chain(c) if k >= 1 => c.joins().unwrap_or_default(),
Curve2::Implicit(c) if k >= 1 => implicit::joins(c),
_ => Vec::new(),
}
}
#[must_use]
pub fn continuity_breaks3(curve: &Curve3, k: usize) -> Vec<Scalar> {
match curve {
Curve3::BSpline(b) => bspline_breaks(b, k),
Curve3::Polyline(p) if k >= 1 => polyline_breaks(p.points.len(), p.closed),
Curve3::Elevated(e) => crate::elevated::elevated_breaks(e, k),
Curve3::Banked(b) => crate::elevated::banked_breaks(b, k),
_ => Vec::new(),
}
}
fn polyline_breaks(count: usize, closed: bool) -> Vec<Scalar> {
let segments = if closed {
count
} else {
count.saturating_sub(1)
};
(1..segments).map(|k| k as Scalar).collect()
}
fn breaks_inside(breaks: &[Scalar], a: Scalar, b: Scalar) -> bool {
let (lo, hi) = ordered(a, b);
breaks.iter().any(|&t| t > lo && t < hi)
}
#[must_use]
pub fn curve_derivative_bounds3(
curve: &Curve3,
a: Scalar,
b: Scalar,
) -> Option<CurveDerivativeBounds> {
if !(a.is_finite() && b.is_finite()) {
return None;
}
match curve {
Curve3::Line(line) => Some(CurveDerivativeBounds {
first: inflate(line.direction.length()),
second: 0.0,
third: 0.0,
}),
Curve3::Circle(c) => Some(conic_bounds(c.radius, c.radius, c.frame.x, c.frame.y, a, b)),
Curve3::Ellipse(e) => Some(conic_bounds(
e.semi_axis_x,
e.semi_axis_y,
e.frame.x,
e.frame.y,
a,
b,
)),
Curve3::BSpline(spline) => {
bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b).map(|(bounds, ..)| bounds)
}
Curve3::Elevated(e) => crate::elevated::elevated_derivative_bounds(e, a, b),
Curve3::Banked(banked) => crate::elevated::banked_derivative_bounds(banked, a, b),
_ => None,
}
}
#[must_use]
pub fn curve_derivative_bounds2(
curve: &Curve2,
a: Scalar,
b: Scalar,
) -> Option<CurveDerivativeBounds> {
if !(a.is_finite() && b.is_finite()) {
return None;
}
match curve {
Curve2::Line(line) => Some(CurveDerivativeBounds {
first: inflate(line.direction.length()),
second: 0.0,
third: 0.0,
}),
Curve2::Circle(c) => Some(conic_bounds(
c.radius,
c.radius,
lift2(c.frame.x),
lift2(c.frame.y),
a,
b,
)),
Curve2::Ellipse(e) => Some(conic_bounds(
e.semi_axis_x,
e.semi_axis_y,
lift2(e.frame.x),
lift2(e.frame.y),
a,
b,
)),
Curve2::BSpline(spline) => {
bspline_bounds(spline, |p| [p.x, p.y], a, b).map(|(bounds, ..)| bounds)
}
Curve2::Sinusoid(w) => {
let amplitude = w.cosine.hypot(w.sine);
Some(CurveDerivativeBounds {
first: inflate((1.0 + amplitude * amplitude).sqrt()),
second: inflate(amplitude),
third: inflate(amplitude),
})
}
_ => None,
}
}
#[must_use]
pub(crate) fn curve_fourth_bound3(curve: &Curve3, a: Scalar, b: Scalar) -> Option<Scalar> {
if !(a.is_finite() && b.is_finite()) {
return None;
}
match curve {
Curve3::Line(_) => Some(0.0),
Curve3::Circle(c) => Some(inflate(conic_second(
c.radius, c.radius, c.frame.x, c.frame.y, a, b,
))),
Curve3::Ellipse(e) => Some(inflate(conic_second(
e.semi_axis_x,
e.semi_axis_y,
e.frame.x,
e.frame.y,
a,
b,
))),
Curve3::BSpline(spline) => {
bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b).map(|(.., fourth)| fourth)
}
_ => None,
}
}
#[must_use]
pub(crate) fn curve_fourth_bound2(curve: &Curve2, a: Scalar, b: Scalar) -> Option<Scalar> {
if !(a.is_finite() && b.is_finite()) {
return None;
}
match curve {
Curve2::Line(_) => Some(0.0),
Curve2::Circle(c) => Some(inflate(conic_second(
c.radius,
c.radius,
lift2(c.frame.x),
lift2(c.frame.y),
a,
b,
))),
Curve2::Ellipse(e) => Some(inflate(conic_second(
e.semi_axis_x,
e.semi_axis_y,
lift2(e.frame.x),
lift2(e.frame.y),
a,
b,
))),
Curve2::BSpline(spline) => {
bspline_bounds(spline, |p| [p.x, p.y], a, b).map(|(.., fourth)| fourth)
}
_ => None,
}
}
fn conic_bounds(
rx: Scalar,
ry: Scalar,
x: Vec3,
y: Vec3,
a: Scalar,
b: Scalar,
) -> CurveDerivativeBounds {
let speed = ((rx * rx * x.length_squared()).max(ry * ry * y.length_squared())
+ (rx * ry * x.dot(y)).abs())
.sqrt();
CurveDerivativeBounds {
first: inflate(speed),
second: inflate(conic_second(rx, ry, x, y, a, b)),
third: inflate(speed),
}
}
#[must_use]
pub fn chord_bound2(curve: &Curve2, a: Scalar, b: Scalar) -> Option<Scalar> {
if !(a.is_finite() && b.is_finite()) {
return None;
}
if a == b {
return Some(0.0);
}
if breaks_inside(&continuity_breaks2(curve, 1), a, b) {
return None;
}
match curve {
Curve2::Line(_) | Curve2::Polyline(_) => Some(0.0),
Curve2::Circle(c) => Some(inflate(
circle_sagitta(c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b).unwrap_or_else(
|| {
taylor(
b - a,
conic_second(c.radius, c.radius, lift2(c.frame.x), lift2(c.frame.y), a, b),
)
},
),
)),
Curve2::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y], a, b)
.map(|(_, chord, _)| inflate(taylor(b - a, chord))),
Curve2::Chain(chain) => crate::chain::chain_chord_bound(chain, a, b),
Curve2::Implicit(c) => {
let (lo, hi) = ordered(a, b);
implicit::chord_bound(c, lo, hi).map(inflate)
}
_ => curve_derivative_bounds2(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
}
}
#[must_use]
pub fn chord_bound3(curve: &Curve3, a: Scalar, b: Scalar) -> Option<Scalar> {
if !(a.is_finite() && b.is_finite()) {
return None;
}
if a == b {
return Some(0.0);
}
if breaks_inside(&continuity_breaks3(curve, 1), a, b) {
return None;
}
match curve {
Curve3::Line(_) | Curve3::Polyline(_) => Some(0.0),
Curve3::Circle(c) => Some(inflate(
circle_sagitta(c.radius, c.frame.x, c.frame.y, a, b).unwrap_or_else(|| {
taylor(
b - a,
conic_second(c.radius, c.radius, c.frame.x, c.frame.y, a, b),
)
}),
)),
Curve3::BSpline(spline) => bspline_bounds(spline, |p| [p.x, p.y, p.z], a, b)
.map(|(_, chord, _)| inflate(taylor(b - a, chord))),
Curve3::Elevated(e) => crate::elevated::elevated_chord_bound(e, a, b),
Curve3::Banked(banked) => crate::elevated::banked_chord_bound(banked, a, b),
_ => curve_derivative_bounds3(curve, a, b).map(|d| inflate(taylor(b - a, d.second))),
}
}
fn taylor(h: Scalar, second: Scalar) -> Scalar {
h * h * 0.125 * second
}
#[must_use]
pub fn certifies_flattening2(curve: &Curve2) -> bool {
match curve {
Curve2::Line(_)
| Curve2::Polyline(_)
| Curve2::Circle(_)
| Curve2::Ellipse(_)
| Curve2::Sinusoid(_) => true,
Curve2::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
Curve2::Chain(c) => crate::chain::chain_certifies(c),
_ => false,
}
}
#[must_use]
pub fn certifies_flattening3(curve: &Curve3) -> bool {
match curve {
Curve3::Line(_) | Curve3::Polyline(_) | Curve3::Circle(_) | Curve3::Ellipse(_) => true,
Curve3::BSpline(b) => spline_weights_positive(b.weights.as_deref()),
Curve3::Elevated(e) => crate::elevated::certifies_elevated(e),
Curve3::Banked(_) => false,
_ => false,
}
}
fn spline_weights_positive(weights: Option<&[Scalar]>) -> bool {
weights.is_none_or(|w| w.iter().all(|w| w.is_finite() && *w > 0.0))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SurfaceBounds {
pub du: Scalar,
pub dv: Scalar,
pub duu: Scalar,
pub duv: Scalar,
pub dvv: Scalar,
pub smooth: bool,
pub interpolation: [Scalar; 3],
}
impl SurfaceBounds {
fn inflated(self) -> Self {
Self {
du: inflate(self.du),
dv: inflate(self.dv),
duu: inflate(self.duu),
duv: inflate(self.duv),
dvv: inflate(self.dvv),
smooth: self.smooth,
interpolation: self.interpolation.map(inflate),
}
}
fn finite(self) -> Option<Self> {
[self.du, self.dv, self.duu, self.duv, self.dvv]
.iter()
.chain(&self.interpolation)
.all(|v| v.is_finite())
.then_some(self)
}
}
#[derive(Debug, Clone)]
pub struct SurfaceBoundOracle<'a> {
surface: &'a Surface,
spline: Option<SplineNets>,
}
#[derive(Debug, Clone)]
struct SplineNets {
u_knots: Vec<Scalar>,
v_knots: Vec<Scalar>,
p: usize,
q: usize,
rows: usize,
cols: usize,
points: Vec<Vec<[Scalar; 3]>>,
weights: Vec<Vec<Scalar>>,
u_breaks: Vec<Scalar>,
v_breaks: Vec<Scalar>,
}
impl<'a> SurfaceBoundOracle<'a> {
#[must_use]
pub fn new(surface: &'a Surface) -> Option<Self> {
let spline = match surface {
Surface::Plane(_)
| Surface::Cylinder(_)
| Surface::EllipticalCylinder(_)
| Surface::Cone(_)
| Surface::Sphere(_)
| Surface::Torus(_) => None,
Surface::BSpline(b) => Some(SplineNets::new(b)?),
_ => return None,
};
Some(Self { surface, spline })
}
#[must_use]
pub fn bounds(&self, u: (Scalar, Scalar), v: (Scalar, Scalar)) -> Option<SurfaceBounds> {
let (u0, u1) = ordered(u.0, u.1);
let (v0, v1) = ordered(v.0, v.1);
if ![u0, u1, v0, v1].iter().all(|x| x.is_finite()) {
return None;
}
let raw = match self.surface {
Surface::Plane(p) => SurfaceBounds {
du: p.frame.x.length(),
dv: p.frame.y.length(),
duu: 0.0,
duv: 0.0,
dvv: 0.0,
smooth: true,
interpolation: [0.0; 3],
},
Surface::Cylinder(c) => {
let s = frame_stretch3(&c.frame);
SurfaceBounds {
du: s * c.radius.abs(),
dv: c.frame.z.length(),
duu: s * c.radius.abs(),
duv: 0.0,
dvv: 0.0,
smooth: true,
interpolation: [0.0; 3],
}
}
Surface::EllipticalCylinder(c) => {
let s = frame_stretch3(&c.frame);
let r = c.semi_axis_x.abs().max(c.semi_axis_y.abs());
SurfaceBounds {
du: s * r,
dv: c.frame.z.length(),
duu: s * r,
duv: 0.0,
dvv: 0.0,
smooth: true,
interpolation: [0.0; 3],
}
}
Surface::Cone(c) => {
let s = frame_stretch3(&c.frame);
let slope = c.semi_angle.tan();
let (r0, r1) = (c.radius + v0 * slope, c.radius + v1 * slope);
if r0 < 0.0 || r1 < 0.0 {
return None;
}
let rho = r0.max(r1);
SurfaceBounds {
du: s * rho,
dv: s * (1.0 + slope * slope).sqrt(),
duu: s * rho,
duv: s * slope.abs(),
dvv: 0.0,
smooth: true,
interpolation: [0.0; 3],
}
}
Surface::Sphere(sphere) => {
let s = frame_stretch3(&sphere.frame) * sphere.radius.abs();
let cos = max_abs_cos(v0, v1);
SurfaceBounds {
du: s * cos,
dv: s,
duu: s * cos,
duv: s * max_abs_sin(v0, v1),
dvv: s,
smooth: true,
interpolation: [0.0; 3],
}
}
Surface::Torus(t) => {
let s = frame_stretch3(&t.frame);
let (cmin, cmax) = cos_range(v0, v1);
let r = t.minor_radius.abs();
let ring = (t.major_radius + r * cmin)
.abs()
.max((t.major_radius + r * cmax).abs());
SurfaceBounds {
du: s * ring,
dv: s * r,
duu: s * ring,
duv: s * r * max_abs_sin(v0, v1),
dvv: s * r,
smooth: true,
interpolation: [0.0; 3],
}
}
Surface::BSpline(_) => self.spline.as_ref()?.bounds(u0, u1, v0, v1)?,
_ => return None,
};
let raw = if matches!(self.surface, Surface::BSpline(_)) {
raw
} else {
SurfaceBounds {
interpolation: [raw.duu, raw.duv, raw.dvv],
..raw
}
};
raw.inflated().finite()
}
}
impl SplineNets {
fn new(b: &BSplineSurface) -> Option<Self> {
let rows = b.control_points.len();
let cols = b.control_points.first()?.len();
if b.control_points.iter().any(|row| row.len() != cols) {
return None;
}
let ua = SplineAxis::new(&b.u_knots, &b.u_multiplicities, b.u_degree, rows, "u").ok()?;
let va = SplineAxis::new(&b.v_knots, &b.v_multiplicities, b.v_degree, cols, "v").ok()?;
let weights: Vec<Vec<Scalar>> = match &b.weights {
Some(net) => {
if net.len() != rows || net.iter().any(|row| row.len() != cols) {
return None;
}
net.clone()
}
None => vec![vec![1.0; cols]; rows],
};
if weights
.iter()
.flatten()
.any(|w| !(w.is_finite() && *w > 0.0))
{
return None;
}
let points: Vec<Vec<[Scalar; 3]>> = b
.control_points
.iter()
.map(|row| row.iter().map(|p| [p.x, p.y, p.z]).collect())
.collect();
if points.iter().flatten().flatten().any(|c| !c.is_finite()) {
return None;
}
let breaks = |knots: &[Scalar], mults: &[u32], degree: u16| {
let interior = knots.len().saturating_sub(2);
knots
.iter()
.zip(mults)
.skip(1)
.take(interior)
.filter(|(_, &m)| m as usize >= usize::from(degree))
.map(|(&k, _)| k)
.collect::<Vec<_>>()
};
Some(Self {
u_breaks: breaks(&b.u_knots, &b.u_multiplicities, b.u_degree),
v_breaks: breaks(&b.v_knots, &b.v_multiplicities, b.v_degree),
u_knots: ua.knots,
v_knots: va.knots,
p: ua.degree,
q: va.degree,
rows,
cols,
points,
weights,
})
}
#[allow(clippy::needless_range_loop)]
fn bounds(&self, u0: Scalar, u1: Scalar, v0: Scalar, v1: Scalar) -> Option<SurfaceBounds> {
let clamp = |x: Scalar, knots: &[Scalar], d: usize, n: usize| x.clamp(knots[d], knots[n]);
let (cu0, cu1) = (
clamp(u0, &self.u_knots, self.p, self.rows),
clamp(u1, &self.u_knots, self.p, self.rows),
);
let (cv0, cv1) = (
clamp(v0, &self.v_knots, self.q, self.cols),
clamp(v1, &self.v_knots, self.q, self.cols),
);
let su0 = span_in(&self.u_knots, self.rows, self.p, cu0);
let su1 = span_in(&self.u_knots, self.rows, self.p, cu1);
let sv0 = span_in(&self.v_knots, self.cols, self.q, cv0);
let sv1 = span_in(&self.v_knots, self.cols, self.q, cv1);
let (r0, r1) = (su0 - self.p, su1);
let (c0, c1) = (sv0 - self.q, sv1);
let mut low = [Scalar::INFINITY; 3];
let mut high = [Scalar::NEG_INFINITY; 3];
for row in &self.points[r0..=r1] {
for p in &row[c0..=c1] {
for k in 0..3 {
low[k] = low[k].min(p[k]);
high[k] = high[k].max(p[k]);
}
}
}
let centre: [Scalar; 3] = core::array::from_fn(|k| 0.5 * (low[k] + high[k]));
let mut radius: Scalar = 0.0;
let mut w_min = Scalar::INFINITY;
let (wr0, wr1) = (r0, r1.min(self.rows - 1));
let (wc0, wc1) = (c0, c1.min(self.cols - 1));
let mut hom: Vec<Vec<[Scalar; 4]>> = Vec::with_capacity(wr1 + 1 - wr0);
for i in wr0..=wr1 {
let mut row = Vec::with_capacity(wc1 + 1 - wc0);
for j in wc0..=wc1 {
let p = self.points[i][j];
let w = self.weights[i][j];
let d: [Scalar; 3] = core::array::from_fn(|k| p[k] - centre[k]);
radius = radius.max(norm(&d));
w_min = w_min.min(w);
row.push([d[0] * w, d[1] * w, d[2] * w, w]);
}
hom.push(row);
}
let du_net =
|net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
let mut out = Vec::new();
for i in 0..net.len().saturating_sub(1) {
let width = knots[offset + i + degree + 1] - knots[offset + i + 1];
let f = if width > 0.0 {
degree as Scalar / width
} else {
0.0
};
out.push(
net[i]
.iter()
.zip(&net[i + 1])
.map(|(a, b)| core::array::from_fn(|k| (b[k] - a[k]) * f))
.collect::<Vec<[Scalar; 4]>>(),
);
}
out
};
let dv_net =
|net: &Vec<Vec<[Scalar; 4]>>, knots: &[Scalar], degree: usize, offset: usize| {
net.iter()
.map(|row| {
(0..row.len().saturating_sub(1))
.map(|j| {
let width = knots[offset + j + degree + 1] - knots[offset + j + 1];
let f = if width > 0.0 {
degree as Scalar / width
} else {
0.0
};
core::array::from_fn(|k| (row[j + 1][k] - row[j][k]) * f)
})
.collect::<Vec<[Scalar; 4]>>()
})
.collect::<Vec<_>>()
};
let sup = |net: &Vec<Vec<[Scalar; 4]>>| {
let mut a: Scalar = 0.0;
let mut w: Scalar = 0.0;
for p in net.iter().flatten() {
a = a.max((p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt());
w = w.max(p[3].abs());
}
(a, w)
};
let (p, q) = (self.p, self.q);
let nu = if p >= 1 {
du_net(&hom, &self.u_knots, p, wr0)
} else {
Vec::new()
};
let nuu = if p >= 2 {
du_net(&nu, &self.u_knots, p - 1, wr0 + 1)
} else {
Vec::new()
};
let nv = if q >= 1 {
dv_net(&hom, &self.v_knots, q, wc0)
} else {
Vec::new()
};
let nvv = if q >= 2 {
dv_net(&nv, &self.v_knots, q - 1, wc0 + 1)
} else {
Vec::new()
};
let nuv = if p >= 1 && q >= 1 {
dv_net(&nu, &self.v_knots, q, wc0)
} else {
Vec::new()
};
let (au, wu) = sup(&nu);
let (av, wv) = sup(&nv);
let (auu, wuu) = sup(&nuu);
let (avv, wvv) = sup(&nvv);
let (auv, wuv) = sup(&nuv);
if !(w_min.is_finite() && w_min > 0.0) {
return None;
}
let su = (au + wu * radius) / w_min;
let sv = (av + wv * radius) / w_min;
let suu = (auu + 2.0 * wu * su + wuu * radius) / w_min;
let svv = (avv + 2.0 * wv * sv + wvv * radius) / w_min;
let suv = (auv + wu * sv + wv * su + wuv * radius) / w_min;
let inside = cu0 == u0 && cu1 == u1 && cv0 == v0 && cv1 == v1;
let smooth = inside
&& !breaks_inside(&self.u_breaks, cu0, cu1)
&& !breaks_inside(&self.v_breaks, cv0, cv1);
Some(SurfaceBounds {
du: su,
dv: sv,
duu: suu,
duv: suv,
dvv: svv,
smooth,
interpolation: [
(auu + radius * wuu) / w_min,
(auv + radius * wuv) / w_min,
(avv + radius * wvv) / w_min,
],
})
}
}
#[cfg(test)]
mod tests;