pub mod bezier;
pub mod bounds;
pub(super) mod admitted;
use crate::features::FinitePoint3;
use crate::math::Point3;
use crate::scalar::{FiniteReal, NonZeroReal};
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub struct KnotVector(Vec<f64>);
impl KnotVector {
pub fn new(knots: Vec<f64>) -> Result<Self, NurbsError> {
build_raw_knots(knots, "")
}
pub fn from_finite_lanes(knots: Vec<FiniteReal>) -> Result<Self, NurbsError> {
build_finite_knots(knots, "")
}
pub fn into_values(self) -> Vec<f64> {
self.0
}
#[must_use]
pub fn as_slice(&self) -> &[f64] {
&self.0
}
pub(super) fn reverse_negated(&mut self) {
self.0.reverse();
for knot in &mut self.0 {
*knot = -*knot;
}
}
}
impl std::ops::Deref for KnotVector {
type Target = [f64];
fn deref(&self) -> &[f64] {
&self.0
}
}
impl<'a> IntoIterator for &'a KnotVector {
type Item = &'a f64;
type IntoIter = std::slice::Iter<'a, f64>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
mod knot_value_sealed {
pub trait Sealed {}
impl Sealed for Vec<f64> {}
impl Sealed for Vec<crate::scalar::FiniteReal> {}
impl Sealed for super::KnotVector {}
}
pub trait KnotValue: knot_value_sealed::Sealed {
fn knot_count(&self) -> usize;
fn admit<E>(
self,
raw: impl FnOnce(Vec<f64>) -> Result<KnotVector, E>,
finite: impl FnOnce(Vec<FiniteReal>) -> Result<KnotVector, E>,
) -> Result<KnotVector, E>;
}
impl KnotValue for Vec<f64> {
fn knot_count(&self) -> usize {
Vec::len(self)
}
fn admit<E>(
self,
raw: impl FnOnce(Vec<f64>) -> Result<KnotVector, E>,
_finite: impl FnOnce(Vec<FiniteReal>) -> Result<KnotVector, E>,
) -> Result<KnotVector, E> {
raw(self)
}
}
impl KnotValue for Vec<FiniteReal> {
fn knot_count(&self) -> usize {
Vec::len(self)
}
fn admit<E>(
self,
_raw: impl FnOnce(Vec<f64>) -> Result<KnotVector, E>,
finite: impl FnOnce(Vec<FiniteReal>) -> Result<KnotVector, E>,
) -> Result<KnotVector, E> {
finite(self)
}
}
impl KnotValue for KnotVector {
fn knot_count(&self) -> usize {
self.0.len()
}
fn admit<E>(
self,
_raw: impl FnOnce(Vec<f64>) -> Result<KnotVector, E>,
_finite: impl FnOnce(Vec<FiniteReal>) -> Result<KnotVector, E>,
) -> Result<KnotVector, E> {
Ok(self)
}
}
fn build_raw_knots(knots: Vec<f64>, prefix: &str) -> Result<KnotVector, NurbsError> {
require_nondecreasing_knots(&knots, prefix)?;
Ok(KnotVector(knots))
}
fn build_finite_knots(knots: Vec<FiniteReal>, prefix: &str) -> Result<KnotVector, NurbsError> {
let values: Vec<f64> = knots.into_iter().map(FiniteReal::get).collect();
require_knot_order(&values, prefix)?;
Ok(KnotVector(values))
}
pub(super) fn admit_knots<K: KnotValue>(knots: K, prefix: &str) -> Result<KnotVector, NurbsError> {
knots.admit(
|values| build_raw_knots(values, prefix),
|values| build_finite_knots(values, prefix),
)
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct WeightedPole3<P = Point3> {
pub point: P,
pub weight: NonZeroReal,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "form", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum NurbsPoles3<P = Point3> {
Polynomial {
points: Vec<P>,
},
Rational {
points: Vec<WeightedPole3<P>>,
},
}
pub trait PoleValue<T>: Copy {
fn admit(self) -> Option<T>;
fn admit_curve_poles<E>(
poles: NurbsPoles3<Self>,
convert: impl FnOnce(NurbsPoles3<Self>) -> Result<NurbsPoles3<T>, E>,
) -> Result<NurbsPoles3<T>, E> {
convert(poles)
}
fn admit_pcurve_poles<E>(
poles: super::pcurve::PcurveNurbsPoles<Self>,
convert: impl FnOnce(
super::pcurve::PcurveNurbsPoles<Self>,
) -> Result<super::pcurve::PcurveNurbsPoles<T>, E>,
) -> Result<super::pcurve::PcurveNurbsPoles<T>, E> {
convert(poles)
}
fn admit_surface_poles<E>(
grid: NurbsPoleGrid<Self>,
convert: impl FnOnce(NurbsPoleGrid<Self>) -> Result<NurbsPoleGrid<T>, E>,
) -> Result<NurbsPoleGrid<T>, E> {
convert(grid)
}
}
impl PoleValue<FinitePoint3> for Point3 {
fn admit(self) -> Option<FinitePoint3> {
FinitePoint3::new(self)
}
}
impl PoleValue<FinitePoint3> for FinitePoint3 {
fn admit(self) -> Option<FinitePoint3> {
Some(self)
}
fn admit_curve_poles<E>(
poles: NurbsPoles3<Self>,
_convert: impl FnOnce(NurbsPoles3<Self>) -> Result<NurbsPoles3<FinitePoint3>, E>,
) -> Result<NurbsPoles3<FinitePoint3>, E> {
Ok(poles)
}
fn admit_surface_poles<E>(
grid: NurbsPoleGrid<Self>,
_convert: impl FnOnce(NurbsPoleGrid<Self>) -> Result<NurbsPoleGrid<FinitePoint3>, E>,
) -> Result<NurbsPoleGrid<FinitePoint3>, E> {
Ok(grid)
}
}
fn map_pole<P: PoleValue<T>, T>(point: P) -> Result<T, NurbsError> {
match point.admit() {
Some(point) => Ok(point),
None => Err(NurbsError::Structure(
"control_points contains a non-finite point".to_owned(),
)),
}
}
fn map_curve_poles<P: PoleValue<T>, T>(
poles: NurbsPoles3<P>,
) -> Result<NurbsPoles3<T>, NurbsError> {
Ok(match poles {
NurbsPoles3::Polynomial { points } => NurbsPoles3::Polynomial {
points: points
.into_iter()
.map(map_pole)
.collect::<Result<Vec<_>, NurbsError>>()?,
},
NurbsPoles3::Rational { points } => NurbsPoles3::Rational {
points: points
.into_iter()
.map(|pole| {
Ok(WeightedPole3 {
point: map_pole(pole.point)?,
weight: pole.weight,
})
})
.collect::<Result<Vec<_>, NurbsError>>()?,
},
})
}
fn map_surface_poles<P: PoleValue<T>, T>(
grid: NurbsPoleGrid<P>,
) -> Result<NurbsPoleGrid<T>, NurbsError> {
Ok(match grid {
NurbsPoleGrid::Polynomial { rows } => NurbsPoleGrid::Polynomial {
rows: rows
.into_iter()
.map(|row| {
row.into_iter()
.map(map_pole)
.collect::<Result<Vec<_>, NurbsError>>()
})
.collect::<Result<Vec<_>, NurbsError>>()?,
},
NurbsPoleGrid::Rational { rows } => NurbsPoleGrid::Rational {
rows: rows
.into_iter()
.map(|row| {
row.into_iter()
.map(|pole| {
Ok(WeightedPole3 {
point: map_pole(pole.point)?,
weight: pole.weight,
})
})
.collect::<Result<Vec<_>, NurbsError>>()
})
.collect::<Result<Vec<_>, NurbsError>>()?,
},
})
}
fn weighted_poles<P, W, E>(
points: Vec<P>,
weights: Vec<W>,
mut weight: impl FnMut(usize, W) -> Result<NonZeroReal, E>,
) -> Result<Vec<WeightedPole3<P>>, E> {
points
.into_iter()
.zip(weights)
.enumerate()
.map(|(index, (point, value))| {
Ok(WeightedPole3 {
point,
weight: weight(index, value)?,
})
})
.collect()
}
impl NurbsPoles3<FinitePoint3> {
#[must_use]
pub fn to_raw(&self) -> NurbsPoles3 {
let Ok(raw) = self
.clone()
.try_map_points(|point| Ok::<_, std::convert::Infallible>(point.get()));
raw
}
#[must_use]
pub fn raw_points(&self) -> Vec<Point3> {
self.points().into_iter().map(FinitePoint3::get).collect()
}
}
impl<P> NurbsPoles3<P> {
pub fn from_lanes(points: Vec<P>, weights: Option<Vec<f64>>) -> Result<Self, NurbsError> {
pair_curve_lanes(points, weights, |index, weight| {
admit_weight("poles", index, weight)
})
}
pub fn from_finite_lanes(
points: Vec<P>,
weights: Option<Vec<FiniteReal>>,
) -> Result<Self, NurbsError> {
pair_curve_lanes(points, weights, |index, weight| {
admit_finite_weight("poles", index, weight)
})
}
pub fn from_checked_lanes(
points: Vec<P>,
weights: Option<Vec<NonZeroReal>>,
) -> Result<Self, NurbsError> {
pair_curve_lanes(points, weights, |_, weight| Ok(weight))
}
#[must_use]
pub fn count(&self) -> usize {
match self {
Self::Polynomial { points } => points.len(),
Self::Rational { points } => points.len(),
}
}
#[must_use]
pub fn weights(&self) -> Option<Vec<f64>> {
match self {
Self::Polynomial { .. } => None,
Self::Rational { points } => {
Some(points.iter().map(|pole| pole.weight.get()).collect())
}
}
}
pub fn reverse(&mut self) {
match self {
Self::Polynomial { points } => points.reverse(),
Self::Rational { points } => points.reverse(),
}
}
fn try_map_points<Q, E>(
self,
mut point: impl FnMut(P) -> Result<Q, E>,
) -> Result<NurbsPoles3<Q>, E> {
Ok(match self {
Self::Polynomial { points } => NurbsPoles3::Polynomial {
points: points.into_iter().map(point).collect::<Result<_, E>>()?,
},
Self::Rational { points } => NurbsPoles3::Rational {
points: points
.into_iter()
.map(|pole| {
Ok(WeightedPole3 {
point: point(pole.point)?,
weight: pole.weight,
})
})
.collect::<Result<_, E>>()?,
},
})
}
}
impl<P: Copy> NurbsPoles3<P> {
#[must_use]
pub fn point_at(&self, index: usize) -> Option<P> {
match self {
Self::Polynomial { points } => points.get(index).copied(),
Self::Rational { points } => points.get(index).map(|pole| pole.point),
}
}
#[must_use]
pub fn weight_at(&self, index: usize) -> Option<f64> {
match self {
Self::Polynomial { .. } => None,
Self::Rational { points } => points.get(index).map(|pole| pole.weight.get()),
}
}
#[must_use]
pub fn points(&self) -> Vec<P> {
match self {
Self::Polynomial { points } => points.clone(),
Self::Rational { points } => points.iter().map(|pole| pole.point).collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "form", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum NurbsPoleGrid<P = Point3> {
Polynomial {
rows: Vec<Vec<P>>,
},
Rational {
rows: Vec<Vec<WeightedPole3<P>>>,
},
}
pub(crate) fn pair_curve_lanes<P, W>(
points: Vec<P>,
weights: Option<Vec<W>>,
mut weight: impl FnMut(usize, W) -> Result<NonZeroReal, NurbsError>,
) -> Result<NurbsPoles3<P>, NurbsError> {
let Some(weights) = weights else {
return Ok(NurbsPoles3::Polynomial { points });
};
require_weight_lane("poles", points.len(), weights.len())?;
Ok(NurbsPoles3::Rational {
points: weighted_poles(points, weights, &mut weight)?,
})
}
fn pair_grid_lanes<P, W>(
rows: Vec<Vec<P>>,
weights: Option<Vec<Vec<W>>>,
mut weight: impl FnMut(usize, W) -> Result<NonZeroReal, NurbsError>,
) -> Result<NurbsPoleGrid<P>, NurbsError> {
let Some(weights) = weights else {
return Ok(NurbsPoleGrid::Polynomial { rows });
};
require_weight_lane("pole grid", rows.len(), weights.len())?;
let mut output = Vec::new();
for (row, weights) in rows.into_iter().zip(weights) {
require_weight_lane("pole grid row", row.len(), weights.len())?;
output.push(weighted_poles(row, weights, &mut weight)?);
}
Ok(NurbsPoleGrid::Rational { rows: output })
}
impl NurbsPoleGrid<FinitePoint3> {
#[must_use]
pub fn to_raw(&self) -> NurbsPoleGrid {
let Ok(raw) = self
.clone()
.try_map_points(|point| Ok::<_, std::convert::Infallible>(point.get()));
raw
}
#[must_use]
pub fn raw_points(&self) -> Vec<Vec<Point3>> {
self.points()
.into_iter()
.map(|row| row.into_iter().map(FinitePoint3::get).collect())
.collect()
}
}
impl<P> NurbsPoleGrid<P> {
pub fn from_lanes(
rows: Vec<Vec<P>>,
weights: Option<Vec<Vec<f64>>>,
) -> Result<Self, NurbsError> {
pair_grid_lanes(rows, weights, |index, weight| {
admit_weight("pole grid row", index, weight)
})
}
pub fn from_finite_lanes(
rows: Vec<Vec<P>>,
weights: Option<Vec<Vec<FiniteReal>>>,
) -> Result<Self, NurbsError> {
pair_grid_lanes(rows, weights, |index, weight| {
admit_finite_weight("pole grid row", index, weight)
})
}
pub fn from_checked_lanes(
rows: Vec<Vec<P>>,
weights: Option<Vec<Vec<NonZeroReal>>>,
) -> Result<Self, NurbsError> {
pair_grid_lanes(rows, weights, |_, weight| Ok(weight))
}
#[must_use]
pub fn u_count(&self) -> usize {
match self {
Self::Polynomial { rows } => rows.len(),
Self::Rational { rows } => rows.len(),
}
}
#[must_use]
pub fn v_count(&self) -> usize {
match self {
Self::Polynomial { rows } => rows.first().map_or(0, Vec::len),
Self::Rational { rows } => rows.first().map_or(0, Vec::len),
}
}
#[must_use]
pub fn weights(&self) -> Option<Vec<Vec<f64>>> {
match self {
Self::Polynomial { .. } => None,
Self::Rational { rows } => Some(
rows.iter()
.map(|row| row.iter().map(|pole| pole.weight.get()).collect())
.collect(),
),
}
}
fn try_map_points<Q, E>(
self,
mut point: impl FnMut(P) -> Result<Q, E>,
) -> Result<NurbsPoleGrid<Q>, E> {
Ok(match self {
Self::Polynomial { rows } => NurbsPoleGrid::Polynomial {
rows: rows
.into_iter()
.map(|row| row.into_iter().map(&mut point).collect())
.collect::<Result<_, E>>()?,
},
Self::Rational { rows } => NurbsPoleGrid::Rational {
rows: rows
.into_iter()
.map(|row| {
row.into_iter()
.map(|pole| {
Ok(WeightedPole3 {
point: point(pole.point)?,
weight: pole.weight,
})
})
.collect()
})
.collect::<Result<_, E>>()?,
},
})
}
}
impl<P: Copy> NurbsPoleGrid<P> {
#[must_use]
pub fn points(&self) -> Vec<Vec<P>> {
match self {
Self::Polynomial { rows } => rows.clone(),
Self::Rational { rows } => rows
.iter()
.map(|row| row.iter().map(|pole| pole.point).collect())
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct NurbsSurface {
u_degree: u32,
v_degree: u32,
#[cfg_attr(feature = "schema", schemars(with = "Vec<f64>"))]
u_knots: KnotVector,
#[cfg_attr(feature = "schema", schemars(with = "Vec<f64>"))]
v_knots: KnotVector,
#[cfg_attr(feature = "schema", schemars(with = "NurbsPoleGrid"))]
poles: NurbsPoleGrid<FinitePoint3>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
normal_reversed: bool,
u_periodic: bool,
v_periodic: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct BsplineSurface {
u_degree: u32,
v_degree: u32,
#[cfg_attr(feature = "schema", schemars(with = "Vec<f64>"))]
u_knots: KnotVector,
#[cfg_attr(feature = "schema", schemars(with = "Vec<f64>"))]
v_knots: KnotVector,
#[cfg_attr(feature = "schema", schemars(with = "Vec<Vec<Point3>>"))]
control_points: Vec<Vec<FinitePoint3>>,
}
impl BsplineSurface {
pub fn new(
u_degree: u32,
v_degree: u32,
u_knots: Vec<f64>,
v_knots: Vec<f64>,
control_points: Vec<Vec<Point3>>,
) -> Result<Self, NurbsError> {
let u_count = control_points.len();
let v_count = control_points.first().map_or(0, Vec::len);
let u_knots = bspline_axis_knots("u", "u_knots", u_degree, u_count, u_knots)?;
let v_knots = bspline_axis_knots("v", "v_knots", v_degree, v_count, v_knots)?;
require_rectangular_grid("control_points", &control_points)?;
let control_points = control_points
.into_iter()
.map(|row| {
row.into_iter()
.map(map_pole)
.collect::<Result<Vec<_>, NurbsError>>()
})
.collect::<Result<Vec<_>, NurbsError>>()?;
Ok(BsplineSurface {
u_degree,
v_degree,
u_knots,
v_knots,
control_points,
})
}
pub const fn u_degree(&self) -> u32 {
self.u_degree
}
pub const fn v_degree(&self) -> u32 {
self.v_degree
}
pub fn try_map_control_points<E>(
&mut self,
map: impl Fn(usize, FinitePoint3) -> Result<FinitePoint3, E>,
) -> Result<(), E> {
for (index, point) in self.control_points.iter().flatten().copied().enumerate() {
map(index, point)?;
}
for (index, point) in self.control_points.iter_mut().flatten().enumerate() {
*point = map(index, *point)?;
}
Ok(())
}
}
fn bspline_axis_knots(
axis: &str,
knot_field: &str,
degree: u32,
count: usize,
knots: Vec<f64>,
) -> Result<KnotVector, NurbsError> {
if count <= cadmpeg_core::decode::index_from_u32(degree) {
return Err(NurbsError::Structure(format!(
"control_points {axis} count must exceed degree {degree}, found {count}"
)));
}
require_length(
knot_field,
knots.len(),
checked_knot_count(axis, count, degree)?,
)?;
build_raw_knots(knots, "")
}
impl<'de> Deserialize<'de> for BsplineSurface {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Wire {
u_degree: u32,
v_degree: u32,
u_knots: Vec<f64>,
v_knots: Vec<f64>,
control_points: Vec<Vec<Point3>>,
}
let wire = Wire::deserialize(deserializer)?;
BsplineSurface::new(
wire.u_degree,
wire.v_degree,
wire.u_knots,
wire.v_knots,
wire.control_points,
)
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum NurbsError {
#[error("{field}: {poles} pole(s) against {weights} weight(s)")]
WeightLaneLength {
field: String,
poles: usize,
weights: usize,
},
#[error("{field}: weight {weight} at index {index} is not a usable weight")]
UnusableWeight {
field: String,
index: usize,
weight: f64,
},
#[error("{0}")]
Structure(String),
#[error("{0}")]
EditRefused(String),
}
impl From<NurbsError> for cadmpeg_core::CodecError {
fn from(error: NurbsError) -> Self {
Self::Malformed(error.to_string())
}
}
fn checked_knot_count(field: &str, pole_count: usize, degree: u32) -> Result<usize, NurbsError> {
let Ok(degree) = usize::try_from(degree) else {
return Err(NurbsError::Structure(format!(
"{field} knot count overflows usize"
)));
};
match pole_count
.checked_add(degree)
.and_then(|count| count.checked_add(1))
{
Some(count) => Ok(count),
None => Err(NurbsError::Structure(format!(
"{field} knot count overflows usize"
))),
}
}
fn require_rectangular_grid<T>(field: &str, rows: &[Vec<T>]) -> Result<(), NurbsError> {
let width = rows.first().map_or(0, Vec::len);
for row in rows {
if row.len() != width {
return Err(NurbsError::Structure(format!(
"{field} row must contain {width} values, found {}",
row.len(),
)));
}
}
Ok(())
}
fn require_length(field: &str, actual: usize, expected: usize) -> Result<(), NurbsError> {
if actual == expected {
Ok(())
} else {
Err(NurbsError::Structure(format!(
"{field} must contain {expected} values, found {actual}"
)))
}
}
pub(super) fn require_weight_lane(
field: &str,
poles: usize,
weights: usize,
) -> Result<(), NurbsError> {
if poles == weights {
Ok(())
} else {
Err(NurbsError::WeightLaneLength {
field: field.to_owned(),
poles,
weights,
})
}
}
pub(crate) fn admit_weight(
field: &str,
index: usize,
weight: f64,
) -> Result<NonZeroReal, NurbsError> {
if let Some(admitted) = NonZeroReal::new(weight) {
return Ok(admitted);
}
Err(NurbsError::UnusableWeight {
field: field.to_owned(),
index,
weight,
})
}
pub(super) fn admit_finite_weight(
field: &str,
index: usize,
weight: FiniteReal,
) -> Result<NonZeroReal, NurbsError> {
if let Some(admitted) = NonZeroReal::from_finite(weight) {
return Ok(admitted);
}
Err(NurbsError::UnusableWeight {
field: field.to_owned(),
index,
weight: weight.get(),
})
}
fn require_finite_scalars(prefix: &str, values: &[f64]) -> Result<(), NurbsError> {
for value in values {
if !value.is_finite() {
return Err(NurbsError::Structure(format!(
"{prefix}knots contains a non-finite value"
)));
}
}
Ok(())
}
fn require_knot_order(knots: &[f64], prefix: &str) -> Result<(), NurbsError> {
if knots_ordered(knots, true) {
Ok(())
} else {
Err(NurbsError::Structure(format!(
"{prefix}knots must be non-decreasing"
)))
}
}
fn require_nondecreasing_knots(knots: &[f64], prefix: &str) -> Result<(), NurbsError> {
require_finite_scalars(prefix, knots)?;
require_knot_order(knots, prefix)
}
pub(super) fn require_curve_cardinality(
degree: u32,
knot_count: usize,
pole_count: usize,
point_field: &str,
) -> Result<(), NurbsError> {
if u64::try_from(pole_count).is_ok_and(|count| count <= u64::from(degree)) {
return Err(NurbsError::Structure(format!(
"{point_field} must contain more than degree {degree} poles, found {pole_count}"
)));
}
require_length(
"knots",
knot_count,
checked_knot_count("curve", pole_count, degree)?,
)
}
#[derive(Debug, Clone, PartialEq)]
pub struct NurbsSurfaceAxis<K = Vec<f64>> {
degree: u32,
knots: K,
periodic: bool,
}
impl<K> NurbsSurfaceAxis<K> {
#[must_use]
pub const fn new(degree: u32, knots: K, periodic: bool) -> Self {
Self {
degree,
knots,
periodic,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NurbsSurfaceLanes<P = Point3, W = f64> {
control_points: Vec<Vec<P>>,
weights: Option<Vec<Vec<W>>>,
}
impl<P, W> NurbsSurfaceLanes<P, W> {
#[must_use]
pub const fn new(control_points: Vec<Vec<P>>, weights: Option<Vec<Vec<W>>>) -> Self {
Self {
control_points,
weights,
}
}
}
fn require_surface_shape<P>(
u_degree: u32,
u_knots: usize,
v_degree: u32,
v_knots: usize,
poles: &NurbsPoleGrid<P>,
) -> Result<(), NurbsError> {
let u_count = poles.u_count();
let v_count = poles.v_count();
if u64::try_from(u_count).is_ok_and(|count| count <= u64::from(u_degree)) {
return Err(NurbsError::Structure(format!(
"u_count must exceed u_degree {u_degree}, found {u_count}"
)));
}
if u64::try_from(v_count).is_ok_and(|count| count <= u64::from(v_degree)) {
return Err(NurbsError::Structure(format!(
"v_count must exceed v_degree {v_degree}, found {v_count}"
)));
}
require_length(
"u_knots",
u_knots,
checked_knot_count("u", u_count, u_degree)?,
)?;
require_length(
"v_knots",
v_knots,
checked_knot_count("v", v_count, v_degree)?,
)?;
match poles {
NurbsPoleGrid::Polynomial { rows } => {
require_rectangular_grid("control_points", rows)?;
}
NurbsPoleGrid::Rational { rows } => {
require_rectangular_grid("control_points", rows)?;
}
}
Ok(())
}
impl NurbsSurface {
pub fn new<P: PoleValue<FinitePoint3>, U: KnotValue, V: KnotValue>(
u: NurbsSurfaceAxis<U>,
v: NurbsSurfaceAxis<V>,
poles: NurbsPoleGrid<P>,
normal_reversed: bool,
) -> Result<Self, NurbsError> {
let NurbsSurfaceAxis {
degree: u_degree,
knots: u_knots,
periodic: u_periodic,
} = u;
let NurbsSurfaceAxis {
degree: v_degree,
knots: v_knots,
periodic: v_periodic,
} = v;
require_surface_shape(
u_degree,
u_knots.knot_count(),
v_degree,
v_knots.knot_count(),
&poles,
)?;
let poles = P::admit_surface_poles(poles, |poles| map_surface_poles(poles))?;
let u_knots = admit_knots(u_knots, "u_")?;
let v_knots = admit_knots(v_knots, "v_")?;
Ok(NurbsSurface {
u_degree,
v_degree,
u_knots,
v_knots,
poles,
normal_reversed,
u_periodic,
v_periodic,
})
}
pub const fn u_degree(&self) -> u32 {
self.u_degree
}
pub const fn v_degree(&self) -> u32 {
self.v_degree
}
pub fn u_knots(&self) -> &KnotVector {
&self.u_knots
}
pub fn v_knots(&self) -> &KnotVector {
&self.v_knots
}
pub fn u_count(&self) -> usize {
self.poles.u_count()
}
pub fn v_count(&self) -> usize {
self.poles.v_count()
}
pub fn from_finite_lanes(
u: NurbsSurfaceAxis<Vec<FiniteReal>>,
v: NurbsSurfaceAxis<Vec<FiniteReal>>,
lanes: NurbsSurfaceLanes<FinitePoint3, FiniteReal>,
normal_reversed: bool,
) -> Result<Self, NurbsError> {
let NurbsSurfaceLanes {
control_points,
weights,
} = lanes;
let poles = pair_grid_lanes(control_points, weights, |index, weight| {
admit_finite_weight("pole grid row", index, weight)
})?;
NurbsSurface::new(u, v, poles, normal_reversed)
}
pub fn from_checked_lanes<P: PoleValue<FinitePoint3>, U: KnotValue, V: KnotValue>(
u: NurbsSurfaceAxis<U>,
v: NurbsSurfaceAxis<V>,
lanes: NurbsSurfaceLanes<P, NonZeroReal>,
normal_reversed: bool,
) -> Result<Self, NurbsError> {
let NurbsSurfaceLanes {
control_points,
weights,
} = lanes;
let poles = pair_grid_lanes(control_points, weights, |_, weight| Ok(weight))?;
NurbsSurface::new(u, v, poles, normal_reversed)
}
pub const fn pole_grid(&self) -> &NurbsPoleGrid<FinitePoint3> {
&self.poles
}
#[must_use]
pub fn control_grid(&self) -> Vec<Vec<FinitePoint3>> {
self.poles.points()
}
#[must_use]
pub fn poles(&self) -> Vec<FinitePoint3> {
self.control_grid().into_iter().flatten().collect()
}
#[must_use]
pub fn pole(&self, u: usize, v: usize) -> Option<FinitePoint3> {
match &self.poles {
NurbsPoleGrid::Polynomial { rows } => rows.get(u)?.get(v).copied(),
NurbsPoleGrid::Rational { rows } => rows.get(u)?.get(v).map(|pole| pole.point),
}
}
pub fn weight(&self, u: usize, v: usize) -> Option<NonZeroReal> {
match &self.poles {
NurbsPoleGrid::Polynomial { .. } => None,
NurbsPoleGrid::Rational { rows } => rows.get(u)?.get(v).map(|pole| pole.weight),
}
}
pub fn try_map_control_points<E>(
&mut self,
map: impl Fn(usize, FinitePoint3) -> Result<FinitePoint3, E>,
) -> Result<(), E> {
match &self.poles {
NurbsPoleGrid::Polynomial { rows } => {
for (index, point) in rows.iter().flatten().copied().enumerate() {
map(index, point)?;
}
}
NurbsPoleGrid::Rational { rows } => {
for (index, pole) in rows.iter().flatten().enumerate() {
map(index, pole.point)?;
}
}
}
match &mut self.poles {
NurbsPoleGrid::Polynomial { rows } => {
for (index, point) in rows.iter_mut().flatten().enumerate() {
*point = map(index, *point)?;
}
}
NurbsPoleGrid::Rational { rows } => {
for (index, pole) in rows.iter_mut().flatten().enumerate() {
pole.point = map(index, pole.point)?;
}
}
}
Ok(())
}
pub fn weights(&self) -> Option<Vec<Vec<NonZeroReal>>> {
match &self.poles {
NurbsPoleGrid::Polynomial { .. } => None,
NurbsPoleGrid::Rational { rows } => Some(
rows.iter()
.map(|row| row.iter().map(|pole| pole.weight).collect())
.collect(),
),
}
}
pub fn pole_weights(&self) -> Option<Vec<NonZeroReal>> {
Some(self.weights()?.into_iter().flatten().collect())
}
pub const fn normal_reversed(&self) -> bool {
self.normal_reversed
}
pub const fn u_periodic(&self) -> bool {
self.u_periodic
}
pub const fn v_periodic(&self) -> bool {
self.v_periodic
}
pub fn transpose_parameter_axes(&mut self) {
fn transpose<T: Copy>(rows: &[Vec<T>], width: usize) -> Vec<Vec<T>> {
(0..width)
.map(|column| rows.iter().map(|row| row[column]).collect())
.collect()
}
let width = self.v_count();
match &mut self.poles {
NurbsPoleGrid::Polynomial { rows } => *rows = transpose(rows, width),
NurbsPoleGrid::Rational { rows } => *rows = transpose(rows, width),
}
std::mem::swap(&mut self.u_degree, &mut self.v_degree);
std::mem::swap(&mut self.u_knots, &mut self.v_knots);
std::mem::swap(&mut self.u_periodic, &mut self.v_periodic);
}
}
impl<'de> Deserialize<'de> for NurbsSurface {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Wire {
u_degree: u32,
v_degree: u32,
u_knots: Vec<f64>,
v_knots: Vec<f64>,
poles: NurbsPoleGrid,
#[serde(default)]
normal_reversed: bool,
u_periodic: bool,
v_periodic: bool,
}
let wire = Wire::deserialize(deserializer)?;
NurbsSurface::new(
NurbsSurfaceAxis::new(wire.u_degree, wire.u_knots, wire.u_periodic),
NurbsSurfaceAxis::new(wire.v_degree, wire.v_knots, wire.v_periodic),
wire.poles,
wire.normal_reversed,
)
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum SurfaceParameterAxis {
U,
V,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct NurbsCurve {
degree: u32,
#[cfg_attr(feature = "schema", schemars(with = "Vec<f64>"))]
knots: KnotVector,
#[cfg_attr(feature = "schema", schemars(with = "NurbsPoles3"))]
poles: NurbsPoles3<FinitePoint3>,
periodic: bool,
}
impl NurbsCurve {
pub fn map_control_points(&self, mut map: impl FnMut(Point3) -> Point3) -> Option<Self> {
let mut mapped = self.clone();
match &mut mapped.poles {
NurbsPoles3::Polynomial { points } => {
for point in points {
let next = FinitePoint3::new(map(point.get()))?;
*point = next;
}
}
NurbsPoles3::Rational { points } => {
for pole in points {
let next = FinitePoint3::new(map(pole.point.get()))?;
pole.point = next;
}
}
}
Some(mapped)
}
pub fn new<P: PoleValue<FinitePoint3>, K: KnotValue>(
degree: u32,
knots: K,
poles: NurbsPoles3<P>,
periodic: bool,
) -> Result<Self, NurbsError> {
require_curve_cardinality(degree, knots.knot_count(), poles.count(), "control_points")?;
let poles = P::admit_curve_poles(poles, |poles| map_curve_poles(poles))?;
let knots = admit_knots(knots, "")?;
Ok(NurbsCurve {
degree,
knots,
poles,
periodic,
})
}
pub const fn degree(&self) -> u32 {
self.degree
}
pub fn knots(&self) -> &KnotVector {
&self.knots
}
pub fn into_parts(self) -> (u32, KnotVector, NurbsPoles3<FinitePoint3>, bool) {
(self.degree, self.knots, self.poles, self.periodic)
}
pub fn edit_knots(&mut self, edit: impl FnOnce(&mut [f64])) -> Result<(), NurbsError> {
let mut values = self.knots.as_slice().to_vec();
edit(&mut values);
let knots = build_raw_knots(values, "")?;
self.knots = knots;
Ok(())
}
pub fn with_knots(mut self, knots: Vec<f64>) -> Result<Self, NurbsError> {
require_curve_cardinality(
self.degree,
knots.len(),
self.poles.count(),
"control_points",
)?;
self.knots = build_raw_knots(knots, "")?;
Ok(self)
}
pub fn from_finite_lanes(
degree: u32,
knots: Vec<FiniteReal>,
control_points: Vec<FinitePoint3>,
weights: Option<Vec<FiniteReal>>,
periodic: bool,
) -> Result<Self, NurbsError> {
let poles = pair_curve_lanes(control_points, weights, |index, weight| {
admit_finite_weight("poles", index, weight)
})?;
NurbsCurve::new(degree, knots, poles, periodic)
}
pub fn from_checked_lanes<P: PoleValue<FinitePoint3>, K: KnotValue>(
degree: u32,
knots: K,
control_points: Vec<P>,
weights: Option<Vec<NonZeroReal>>,
periodic: bool,
) -> Result<Self, NurbsError> {
let poles = pair_curve_lanes(control_points, weights, |_, weight| Ok(weight))?;
NurbsCurve::new(degree, knots, poles, periodic)
}
pub const fn pole_rows(&self) -> &NurbsPoles3<FinitePoint3> {
&self.poles
}
#[must_use]
pub fn control_points(&self) -> Vec<FinitePoint3> {
self.poles.points()
}
pub fn pole_count(&self) -> usize {
self.poles.count()
}
pub fn try_map_control_points<E>(
&mut self,
map: impl Fn(usize, FinitePoint3) -> Result<FinitePoint3, E>,
) -> Result<(), E> {
match &self.poles {
NurbsPoles3::Polynomial { points } => {
for (index, point) in points.iter().copied().enumerate() {
map(index, point)?;
}
}
NurbsPoles3::Rational { points } => {
for (index, pole) in points.iter().enumerate() {
map(index, pole.point)?;
}
}
}
match &mut self.poles {
NurbsPoles3::Polynomial { points } => {
for (index, point) in points.iter_mut().enumerate() {
*point = map(index, *point)?;
}
}
NurbsPoles3::Rational { points } => {
for (index, pole) in points.iter_mut().enumerate() {
pole.point = map(index, pole.point)?;
}
}
}
Ok(())
}
pub fn weights(&self) -> Option<Vec<NonZeroReal>> {
match &self.poles {
NurbsPoles3::Polynomial { .. } => None,
NurbsPoles3::Rational { points } => {
Some(points.iter().map(|pole| pole.weight).collect())
}
}
}
pub const fn periodic(&self) -> bool {
self.periodic
}
pub fn reverse_parameterization(&mut self) {
self.knots.reverse_negated();
self.poles.reverse();
}
pub fn reverse_parameterization_in_range(
&mut self,
start: FiniteReal,
end: FiniteReal,
) -> Option<()> {
let mut previous = None;
for knot in self.knots.0.iter().rev() {
let reflected = FiniteReal::new(*knot)
.and_then(|knot| crate::math::reflect_parameter(knot, start, end))?;
if previous.is_some_and(|previous| previous > reflected) {
return None;
}
previous = Some(reflected);
}
self.poles.reverse();
self.knots.0.reverse();
for knot in &mut self.knots.0 {
let reflected = FiniteReal::new(*knot)
.and_then(|knot| crate::math::reflect_parameter(knot, start, end))?;
*knot = reflected.get();
}
Some(())
}
}
impl<'de> Deserialize<'de> for NurbsCurve {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Wire {
degree: u32,
knots: Vec<f64>,
poles: NurbsPoles3,
periodic: bool,
}
let wire = Wire::deserialize(deserializer)?;
NurbsCurve::new(wire.degree, wire.knots, wire.poles, wire.periodic)
.map_err(serde::de::Error::custom)
}
}
pub fn knots_nondecreasing(knots: &[f64]) -> bool {
knots_ordered(knots, true)
}
pub fn knots_strictly_increasing(knots: &[f64]) -> bool {
knots_ordered(knots, false)
}
fn knots_ordered(knots: &[f64], repeats: bool) -> bool {
for pair in knots.windows(2) {
match pair[0].partial_cmp(&pair[1]) {
Some(std::cmp::Ordering::Less) => {}
Some(std::cmp::Ordering::Equal) if repeats => {}
_ => return false,
}
}
true
}
#[cfg(test)]
mod tests;
mod identity_rewrite;