use crate::curve::{
basis_derivatives_into, basis_functions_into, knot_clamp, knot_domain, knot_find_span,
validate_knots, MAX_STACK_DEGREE, MAX_STACK_ORDER,
};
use crate::{KnotVector, NurbsCurve, Vec3, Vec4};
use serde::{Deserialize, Serialize};
#[path = "surface/extension.rs"]
mod extension;
pub use extension::{
ExtendRefusal, SurfaceSide, WeightSite, MAXIMUM_FOLD_ANGLE, MAXIMUM_GROWTH_RATIO,
};
const EPS: f64 = crate::tolerance::FP_FLOOR;
const NEAR_AXIS_RELATIVE_TOLERANCE: f64 = 1e-4;
pub(crate) const TENSOR_MAX_BLOCK: usize = 24;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NurbsSurface {
pub degree_u: usize,
pub degree_v: usize,
pub knots_u: Vec<f64>,
pub knots_v: Vec<f64>,
pub control_points: Vec<Vec<Vec4>>,
#[serde(skip, default)]
analytic: std::cell::OnceCell<Option<crate::AnalyticSurface>>,
#[serde(skip, default)]
affine_sphere: std::cell::OnceCell<Option<crate::affine_sphere::AffineSphere>>,
#[serde(skip, default)]
validated: std::cell::Cell<bool>,
#[serde(skip, default)]
closed_directions: std::cell::OnceCell<(bool, bool)>,
#[serde(skip, default)]
pub(crate) projection_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
#[serde(skip, default)]
pub(crate) projection_dense_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
#[serde(skip, default)]
pub(crate) projection_ring_grid: std::cell::OnceCell<Vec<Vec3>>,
#[serde(skip, default)]
pub(crate) projection_cells: std::cell::OnceCell<Option<crate::projection::ProjectionCells>>,
}
impl NurbsSurface {
pub fn new(
degree_u: usize,
degree_v: usize,
knots_u: Vec<f64>,
knots_v: Vec<f64>,
control_points: Vec<Vec<Vec4>>,
) -> Result<Self, String> {
let surface = Self {
degree_u,
degree_v,
knots_u,
knots_v,
control_points,
analytic: std::cell::OnceCell::new(),
affine_sphere: std::cell::OnceCell::new(),
validated: std::cell::Cell::new(false),
closed_directions: std::cell::OnceCell::new(),
projection_grid: std::cell::OnceCell::new(),
projection_dense_grid: std::cell::OnceCell::new(),
projection_ring_grid: std::cell::OnceCell::new(),
projection_cells: std::cell::OnceCell::new(),
};
surface.ensure_valid()?;
Ok(surface)
}
pub fn closed_directions(&self) -> Result<(bool, bool), String> {
if let Some(&cached) = self.closed_directions.get() {
return Ok(cached);
}
const CLOSED_SEAM_TOLERANCE: f64 = 1e-6;
let [u0, u1] = self.domain_u()?;
let [v0, v1] = self.domain_v()?;
let closed_along = |direction_u: bool| -> Result<bool, String> {
let mut travels = false;
for fraction in [0.17, 0.5, 0.83] {
let (a, b, middle) = if direction_u {
let v = v0 + (v1 - v0) * fraction;
(
self.evaluate(u0, v)?,
self.evaluate(u1, v)?,
self.evaluate(0.5 * (u0 + u1), v)?,
)
} else {
let u = u0 + (u1 - u0) * fraction;
(
self.evaluate(u, v0)?,
self.evaluate(u, v1)?,
self.evaluate(u, 0.5 * (v0 + v1))?,
)
};
if a.sub(b).length() > CLOSED_SEAM_TOLERANCE {
return Ok(false);
}
if middle.sub(a).length() > CLOSED_SEAM_TOLERANCE {
travels = true;
}
}
Ok(travels)
};
let value = (closed_along(true)?, closed_along(false)?);
Ok(*self.closed_directions.get_or_init(|| value))
}
fn ensure_valid(&self) -> Result<(), String> {
if self.validated.get() {
return Ok(());
}
validate_knots(&self.knots_u, self.degree_u)?;
validate_knots(&self.knots_v, self.degree_v)?;
let rows = self.knots_u.len() - self.degree_u - 1;
let columns = self.knots_v.len() - self.degree_v - 1;
if self.control_points.len() != rows {
return Err(format!(
"NurbsSurface: u knot vector implies {} rows, got {}",
rows,
self.control_points.len()
));
}
for row in &self.control_points {
if row.len() != columns {
return Err(format!(
"NurbsSurface: v knot vector implies {} columns, got {}",
columns,
row.len()
));
}
if row.iter().any(|point| {
point.w <= EPS
|| ![point.x, point.y, point.z, point.w]
.iter()
.all(|value| value.is_finite())
}) {
return Err(
"NurbsSurface: control points must be finite with positive weights".into(),
);
}
}
self.validated.set(true);
Ok(())
}
pub fn domain_u(&self) -> Result<[f64; 2], String> {
self.ensure_valid()?;
Ok(knot_domain(&self.knots_u, self.degree_u))
}
pub fn domain_v(&self) -> Result<[f64; 2], String> {
self.ensure_valid()?;
Ok(knot_domain(&self.knots_v, self.degree_v))
}
pub fn analytic(&self) -> Option<&crate::AnalyticSurface> {
self.analytic
.get_or_init(|| crate::analytic_surface::recognize(self))
.as_ref()
}
pub(crate) fn affine_sphere(&self) -> Option<&crate::affine_sphere::AffineSphere> {
self.affine_sphere
.get_or_init(|| {
if !crate::affine_sphere::affine_sphere_enabled() || self.analytic().is_some() {
None
} else {
crate::affine_sphere::recognize(self)
}
})
.as_ref()
}
pub(crate) fn warm_caches(&self) -> Vec<&'static str> {
let mut warm = Vec::new();
let cells = [
("analytic", self.analytic.get().is_some()),
("affine_sphere", self.affine_sphere.get().is_some()),
("validated", self.validated.get()),
("closed", self.closed_directions.get().is_some()),
("grid", self.projection_grid.get().is_some()),
("dense", self.projection_dense_grid.get().is_some()),
("ring", self.projection_ring_grid.get().is_some()),
("cells", self.projection_cells.get().is_some()),
];
for (name, held) in cells {
if held {
warm.push(name);
}
}
warm
}
pub(crate) fn exchange_caches(&mut self, names: &[&str], from: Option<&Self>) {
let wants = |name: &str| names.iter().any(|&n| n == name || n == "all");
if wants("analytic") {
self.analytic = from.map(|f| f.analytic.clone()).unwrap_or_default();
}
if wants("affine_sphere") {
self.affine_sphere = from.map(|f| f.affine_sphere.clone()).unwrap_or_default();
}
if wants("validated") {
self.validated = std::cell::Cell::new(from.is_some_and(|f| f.validated.get()));
}
if wants("closed") {
self.closed_directions = from.map(|f| f.closed_directions.clone()).unwrap_or_default();
}
if wants("grid") {
self.projection_grid = from.map(|f| f.projection_grid.clone()).unwrap_or_default();
}
if wants("dense") {
self.projection_dense_grid = from.map(|f| f.projection_dense_grid.clone()).unwrap_or_default();
}
if wants("ring") {
self.projection_ring_grid = from.map(|f| f.projection_ring_grid.clone()).unwrap_or_default();
}
if wants("cells") {
self.projection_cells = from.map(|f| f.projection_cells.clone()).unwrap_or_default();
}
}
fn knot_vectors(&self) -> Result<(KnotVector, KnotVector), String> {
Ok((
KnotVector::new(self.knots_u.clone(), self.degree_u)?,
KnotVector::new(self.knots_v.clone(), self.degree_v)?,
))
}
#[cfg_attr(feature = "profile-wall", track_caller)]
pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String> {
let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
crate::projection_profile::count(crate::projection_profile::LANE_EVALUATE, self);
self.ensure_valid()?;
if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
return self.evaluate_homogeneous_heap(u, v);
}
let u = knot_clamp(&self.knots_u, self.degree_u, u);
let v = knot_clamp(&self.knots_v, self.degree_v, v);
let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
if analytic_point_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
&& matches!(self.analytic.get().and_then(|carrier| carrier.as_ref()),
Some(crate::AnalyticSurface::Plane { .. } | crate::AnalyticSurface::RuledRevolution { .. }
| crate::AnalyticSurface::Sphere { .. } | crate::AnalyticSurface::Torus { .. })) {
return Ok(match (self.degree_u, self.degree_v) {
(1, 1) => self.analytic_point_small::<1, 1>(u, v, span_u, span_v),
(2, 1) => self.analytic_point_small::<2, 1>(u, v, span_u, span_v),
(1, 2) => self.analytic_point_small::<1, 2>(u, v, span_u, span_v),
(2, 2) => self.analytic_point_small::<2, 2>(u, v, span_u, span_v),
_ => unreachable!(),
});
}
let mut basis_u = [0.0f64; MAX_STACK_ORDER];
let mut basis_v = [0.0f64; MAX_STACK_ORDER];
basis_functions_into(&self.knots_u, self.degree_u, span_u, u, &mut basis_u);
basis_functions_into(&self.knots_v, self.degree_v, span_v, v, &mut basis_v);
let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
let mut point = Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
for (i, value_u) in basis_u[..=self.degree_u].iter().enumerate() {
let row = &self.control_points[span_u - self.degree_u + i];
for (j, value_v) in basis_v[..=self.degree_v].iter().enumerate() {
point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
}
}
Ok(point)
}
fn analytic_point_small<const DU: usize, const DV: usize>(&self, u: f64, v: f64, span_u: usize, span_v: usize) -> Vec4 {
fn basis<const D: usize>(knots: &[f64], span: usize, parameter: f64) -> [f64; 3] {
let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::BASIS);
let mut values = [0.0; 3];
let mut left = [0.0; 3];
let mut right = [0.0; 3];
values[0] = 1.0;
for j in 1..=D {
left[j] = parameter - knots[span + 1 - j];
right[j] = knots[span + j] - parameter;
let mut saved = 0.0;
for r in 0..j {
let denominator = right[r + 1] + left[j - r];
let temporary = if denominator.abs() <= EPS { 0.0 } else { values[r] / denominator };
values[r] = saved + right[r + 1] * temporary;
saved = left[j - r] * temporary;
}
values[j] = saved;
}
values
}
let basis_u = basis::<DU>(&self.knots_u, span_u, u);
let basis_v = basis::<DV>(&self.knots_v, span_v, v);
let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
let mut point = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
for (i, value_u) in basis_u[..=DU].iter().enumerate() {
let row = &self.control_points[span_u - DU + i];
for (j, value_v) in basis_v[..=DV].iter().enumerate() {
point = point.add(row[span_v - DV + j].scale(value_u * value_v));
}
}
point
}
fn evaluate_homogeneous_heap(&self, u: f64, v: f64) -> Result<Vec4, String> {
let (knot_u, knot_v) = self.knot_vectors()?;
let u = knot_u.clamp_param(u);
let v = knot_v.clamp_param(v);
let span_u = knot_u.find_span(u);
let span_v = knot_v.find_span(v);
let basis_u = knot_u.basis_functions(span_u, u);
let basis_v = knot_v.basis_functions(span_v, v);
let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
let mut point = Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
for (i, value_u) in basis_u.iter().enumerate() {
let row = &self.control_points[span_u - self.degree_u + i];
for (j, value_v) in basis_v.iter().enumerate() {
point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
}
}
Ok(point)
}
#[cfg_attr(feature = "profile-wall", track_caller)]
pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String> {
let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
self.evaluate_homogeneous(u, v)?.point()
}
#[cfg_attr(feature = "profile-wall", track_caller)]
pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String> {
let _reader = crate::reader_profile::Point::open(crate::reader_profile::SURFACE, std::panic::Location::caller());
let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::POINT);
if small_derivatives_enabled() {
return Ok(self.derivatives_extended_small(u, v, 0)?[0][0]);
}
Ok(self.derivatives_extended(u, v, 0)?[0][0])
}
pub(crate) fn derivatives_extended_small(
&self,
u: f64,
v: f64,
derivative_count: usize,
) -> Result<[[Vec3; 3]; 3], String> {
debug_assert!(derivative_count <= 2);
let [u0, u1] = self.domain_u()?;
let [v0, v1] = self.domain_v()?;
let (closed_u, closed_v) = self.closed_directions()?;
let mut uu = u;
let mut vv = v;
if closed_u && (u < u0 || u > u1) {
uu = u0 + (u - u0).rem_euclid(u1 - u0);
}
if closed_v && (v < v0 || v > v1) {
vv = v0 + (v - v0).rem_euclid(v1 - v0);
}
let du_out = if uu < u0 { uu - u0 } else if uu > u1 { uu - u1 } else { 0.0 };
let dv_out = if vv < v0 { vv - v0 } else if vv > v1 { vv - v1 } else { 0.0 };
if du_out == 0.0 && dv_out == 0.0 {
return self.derivatives_small(uu, vv, derivative_count);
}
let anchor_u = uu - du_out;
let anchor_v = vv - dv_out;
let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
let mut result = [[Vec3::default(); 3]; 3];
result[0][0] = base[0][0]
.add(base[1][0].scale(du_out))
.add(base[0][1].scale(dv_out))
.add(base[1][1].scale(du_out * dv_out));
if derivative_count >= 1 {
result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
result[0][1] = base[0][1].add(base[1][1].scale(du_out));
result[1][1] = base[1][1];
}
if derivative_count >= 2 {
if du_out == 0.0 {
result[2][0] = base[2][0];
}
if dv_out == 0.0 {
result[0][2] = base[0][2];
}
}
Ok(result)
}
pub fn derivatives_extended(
&self,
u: f64,
v: f64,
derivative_count: usize,
) -> Result<Vec<Vec<Vec3>>, String> {
if derivative_count <= 2 && small_derivatives_enabled() {
let small = self.derivatives_extended_small(u, v, derivative_count)?;
let order = derivative_count + 1;
return Ok((0..order).map(|k| (0..order).map(|l| small[k][l]).collect()).collect());
}
let [u0, u1] = self.domain_u()?;
let [v0, v1] = self.domain_v()?;
let (closed_u, closed_v) = self.closed_directions()?;
let mut uu = u;
let mut vv = v;
if closed_u && (u < u0 || u > u1) {
uu = u0 + (u - u0).rem_euclid(u1 - u0);
}
if closed_v && (v < v0 || v > v1) {
vv = v0 + (v - v0).rem_euclid(v1 - v0);
}
let du_out = if uu < u0 {
uu - u0
} else if uu > u1 {
uu - u1
} else {
0.0
};
let dv_out = if vv < v0 {
vv - v0
} else if vv > v1 {
vv - v1
} else {
0.0
};
if du_out == 0.0 && dv_out == 0.0 {
return self.derivatives(uu, vv, derivative_count);
}
let anchor_u = uu - du_out;
let anchor_v = vv - dv_out;
let base = self.derivatives(anchor_u, anchor_v, derivative_count.max(2))?;
let order = derivative_count + 1;
let mut result = vec![vec![Vec3::default(); order]; order];
result[0][0] = base[0][0]
.add(base[1][0].scale(du_out))
.add(base[0][1].scale(dv_out))
.add(base[1][1].scale(du_out * dv_out));
if derivative_count >= 1 {
result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
result[0][1] = base[0][1].add(base[1][1].scale(du_out));
result[1][1] = base[1][1];
}
if derivative_count >= 2 {
let second = self.derivatives(anchor_u, anchor_v, 2)?;
if du_out == 0.0 {
result[2][0] = second[2][0];
}
if dv_out == 0.0 {
result[0][2] = second[0][2];
}
}
Ok(result)
}
pub fn derivatives(
&self,
u: f64,
v: f64,
derivative_count: usize,
) -> Result<Vec<Vec<Vec3>>, String> {
crate::projection_profile::count(crate::projection_profile::LANE_DERIVATIVES, self);
self.ensure_valid()?;
let du = derivative_count.min(self.degree_u);
let dv = derivative_count.min(self.degree_v);
let stride = derivative_count + 1;
let zero = Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
let mut homogeneous = vec![zero; stride * stride];
if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
let u = knot_clamp(&self.knots_u, self.degree_u, u);
let v = knot_clamp(&self.knots_v, self.degree_v, v);
let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
basis_derivatives_into(
&self.knots_u,
self.degree_u,
span_u,
u,
du,
&mut basis_u[..=du],
);
basis_derivatives_into(
&self.knots_v,
self.degree_v,
span_v,
v,
dv,
&mut basis_v[..=dv],
);
let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
for k in 0..=du {
for l in 0..=dv {
if k + l > derivative_count {
continue;
}
let mut point = zero;
for i in 0..=self.degree_u {
let row = &self.control_points[span_u - self.degree_u + i];
for j in 0..=self.degree_v {
point = point.add(
row[span_v - self.degree_v + j]
.scale(basis_u[k][i] * basis_v[l][j]),
);
}
}
homogeneous[k * stride + l] = point;
}
}
} else {
let (knot_u, knot_v) = self.knot_vectors()?;
let u = knot_u.clamp_param(u);
let v = knot_v.clamp_param(v);
let span_u = knot_u.find_span(u);
let span_v = knot_v.find_span(v);
let basis_u = knot_u.basis_derivatives(span_u, u, du);
let basis_v = knot_v.basis_derivatives(span_v, v, dv);
let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
for k in 0..=du {
for l in 0..=dv {
if k + l > derivative_count {
continue;
}
let mut point = zero;
for i in 0..=self.degree_u {
let row = &self.control_points[span_u - self.degree_u + i];
for j in 0..=self.degree_v {
point = point.add(
row[span_v - self.degree_v + j]
.scale(basis_u[k][i] * basis_v[l][j]),
);
}
}
homogeneous[k * stride + l] = point;
}
}
}
let mut result = vec![vec![Vec3::default(); derivative_count + 1]; derivative_count + 1];
let weight = homogeneous[0].w;
if weight.abs() <= EPS {
return Err("NurbsSurface: zero evaluated weight".into());
}
for k in 0..=derivative_count {
for l in 0..=derivative_count - k {
if k > du || l > dv {
continue;
}
let at = |row: usize, column: usize| homogeneous[row * stride + column];
let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
for j in 1..=l {
value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
}
for i in 1..=k {
value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
let mut mixed = Vec3::default();
for j in 1..=l {
mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
}
value = value.sub(mixed.scale(binomial(k, i)));
}
result[k][l] = value.scale(1.0 / weight);
}
}
Ok(result)
}
pub(crate) fn derivatives_small(
&self,
u: f64,
v: f64,
derivative_count: usize,
) -> Result<[[Vec3; 3]; 3], String> {
if derivative_count == 0 && switch!("BREP_SURFACE_POINT_ONLY", false)
&& u.is_finite() && v.is_finite()
&& self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
&& !analytic_point_on() {
let point = self.evaluate_homogeneous(u, v)?;
if point.w.abs() <= EPS {
return Err("NurbsSurface: zero evaluated weight".into());
}
let mut result = [[Vec3::default(); 3]; 3];
result[0][0] = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
return Ok(result);
}
debug_assert!(derivative_count <= 2);
self.ensure_valid()?;
let du = derivative_count.min(self.degree_u);
let dv = derivative_count.min(self.degree_v);
let stride = derivative_count + 1;
let zero = Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
let mut homogeneous = [zero; 9];
if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
let u = knot_clamp(&self.knots_u, self.degree_u, u);
let v = knot_clamp(&self.knots_v, self.degree_v, v);
let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; 3];
let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; 3];
basis_derivatives_into(
&self.knots_u,
self.degree_u,
span_u,
u,
du,
&mut basis_u[..=du],
);
basis_derivatives_into(
&self.knots_v,
self.degree_v,
span_v,
v,
dv,
&mut basis_v[..=dv],
);
let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
for k in 0..=du {
for l in 0..=dv {
if k + l > derivative_count {
continue;
}
let mut point = zero;
for i in 0..=self.degree_u {
let row = &self.control_points[span_u - self.degree_u + i];
for j in 0..=self.degree_v {
point = point.add(
row[span_v - self.degree_v + j]
.scale(basis_u[k][i] * basis_v[l][j]),
);
}
}
homogeneous[k * stride + l] = point;
}
}
} else {
let (knot_u, knot_v) = self.knot_vectors()?;
let u = knot_u.clamp_param(u);
let v = knot_v.clamp_param(v);
let span_u = knot_u.find_span(u);
let span_v = knot_v.find_span(v);
let basis_u = knot_u.basis_derivatives(span_u, u, du);
let basis_v = knot_v.basis_derivatives(span_v, v, dv);
let _reader_part = crate::reader_profile::Part::open(crate::reader_profile::ACCUMULATE);
for k in 0..=du {
for l in 0..=dv {
if k + l > derivative_count {
continue;
}
let mut point = zero;
for i in 0..=self.degree_u {
let row = &self.control_points[span_u - self.degree_u + i];
for j in 0..=self.degree_v {
point = point.add(
row[span_v - self.degree_v + j]
.scale(basis_u[k][i] * basis_v[l][j]),
);
}
}
homogeneous[k * stride + l] = point;
}
}
}
let mut result = [[Vec3::default(); 3]; 3];
let weight = homogeneous[0].w;
if weight.abs() <= EPS {
return Err("NurbsSurface: zero evaluated weight".into());
}
for k in 0..=derivative_count {
for l in 0..=derivative_count - k {
if k > du || l > dv {
continue;
}
let at = |row: usize, column: usize| homogeneous[row * stride + column];
let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
for j in 1..=l {
value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
}
for i in 1..=k {
value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
let mut mixed = Vec3::default();
for j in 1..=l {
mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
}
value = value.sub(mixed.scale(binomial(k, i)));
}
result[k][l] = value.scale(1.0 / weight);
}
}
Ok(result)
}
#[inline]
pub(crate) fn deriv1_tensor_supported(&self) -> bool {
self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
}
pub(crate) fn deriv1_tensor_each<F>(
&self,
us: &[f64],
vs: &[f64],
mut f: F,
) -> Result<(), String>
where
F: FnMut(usize, usize, Vec3, Vec3, Vec3) -> Result<(), String>,
{
self.ensure_valid()?;
const MAX_BLOCK: usize = TENSOR_MAX_BLOCK;
if !self.deriv1_tensor_supported() || us.len() > MAX_BLOCK || vs.len() > MAX_BLOCK {
return Err("NurbsSurface: tensor block outside the stack evaluator".into());
}
let du = 1usize.min(self.degree_u);
let dv = 1usize.min(self.degree_v);
let mut span_u = [0usize; MAX_BLOCK];
let mut span_v = [0usize; MAX_BLOCK];
let mut basis_u = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
let mut basis_v = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
for (index, &value) in us.iter().enumerate() {
let value = knot_clamp(&self.knots_u, self.degree_u, value);
span_u[index] = knot_find_span(&self.knots_u, self.degree_u, value);
basis_derivatives_into(
&self.knots_u,
self.degree_u,
span_u[index],
value,
du,
&mut basis_u[index][..=du],
);
}
for (index, &value) in vs.iter().enumerate() {
let value = knot_clamp(&self.knots_v, self.degree_v, value);
span_v[index] = knot_find_span(&self.knots_v, self.degree_v, value);
basis_derivatives_into(
&self.knots_v,
self.degree_v,
span_v[index],
value,
dv,
&mut basis_v[index][..=dv],
);
}
let zero = Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
for i in 0..us.len() {
let rows = &self.control_points[span_u[i] - self.degree_u..];
for j in 0..vs.len() {
let column = span_v[j] - self.degree_v;
let sum = |k: usize, l: usize| {
let mut point = zero;
for row_index in 0..=self.degree_u {
let row = &rows[row_index];
for column_index in 0..=self.degree_v {
point = point.add(
row[column + column_index]
.scale(basis_u[i][k][row_index] * basis_v[j][l][column_index]),
);
}
}
point
};
let h00 = sum(0, 0);
let h01 = if dv >= 1 { sum(0, 1) } else { zero };
let h10 = if du >= 1 { sum(1, 0) } else { zero };
let weight = h00.w;
if weight.abs() <= EPS {
return Err("NurbsSurface: zero evaluated weight".into());
}
let inverse = 1.0 / weight;
let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
let partial_v = if dv >= 1 {
Vec3::new(h01.x, h01.y, h01.z)
.sub(point.scale(h01.w))
.scale(inverse)
} else {
Vec3::default()
};
let partial_u = if du >= 1 {
Vec3::new(h10.x, h10.y, h10.z)
.sub(point.scale(h10.w))
.scale(inverse)
} else {
Vec3::default()
};
f(i, j, point, partial_u, partial_v)?;
}
}
Ok(())
}
#[inline]
pub(crate) fn deriv1(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
match (self.degree_u, self.degree_v) {
(1, 1) => self.deriv1_small_fixed::<1, 1>(u, v),
(2, 1) => self.deriv1_small_fixed::<2, 1>(u, v),
(1, 2) => self.deriv1_small_fixed::<1, 2>(u, v),
(2, 2) => self.deriv1_small_fixed::<2, 2>(u, v),
_ => {
let g = self.derivatives_small(u, v, 1)?;
Ok((g[0][0], g[1][0], g[0][1]))
}
}
}
fn deriv1_small_fixed<const DEGREE_U: usize, const DEGREE_V: usize>(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
self.ensure_valid()?;
let du = DEGREE_U.min(1);
let dv = DEGREE_V.min(1);
let u = knot_clamp(&self.knots_u, DEGREE_U, u);
let v = knot_clamp(&self.knots_v, DEGREE_V, v);
let span_u = knot_find_span(&self.knots_u, DEGREE_U, u);
let span_v = knot_find_span(&self.knots_v, DEGREE_V, v);
let mut basis_u = [[0.0; MAX_STACK_ORDER]; 2];
let mut basis_v = [[0.0; MAX_STACK_ORDER]; 2];
basis_derivatives_into(&self.knots_u, DEGREE_U, span_u, u, du, &mut basis_u[..=du]);
basis_derivatives_into(&self.knots_v, DEGREE_V, span_v, v, dv, &mut basis_v[..=dv]);
let zero = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
let sum = |k: usize, l: usize| {
let mut value = zero;
for i in 0..=DEGREE_U {
let row = &self.control_points[span_u - DEGREE_U + i];
for j in 0..=DEGREE_V {
value = value.add(row[span_v - DEGREE_V + j].scale(basis_u[k][i] * basis_v[l][j]));
}
}
value
};
let h00 = sum(0, 0);
let h01 = if dv == 1 { sum(0, 1) } else { zero };
let h10 = if du == 1 { sum(1, 0) } else { zero };
if h00.w.abs() <= EPS { return Err("NurbsSurface: zero evaluated weight".into()); }
let inverse = 1.0 / h00.w;
let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
let partial_v = if dv == 1 {
Vec3::new(h01.x, h01.y, h01.z).sub(point.scale(1.0 * h01.w)).scale(inverse)
} else { Vec3::default() };
let partial_u = if du == 1 {
Vec3::new(h10.x, h10.y, h10.z).sub(point.scale(1.0 * h10.w))
.sub(Vec3::default().scale(1.0)).scale(inverse)
} else { Vec3::default() };
Ok((point, partial_u, partial_v))
}
pub(crate) fn deriv1_extended(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
let [u0, u1] = self.domain_u()?;
let [v0, v1] = self.domain_v()?;
let (closed_u, closed_v) = self.closed_directions()?;
let mut uu = u;
let mut vv = v;
if closed_u && (u < u0 || u > u1) {
uu = u0 + (u - u0).rem_euclid(u1 - u0);
}
if closed_v && (v < v0 || v > v1) {
vv = v0 + (v - v0).rem_euclid(v1 - v0);
}
let du_out = if uu < u0 {
uu - u0
} else if uu > u1 {
uu - u1
} else {
0.0
};
let dv_out = if vv < v0 {
vv - v0
} else if vv > v1 {
vv - v1
} else {
0.0
};
if du_out == 0.0 && dv_out == 0.0 {
if analytic_deriv1_on() && matches!((self.degree_u, self.degree_v), (1, 1) | (2, 1) | (1, 2) | (2, 2))
&& matches!(self.analytic(), Some(crate::AnalyticSurface::Plane { .. }
| crate::AnalyticSurface::RuledRevolution { .. } | crate::AnalyticSurface::Sphere { .. }
| crate::AnalyticSurface::Torus { .. })) {
return match (self.degree_u, self.degree_v) {
(1, 1) => self.deriv1_small_fixed::<1, 1>(uu, vv),
(2, 1) => self.deriv1_small_fixed::<2, 1>(uu, vv),
(1, 2) => self.deriv1_small_fixed::<1, 2>(uu, vv),
(2, 2) => self.deriv1_small_fixed::<2, 2>(uu, vv),
_ => unreachable!(),
};
}
let g = self.derivatives_small(uu, vv, 1)?;
return Ok((g[0][0], g[1][0], g[0][1]));
}
let anchor_u = uu - du_out;
let anchor_v = vv - dv_out;
let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
let s = base[0][0]
.add(base[1][0].scale(du_out))
.add(base[0][1].scale(dv_out))
.add(base[1][1].scale(du_out * dv_out));
let su = base[1][0].add(base[1][1].scale(dv_out));
let sv = base[0][1].add(base[1][1].scale(du_out));
Ok((s, su, sv))
}
pub(crate) fn partial_by_differences(
&self,
along_u: bool,
u: f64,
v: f64,
) -> Result<Vec3, String> {
self.ensure_valid()?;
if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
return Err("NurbsSurface: partial by differences exceeds the stack degree".into());
}
let (pu, pv) = (self.degree_u, self.degree_v);
let u = knot_clamp(&self.knots_u, pu, u);
let v = knot_clamp(&self.knots_v, pv, v);
let span_u = knot_find_span(&self.knots_u, pu, u);
let span_v = knot_find_span(&self.knots_v, pv, v);
let mut full_u = [0.0f64; MAX_STACK_ORDER];
let mut full_v = [0.0f64; MAX_STACK_ORDER];
basis_functions_into(&self.knots_u, pu, span_u, u, &mut full_u);
basis_functions_into(&self.knots_v, pv, span_v, v, &mut full_v);
let zero = Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
let control = |i: usize, j: usize| self.control_points[span_u - pu + i][span_v - pv + j];
let mut point = zero;
for i in 0..=pu {
for j in 0..=pv {
point = point.add(control(i, j).scale(full_u[i] * full_v[j]));
}
}
if point.w.abs() <= EPS {
return Err("NurbsSurface: zero evaluated weight".into());
}
let source = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
let (knots, degree, span, parameter) = if along_u {
(&self.knots_u, pu, span_u, u)
} else {
(&self.knots_v, pv, span_v, v)
};
if degree == 0 {
return Ok(Vec3::default());
}
let mut lowered = [0.0f64; MAX_STACK_ORDER];
basis_functions_into(knots, degree - 1, span, parameter, &mut lowered);
let mut hodograph = zero;
for r in 0..degree {
let index = span - degree + r;
let width = knots[index + degree + 1] - knots[index + 1];
if width.abs() <= EPS {
continue;
}
let factor = lowered[r] * degree as f64 / width;
if along_u {
for j in 0..=pv {
let difference = control(r + 1, j).add(control(r, j).scale(-1.0));
hodograph = hodograph.add(difference.scale(factor * full_v[j]));
}
} else {
for i in 0..=pu {
let difference = control(i, r + 1).add(control(i, r).scale(-1.0));
hodograph = hodograph.add(difference.scale(factor * full_u[i]));
}
}
}
Ok(Vec3::new(hodograph.x, hodograph.y, hodograph.z)
.sub(source.scale(hodograph.w))
.scale(1.0 / point.w))
}
pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
if small_derivatives_enabled() {
let derivatives = self.derivatives_small(u, v, 1)?;
return derivatives[1][0].cross(derivatives[0][1]).normalized();
}
let derivatives = self.derivatives(u, v, 1)?;
derivatives[1][0].cross(derivatives[0][1]).normalized()
}
pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String> {
let derivatives: [[Vec3; 3]; 3] = if small_derivatives_enabled() {
self.derivatives_small(u, v, 2)?
} else {
let heap = self.derivatives(u, v, 2)?;
[[heap[0][0], heap[0][1], heap[0][2]], [heap[1][0], heap[1][1], Vec3::default()], [heap[2][0], Vec3::default(), Vec3::default()]]
};
let su = derivatives[1][0];
let sv = derivatives[0][1];
let cross = su.cross(sv);
let cross_length = cross.length();
if cross_length <= 1e-12 {
return Err(format!(
"degenerate parametrization at (u={u:.4}, v={v:.4})"
));
}
let normal = cross.scale(1.0 / cross_length);
let e1 = su.dot(su);
let f1 = su.dot(sv);
let g1 = sv.dot(sv);
let l2 = derivatives[2][0].dot(normal);
let m2 = derivatives[1][1].dot(normal);
let n2 = derivatives[0][2].dot(normal);
let denominator = e1 * g1 - f1 * f1;
let mean_double = (l2 * g1 - 2.0 * m2 * f1 + n2 * e1) / denominator; let gauss = (l2 * n2 - m2 * m2) / denominator; let discriminant = (mean_double * mean_double * 0.25 - gauss).max(0.0).sqrt();
Ok((
mean_double * 0.5 - discriminant,
mean_double * 0.5 + discriminant,
))
}
pub fn is_affine(&self) -> Result<bool, String> {
if self.degree_u != 1
|| self.degree_v != 1
|| self.control_points.len() != 2
|| self.control_points[0].len() != 2
|| self.control_points[1].len() != 2
{
return Ok(false);
}
let weight = self.control_points[0][0].w;
if [
self.control_points[0][1].w,
self.control_points[1][0].w,
self.control_points[1][1].w,
]
.iter()
.any(|other| (*other - weight).abs() > 1e-12 * other.abs().max(weight.abs()))
{
return Ok(false);
}
let p00 = self.control_points[0][0].point()?;
let p01 = self.control_points[0][1].point()?;
let p10 = self.control_points[1][0].point()?;
let p11 = self.control_points[1][1].point()?;
let gap = p11.sub(p10).sub(p01.sub(p00)).length();
let extent = [
p01.sub(p00),
p10.sub(p00),
p11.sub(p00),
p10.sub(p01),
p11.sub(p01),
p11.sub(p10),
]
.iter()
.map(|delta| delta.length())
.fold(0.0, f64::max);
Ok(gap <= 1e-9 * (1.0 + extent))
}
pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
self.ensure_valid()?;
let u = knot_clamp(&self.knots_u, self.degree_u, u);
let span = knot_find_span(&self.knots_u, self.degree_u, u);
let basis = if self.degree_u <= MAX_STACK_DEGREE {
let mut stack = [0.0f64; MAX_STACK_ORDER];
basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
stack[..=self.degree_u].to_vec()
} else {
KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
};
let mut points = Vec::with_capacity(self.control_points[0].len());
for column in 0..self.control_points[0].len() {
let mut point = Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
for (index, value) in basis.iter().enumerate() {
point = point
.add(self.control_points[span - self.degree_u + index][column].scale(*value));
}
points.push(point);
}
NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
}
pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
self.ensure_valid()?;
let v = knot_clamp(&self.knots_v, self.degree_v, v);
let span = knot_find_span(&self.knots_v, self.degree_v, v);
let basis = if self.degree_v <= MAX_STACK_DEGREE {
let mut stack = [0.0f64; MAX_STACK_ORDER];
basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
stack[..=self.degree_v].to_vec()
} else {
KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
};
let mut points = Vec::with_capacity(self.control_points.len());
for row in &self.control_points {
let mut point = Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
for (index, value) in basis.iter().enumerate() {
point = point.add(row[span - self.degree_v + index].scale(*value));
}
points.push(point);
}
NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
}
}
pub fn make_plane(
origin: Vec3,
u_direction: Vec3,
v_direction: Vec3,
u_max: f64,
v_max: f64,
) -> Result<NurbsSurface, String> {
if u_max <= EPS || v_max <= EPS {
return Err("makePlane: parameter extents must be positive".into());
}
let p10 = origin.add(u_direction.scale(u_max));
let p01 = origin.add(v_direction.scale(v_max));
let p11 = p10.add(v_direction.scale(v_max));
NurbsSurface::new(
1,
1,
vec![0.0, 0.0, u_max, u_max],
vec![0.0, 0.0, v_max, v_max],
vec![
vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
],
)
}
pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
let rows = profile
.control_points
.iter()
.map(|point| {
vec![
*point,
Vec4 {
x: point.x + point.w * direction.x,
y: point.y + point.w * direction.y,
z: point.z + point.w * direction.z,
w: point.w,
},
]
})
.collect();
NurbsSurface::new(
profile.degree,
1,
profile.knots.clone(),
vec![0.0, 0.0, 1.0, 1.0],
rows,
)
}
pub fn make_revolution(
axis_point: Vec3,
axis_direction: Vec3,
generatrix: &NurbsCurve,
theta: f64,
) -> Result<NurbsSurface, String> {
if theta <= EPS || theta > std::f64::consts::TAU + EPS {
return Err("makeRevolution: theta must be in (0, 2*PI]".into());
}
let theta = theta.min(std::f64::consts::TAU);
let axis = axis_direction.normalized()?;
let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
let arc_angle = theta / arc_count as f64;
let middle_weight = (arc_angle / 2.0).cos();
let mut knots_u = vec![0.0, 0.0, 0.0];
for index in 1..arc_count {
let knot = index as f64 / arc_count as f64;
knots_u.extend([knot, knot]);
}
knots_u.extend([1.0, 1.0, 1.0]);
let mut rows = vec![
vec![
Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
};
generatrix.control_points.len()
];
2 * arc_count + 1
];
let mut radial_extent: f64 = 0.0;
for control_point in &generatrix.control_points {
let point = control_point.point()?;
let offset = point.sub(axis_point);
radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
}
for (column, control_point) in generatrix.control_points.iter().enumerate() {
let point = control_point.point()?;
let weight = control_point.w;
let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
let radial_vector = point.sub(axis_origin);
let measured_radius = radial_vector.length();
let pole_tolerance = EPS.max(1e-8 * radial_extent);
let radius = if measured_radius <= pole_tolerance {
0.0
} else {
measured_radius
};
let (x_axis, y_axis) = if radius <= EPS {
(Vec3::default(), Vec3::default())
} else {
if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
return Err(format!(
"makeRevolution: generatrix control point {column} sits {radius:e} from the \
axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
radial extent (a fuzzy pole)"
));
}
let x_axis = radial_vector.scale(1.0 / radius);
(x_axis, axis.cross(x_axis))
};
rows[0][column] = Vec4::from_point(point, weight);
let mut start_point = point;
let mut start_tangent = y_axis.scale(radius);
let mut angle = 0.0;
for arc in 1..=arc_count {
angle += arc_angle;
let end_point = if radius <= EPS {
point
} else {
axis_origin
.add(x_axis.scale(radius * angle.cos()))
.add(y_axis.scale(radius * angle.sin()))
};
let end_tangent = if radius <= EPS {
Vec3::default()
} else {
x_axis
.scale(-radius * angle.sin())
.add(y_axis.scale(radius * angle.cos()))
};
let row = 2 * (arc - 1);
if radius <= EPS {
rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
} else {
let cross = start_tangent.cross(end_tangent);
let denominator = cross.length_squared();
let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
if denominator <= EPS * tangent_scale {
return Err("makeRevolution: arc tangents are parallel".into());
}
let distance =
end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
let middle = start_point.add(start_tangent.scale(distance));
rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
}
rows[row + 2][column] = Vec4::from_point(end_point, weight);
start_point = end_point;
start_tangent = end_tangent;
}
}
NurbsSurface::new(
2,
generatrix.degree,
knots_u,
generatrix.knots.clone(),
rows,
)
}
pub fn make_cylinder_surface(
base: Vec3,
axis_direction: Vec3,
radius: f64,
height: f64,
) -> Result<NurbsSurface, String> {
if radius <= EPS || height <= EPS {
return Err("makeCylinder: radius and height must be positive".into());
}
let axis = axis_direction.normalized()?;
let x_axis = axis.perpendicular()?;
let start = base.add(x_axis.scale(radius));
let end = start.add(axis.scale(height));
let generatrix = crate::make_line(start, end)?;
make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
}
pub fn make_cone_surface(
base: Vec3,
axis_direction: Vec3,
radius_bottom: f64,
radius_top: f64,
height: f64,
) -> Result<NurbsSurface, String> {
if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
return Err("makeCone: invalid radius or height".into());
}
let axis = axis_direction.normalized()?;
let x_axis = axis.perpendicular()?;
let start = base.add(x_axis.scale(radius_bottom));
let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
let generatrix = crate::make_line(start, end)?;
make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
}
pub fn make_sphere_surface(
center: Vec3,
radius: f64,
polar_axis: Vec3,
) -> Result<NurbsSurface, String> {
make_sphere_surface_framed(center, radius, polar_axis, None)
}
pub fn make_sphere_surface_framed(
center: Vec3,
radius: f64,
polar_axis: Vec3,
seam_direction: Option<Vec3>,
) -> Result<NurbsSurface, String> {
if radius <= EPS {
return Err("makeSphere: radius must be positive".into());
}
let axis = polar_axis.normalized()?;
let x_axis = match seam_direction {
Some(seam) => {
let planar = seam.sub(axis.scale(seam.dot(axis)));
match planar.normalized() {
Ok(unit) if planar.length() > EPS => unit,
_ => axis.perpendicular()?,
}
}
None => axis.perpendicular()?,
};
let meridian = crate::make_arc(
center,
x_axis,
axis,
radius,
-std::f64::consts::FRAC_PI_2,
std::f64::consts::FRAC_PI_2,
)?;
make_revolution(center, axis, &meridian, std::f64::consts::TAU)
}
pub fn make_torus_surface(
center: Vec3,
axis_direction: Vec3,
major_radius: f64,
minor_radius: f64,
) -> Result<NurbsSurface, String> {
if minor_radius <= EPS || major_radius <= minor_radius {
return Err("makeTorus: requires positive minorRadius < majorRadius".into());
}
let axis = axis_direction.normalized()?;
let x_axis = axis.perpendicular()?;
let tube_center = center.add(x_axis.scale(major_radius));
let tube = crate::make_arc(
tube_center,
x_axis,
axis,
minor_radius,
0.0,
std::f64::consts::TAU,
)?;
make_revolution(center, axis, &tube, std::f64::consts::TAU)
}
fn binomial(n: usize, k: usize) -> f64 {
if k > n {
return 0.0;
}
let k = k.min(n - k);
(1..=k).fold(1.0, |value, index| {
value * (n - k + index) as f64 / index as f64
})
}
pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
let inflate = if inflate.is_finite() {
inflate.clamp(1.0, 16.0)
} else {
2.0
};
let (closed_u, closed_v) = surface.closed_directions()?;
let [u0, u1] = surface.domain_u()?;
let [v0, v1] = surface.domain_v()?;
let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
if closed || inflate <= 1.0 {
(d0, d1)
} else {
let centre = 0.5 * (d0 + d1);
let half = 0.5 * (d1 - d0) * inflate;
(centre - half, centre + half)
}
};
let (nu0, nu1) = stretch(u0, u1, closed_u);
let (nv0, nv1) = stretch(v0, v1, closed_v);
if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
return Ok(surface.clone());
}
if surface.is_affine()? {
let corners = [
surface.evaluate_extended(nu0, nv0)?,
surface.evaluate_extended(nu1, nv0)?,
surface.evaluate_extended(nu0, nv1)?,
surface.evaluate_extended(nu1, nv1)?,
];
return NurbsSurface::new(
1,
1,
vec![nu0, nu0, nu1, nu1],
vec![nv0, nv0, nv1, nv1],
vec![
vec![
Vec4::from_point(corners[0], 1.0),
Vec4::from_point(corners[2], 1.0),
],
vec![
Vec4::from_point(corners[1], 1.0),
Vec4::from_point(corners[3], 1.0),
],
],
);
}
const SAMPLES: usize = 32;
let u_params: Vec<f64> = (0..=SAMPLES)
.map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
.collect();
let v_params: Vec<f64> = (0..=SAMPLES)
.map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
.collect();
let mut column_curves = Vec::with_capacity(u_params.len());
for &u in &u_params {
let column: Vec<Vec3> = v_params
.iter()
.map(|&v| surface.evaluate_extended(u, v))
.collect::<Result<_, _>>()?;
column_curves.push(crate::interpolate_curve(
&column,
3,
&normalized(&v_params),
)?);
}
let v_knots = column_curves[0].knots.clone();
let control_rows = column_curves[0].control_points.len();
let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
let mut u_knots = Vec::new();
for row_index in 0..control_rows {
let row: Vec<Vec3> = column_curves
.iter()
.map(|curve| curve.control_points[row_index].point())
.collect::<Result<_, _>>()?;
let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
u_knots = fitted.knots.clone();
for (u_index, control) in fitted.control_points.iter().enumerate() {
if grid.len() <= u_index {
grid.push(Vec::new());
}
grid[u_index].push(*control);
}
}
let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
};
NurbsSurface::new(
3,
3,
rescale(&u_knots, nu0, nu1),
rescale(&v_knots, nv0, nv1),
grid,
)
}
fn normalized(parameters: &[f64]) -> Vec<f64> {
let first = parameters[0];
let last = parameters[parameters.len() - 1];
parameters
.iter()
.map(|p| (p - first) / (last - first))
.collect()
}
fn analytic_deriv1_on() -> bool {
switch!("BREP_ANALYTIC_DERIV1", true)
}
fn analytic_point_on() -> bool {
switch!("BREP_ANALYTIC_POINT", false)
}
pub(crate) fn small_derivatives_enabled() -> bool {
switch!("BREP_SURFACE_SMALL_DERIVATIVES", true)
}