pub mod bezier;
pub mod bounds;
pub mod scoped;
pub(crate) mod scratch;
pub(super) mod admitted;
use crate::features::FinitePoint3;
use crate::math::Point3;
use crate::scalar::{FiniteReal, NonZeroReal};
use cadmpeg_core::decode::DecodeContext;
use cadmpeg_core::CodecError;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
fn copy_decode_grid<T: Copy>(
rows: &[Vec<T>],
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Vec<Vec<T>>, CodecError> {
ctx.try_collect_retained_with(rows, operation, |row| {
super::copy_decode_slice(row, ctx, operation)
})
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub struct KnotVector(Vec<f64>);
impl KnotVector {
pub fn new(
ctx: &DecodeContext<'_>,
knots: Vec<f64>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(build_raw_knots(ctx, knots, ""))
}
pub fn from_finite_lanes(
ctx: &DecodeContext<'_>,
knots: Vec<FiniteReal>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(build_finite_knots(ctx, knots, ""))
}
pub fn into_values(self) -> Vec<f64> {
self.0
}
#[must_use]
pub fn as_slice(&self) -> &[f64] {
&self.0
}
pub fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
Ok(Self(ctx.copy_slice(&self.0, operation)?))
}
pub(super) fn reverse_negated(&mut self, ctx: &DecodeContext<'_>) -> Result<(), CodecError> {
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(self.0.len() / 2),
"IR signed knot reversal",
)?;
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(self.0.len()),
"IR signed knot negation",
)?;
self.0.reverse();
for knot in &mut self.0 {
*knot = -*knot;
}
Ok(())
}
}
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<S: NurbsAdmission>(
admission: &S,
knots: Vec<f64>,
prefix: &str,
) -> Result<KnotVector, S::Error> {
require_nondecreasing_knots(admission, &knots, prefix)?;
Ok(KnotVector(knots))
}
fn build_finite_knots<S: NurbsAdmission>(
admission: &S,
knots: Vec<FiniteReal>,
prefix: &str,
) -> Result<KnotVector, S::Error> {
let values = admission.collect(knots, "IR finite knot values", |value| Ok(value.get()))?;
require_knot_order(admission, &values, prefix)?;
Ok(KnotVector(values))
}
pub(super) fn admit_knots<S: NurbsAdmission, K: KnotValue>(
admission: &S,
knots: K,
prefix: &str,
) -> Result<KnotVector, S::Error> {
knots.admit(
|values| build_raw_knots(admission, values, prefix),
|values| build_finite_knots(admission, 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 {
const RETAINS_POLE_STORAGE: bool = false;
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 {
const RETAINS_POLE_STORAGE: bool = true;
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)
}
}
pub(crate) trait NurbsAdmission {
type Error: From<NurbsError>;
fn collect<I, T>(
&self,
values: Vec<I>,
operation: &'static str,
convert: impl FnMut(I) -> Result<T, Self::Error>,
) -> Result<Vec<T>, Self::Error>;
fn reserve<T>(
&self,
values: &mut Vec<T>,
storage: &mut Option<cadmpeg_core::decode::ScopedReservation<'_>>,
operation: &'static str,
) -> Result<(), Self::Error>;
fn copy_field(&self, field: &str) -> Result<String, Self::Error>;
fn work(&self, count: u64, operation: &'static str) -> Result<(), Self::Error>;
fn structure(&self, message: std::fmt::Arguments<'_>) -> Result<Self::Error, Self::Error>;
}
pub(crate) struct StandardNurbsAdmission;
impl NurbsAdmission for StandardNurbsAdmission {
type Error = NurbsError;
fn collect<I, T>(
&self,
values: Vec<I>,
_operation: &'static str,
mut convert: impl FnMut(I) -> Result<T, Self::Error>,
) -> Result<Vec<T>, Self::Error> {
let mut output = Vec::new();
scratch::reserve_exact(&mut output, values.len(), "reconstruct NURBS poles")?;
for value in values {
output.push(convert(value)?);
}
Ok(output)
}
fn reserve<T>(
&self,
values: &mut Vec<T>,
_storage: &mut Option<cadmpeg_core::decode::ScopedReservation<'_>>,
operation: &'static str,
) -> Result<(), Self::Error> {
values
.try_reserve(1)
.map_err(|_| scratch::allocation_refusal(1, operation).into())
}
fn copy_field(&self, field: &str) -> Result<String, Self::Error> {
Ok(field.to_owned())
}
fn work(&self, _count: u64, _operation: &'static str) -> Result<(), Self::Error> {
Ok(())
}
fn structure(&self, message: std::fmt::Arguments<'_>) -> Result<Self::Error, Self::Error> {
Ok(NurbsError::Structure(message.to_string()))
}
}
fn map_pole<P: PoleValue<T>, T, S: NurbsAdmission>(storage: &S, point: P) -> Result<T, S::Error> {
match point.admit() {
Some(point) => Ok(point),
None => Err(storage.structure(format_args!("control_points contains a non-finite point"))?),
}
}
fn map_curve_poles<P: PoleValue<T>, T, S: NurbsAdmission>(
storage: &S,
poles: NurbsPoles3<P>,
) -> Result<NurbsPoles3<T>, S::Error> {
Ok(match poles {
NurbsPoles3::Polynomial { points } => NurbsPoles3::Polynomial {
points: storage.collect(points, "IR NURBS admitted poles", |point| {
map_pole(storage, point)
})?,
},
NurbsPoles3::Rational { points } => NurbsPoles3::Rational {
points: storage.collect(points, "IR NURBS admitted poles", |pole| {
Ok(WeightedPole3 {
point: map_pole(storage, pole.point)?,
weight: pole.weight,
})
})?,
},
})
}
fn map_surface_poles<P: PoleValue<T>, T, S: NurbsAdmission>(
storage: &S,
grid: NurbsPoleGrid<P>,
) -> Result<NurbsPoleGrid<T>, S::Error> {
Ok(match grid {
NurbsPoleGrid::Polynomial { rows } => NurbsPoleGrid::Polynomial {
rows: storage.collect(rows, "IR NURBS admitted grid rows", |row| {
storage.collect(row, "IR NURBS admitted poles", |point| {
map_pole(storage, point)
})
})?,
},
NurbsPoleGrid::Rational { rows } => NurbsPoleGrid::Rational {
rows: storage.collect(rows, "IR NURBS admitted grid rows", |row| {
storage.collect(row, "IR NURBS admitted poles", |pole| {
Ok(WeightedPole3 {
point: map_pole(storage, pole.point)?,
weight: pole.weight,
})
})
})?,
},
})
}
fn weighted_poles<P, W, E>(
points: Vec<P>,
weights: Vec<W>,
mut reserve: impl FnMut(&mut Vec<WeightedPole3<P>>) -> Result<(), E>,
mut work: impl FnMut() -> Result<(), E>,
mut weight: impl FnMut(usize, W) -> Result<NonZeroReal, E>,
) -> Result<Vec<WeightedPole3<P>>, E> {
let mut output = Vec::new();
for (index, (point, value)) in points.into_iter().zip(weights).enumerate() {
reserve(&mut output)?;
work()?;
output.push(WeightedPole3 {
point,
weight: weight(index, value)?,
});
}
Ok(output)
}
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(
ctx: &DecodeContext<'_>,
points: Vec<P>,
weights: Option<Vec<f64>>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(pair_curve_lanes(
ctx,
points,
weights,
&mut None,
|index, weight| admit_weight(ctx, "poles", index, weight),
))
}
pub fn from_finite_lanes(
ctx: &DecodeContext<'_>,
points: Vec<P>,
weights: Option<Vec<FiniteReal>>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(pair_curve_lanes(
ctx,
points,
weights,
&mut None,
|index, weight| admit_finite_weight(ctx, "poles", index, weight),
))
}
pub fn from_checked_lanes(
ctx: &DecodeContext<'_>,
points: Vec<P>,
weights: Option<Vec<NonZeroReal>>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(pair_curve_lanes(
ctx,
points,
weights,
&mut None,
|_, 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, S: NurbsAdmission>(
admission: &S,
points: Vec<P>,
weights: Option<Vec<W>>,
storage: &mut Option<cadmpeg_core::decode::ScopedReservation<'_>>,
mut weight: impl FnMut(usize, W) -> Result<NonZeroReal, S::Error>,
) -> Result<NurbsPoles3<P>, S::Error> {
let Some(weights) = weights else {
return Ok(NurbsPoles3::Polynomial { points });
};
require_weight_lane(admission, "poles", points.len(), weights.len())?;
Ok(NurbsPoles3::Rational {
points: weighted_poles(
points,
weights,
|values| admission.reserve(values, storage, "IR NURBS paired poles"),
|| admission.work(1, "IR NURBS paired poles"),
&mut weight,
)?,
})
}
fn pair_grid_lanes<P, W, S: NurbsAdmission>(
admission: &S,
rows: Vec<Vec<P>>,
weights: Option<Vec<Vec<W>>>,
storage: &mut Option<cadmpeg_core::decode::ScopedReservation<'_>>,
mut weight: impl FnMut(usize, W) -> Result<NonZeroReal, S::Error>,
) -> Result<NurbsPoleGrid<P>, S::Error> {
let Some(weights) = weights else {
return Ok(NurbsPoleGrid::Polynomial { rows });
};
require_weight_lane(admission, "pole grid", rows.len(), weights.len())?;
let mut output = Vec::new();
for (row, weights) in rows.into_iter().zip(weights) {
admission.work(1, "IR NURBS paired grid rows")?;
admission.reserve(&mut output, storage, "IR NURBS paired grid rows")?;
require_weight_lane(admission, "pole grid row", row.len(), weights.len())?;
output.push(weighted_poles(
row,
weights,
|values| admission.reserve(values, storage, "IR NURBS paired poles"),
|| admission.work(1, "IR NURBS paired poles"),
&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(
ctx: &DecodeContext<'_>,
rows: Vec<Vec<P>>,
weights: Option<Vec<Vec<f64>>>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(pair_grid_lanes(
ctx,
rows,
weights,
&mut None,
|index, weight| admit_weight(ctx, "pole grid row", index, weight),
))
}
pub fn from_finite_lanes(
ctx: &DecodeContext<'_>,
rows: Vec<Vec<P>>,
weights: Option<Vec<Vec<FiniteReal>>>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(pair_grid_lanes(
ctx,
rows,
weights,
&mut None,
|index, weight| admit_finite_weight(ctx, "pole grid row", index, weight),
))
}
pub fn from_checked_lanes(
ctx: &DecodeContext<'_>,
rows: Vec<Vec<P>>,
weights: Option<Vec<Vec<NonZeroReal>>>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(pair_grid_lanes(
ctx,
rows,
weights,
&mut None,
|_, 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 try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
ctx.charge_work(1, operation)?;
Ok(Self {
u_degree: self.u_degree,
v_degree: self.v_degree,
u_knots: self.u_knots.try_clone_for_decode(ctx, operation)?,
v_knots: self.v_knots.try_clone_for_decode(ctx, operation)?,
control_points: copy_decode_grid(&self.control_points, ctx, operation)?,
})
}
pub fn new(
ctx: &DecodeContext<'_>,
u_degree: u32,
v_degree: u32,
u_knots: Vec<f64>,
v_knots: Vec<f64>,
control_points: Vec<Vec<Point3>>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(build_bspline_surface(
ctx,
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>,
ctx: &DecodeContext<'_>,
) -> Result<Result<(), E>, CodecError> {
let count = cadmpeg_core::decode::u64_from_index(self.control_points.len())
.checked_mul(cadmpeg_core::decode::u64_from_index(
self.control_points.first().map_or(0, Vec::len),
))
.ok_or_else(|| ctx.refuse_codec_limit("IR pole edit work", u64::MAX - 1, u64::MAX))?;
ctx.charge_work(count, "IR pole edit validation")?;
ctx.charge_work(count, "IR pole edit mutation")?;
Ok((|| {
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 build_bspline_surface<S: NurbsAdmission>(
admission: &S,
u_degree: u32,
v_degree: u32,
u_knots: Vec<f64>,
v_knots: Vec<f64>,
control_points: Vec<Vec<Point3>>,
) -> Result<BsplineSurface, S::Error> {
let u_count = control_points.len();
let v_count = control_points.first().map_or(0, Vec::len);
let u_knots = bspline_axis_knots(admission, "u", "u_knots", u_degree, u_count, u_knots)?;
let v_knots = bspline_axis_knots(admission, "v", "v_knots", v_degree, v_count, v_knots)?;
require_rectangular_grid(admission, "control_points", &control_points)?;
let control_points =
admission.collect(control_points, "IR admitted B-spline grid rows", |row| {
admission.collect(row, "IR admitted B-spline grid poles", |point| {
map_pole(admission, point)
})
})?;
Ok(BsplineSurface {
u_degree,
v_degree,
u_knots,
v_knots,
control_points,
})
}
fn bspline_axis_knots<S: NurbsAdmission>(
admission: &S,
axis: &str,
knot_field: &str,
degree: u32,
count: usize,
knots: Vec<f64>,
) -> Result<KnotVector, S::Error> {
if count <= cadmpeg_core::decode::index_from_u32(degree) {
return Err(admission.structure(format_args!(
"control_points {axis} count must exceed degree {degree}, found {count}"
))?);
}
require_length(
admission,
knot_field,
knots.len(),
checked_knot_count(admission, axis, count, degree)?,
)?;
build_raw_knots(admission, 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)?;
build_bspline_surface(
&StandardNurbsAdmission,
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("resource limit: {0:?}")]
ResourceLimit(cadmpeg_core::decode::ResourceLimit),
#[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<cadmpeg_core::decode::ResourceLimit> for NurbsError {
fn from(limit: cadmpeg_core::decode::ResourceLimit) -> Self {
Self::ResourceLimit(limit)
}
}
impl From<NurbsError> for cadmpeg_core::CodecError {
fn from(error: NurbsError) -> Self {
match error {
NurbsError::ResourceLimit(limit) => Self::ResourceLimit(limit),
error => Self::Malformed(error.to_string()),
}
}
}
fn checked_knot_count<S: NurbsAdmission>(
admission: &S,
field: &str,
pole_count: usize,
degree: u32,
) -> Result<usize, S::Error> {
let Ok(degree) = usize::try_from(degree) else {
return Err(admission.structure(format_args!("{field} knot count overflows usize"))?);
};
match pole_count
.checked_add(degree)
.and_then(|count| count.checked_add(1))
{
Some(count) => Ok(count),
None => Err(admission.structure(format_args!("{field} knot count overflows usize"))?),
}
}
fn require_rectangular_grid<T, S: NurbsAdmission>(
admission: &S,
field: &str,
rows: &[Vec<T>],
) -> Result<(), S::Error> {
let width = rows.first().map_or(0, Vec::len);
for row in rows {
admission.work(1, "IR NURBS grid row shape")?;
if row.len() != width {
return Err(admission.structure(format_args!(
"{field} row must contain {width} values, found {}",
row.len(),
))?);
}
}
Ok(())
}
fn require_length<S: NurbsAdmission>(
admission: &S,
field: &str,
actual: usize,
expected: usize,
) -> Result<(), S::Error> {
if actual == expected {
Ok(())
} else {
Err(admission.structure(format_args!(
"{field} must contain {expected} values, found {actual}"
))?)
}
}
pub(super) fn require_weight_lane<S: NurbsAdmission>(
admission: &S,
field: &str,
poles: usize,
weights: usize,
) -> Result<(), S::Error> {
if poles == weights {
Ok(())
} else {
Err(NurbsError::WeightLaneLength {
field: admission.copy_field(field)?,
poles,
weights,
}
.into())
}
}
pub(crate) fn admit_weight<S: NurbsAdmission>(
admission: &S,
field: &str,
index: usize,
weight: f64,
) -> Result<NonZeroReal, S::Error> {
if let Some(admitted) = NonZeroReal::new(weight) {
return Ok(admitted);
}
Err(NurbsError::UnusableWeight {
field: admission.copy_field(field)?,
index,
weight,
}
.into())
}
pub(super) fn admit_finite_weight<S: NurbsAdmission>(
admission: &S,
field: &str,
index: usize,
weight: FiniteReal,
) -> Result<NonZeroReal, S::Error> {
if let Some(admitted) = NonZeroReal::from_finite(weight) {
return Ok(admitted);
}
Err(NurbsError::UnusableWeight {
field: admission.copy_field(field)?,
index,
weight: weight.get(),
}
.into())
}
fn require_finite_scalars<S: NurbsAdmission>(
admission: &S,
prefix: &str,
values: &[f64],
) -> Result<(), S::Error> {
admission.work(0, "IR NURBS knot finiteness")?;
for value in values {
admission.work(1, "IR NURBS knot finiteness")?;
if !value.is_finite() {
return Err(
admission.structure(format_args!("{prefix}knots contains a non-finite value"))?
);
}
}
Ok(())
}
fn require_knot_order<S: NurbsAdmission>(
admission: &S,
knots: &[f64],
prefix: &str,
) -> Result<(), S::Error> {
if knots_nondecreasing(knots, |count| admission.work(count, "IR NURBS knot order"))? {
Ok(())
} else {
Err(admission.structure(format_args!("{prefix}knots must be non-decreasing"))?)
}
}
fn require_nondecreasing_knots<S: NurbsAdmission>(
admission: &S,
knots: &[f64],
prefix: &str,
) -> Result<(), S::Error> {
require_finite_scalars(admission, prefix, knots)?;
require_knot_order(admission, knots, prefix)
}
pub(super) fn require_curve_cardinality<S: NurbsAdmission>(
admission: &S,
degree: u32,
knot_count: usize,
pole_count: usize,
point_field: &str,
) -> Result<(), S::Error> {
if u64::try_from(pole_count).is_ok_and(|count| count <= u64::from(degree)) {
return Err(admission.structure(format_args!(
"{point_field} must contain more than degree {degree} poles, found {pole_count}"
))?);
}
require_length(
admission,
"knots",
knot_count,
checked_knot_count(admission, "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,
}
}
}
pub(crate) fn build_curve<P: PoleValue<FinitePoint3>, K: KnotValue, S: NurbsAdmission>(
admission: &S,
degree: u32,
knots: K,
poles: NurbsPoles3<P>,
periodic: bool,
) -> Result<NurbsCurve, S::Error> {
require_curve_cardinality(
admission,
degree,
knots.knot_count(),
poles.count(),
"control_points",
)?;
let poles = P::admit_curve_poles(poles, |poles| map_curve_poles(admission, poles))?;
let knots = admit_knots(admission, knots, "")?;
Ok(NurbsCurve {
degree,
knots,
poles,
periodic,
})
}
fn build_surface<P: PoleValue<FinitePoint3>, U: KnotValue, V: KnotValue, S: NurbsAdmission>(
admission: &S,
u: NurbsSurfaceAxis<U>,
v: NurbsSurfaceAxis<V>,
poles: NurbsPoleGrid<P>,
normal_reversed: bool,
) -> Result<NurbsSurface, S::Error> {
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(
admission,
u_degree,
u_knots.knot_count(),
v_degree,
v_knots.knot_count(),
&poles,
)?;
let poles = P::admit_surface_poles(poles, |poles| map_surface_poles(admission, poles))?;
let u_knots = admit_knots(admission, u_knots, "u_")?;
let v_knots = admit_knots(admission, v_knots, "v_")?;
Ok(NurbsSurface {
u_degree,
v_degree,
u_knots,
v_knots,
poles,
normal_reversed,
u_periodic,
v_periodic,
})
}
fn require_surface_shape<P, S: NurbsAdmission>(
admission: &S,
u_degree: u32,
u_knots: usize,
v_degree: u32,
v_knots: usize,
poles: &NurbsPoleGrid<P>,
) -> Result<(), S::Error> {
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(admission.structure(format_args!(
"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(admission.structure(format_args!(
"v_count must exceed v_degree {v_degree}, found {v_count}"
))?);
}
require_length(
admission,
"u_knots",
u_knots,
checked_knot_count(admission, "u", u_count, u_degree)?,
)?;
require_length(
admission,
"v_knots",
v_knots,
checked_knot_count(admission, "v", v_count, v_degree)?,
)?;
match poles {
NurbsPoleGrid::Polynomial { rows } => {
require_rectangular_grid(admission, "control_points", rows)?;
}
NurbsPoleGrid::Rational { rows } => {
require_rectangular_grid(admission, "control_points", rows)?;
}
}
Ok(())
}
impl NurbsSurface {
pub fn try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
let u_knots = self.u_knots.try_clone_for_decode(ctx, operation)?;
let v_knots = self.v_knots.try_clone_for_decode(ctx, operation)?;
let poles = match &self.poles {
NurbsPoleGrid::Polynomial { rows } => NurbsPoleGrid::Polynomial {
rows: copy_decode_grid(rows, ctx, operation)?,
},
NurbsPoleGrid::Rational { rows } => NurbsPoleGrid::Rational {
rows: copy_decode_grid(rows, ctx, operation)?,
},
};
Ok(Self {
u_degree: self.u_degree,
v_degree: self.v_degree,
u_knots,
v_knots,
poles,
normal_reversed: self.normal_reversed,
u_periodic: self.u_periodic,
v_periodic: self.v_periodic,
})
}
pub fn new<P: PoleValue<FinitePoint3>, U: KnotValue, V: KnotValue>(
ctx: &DecodeContext<'_>,
u: NurbsSurfaceAxis<U>,
v: NurbsSurfaceAxis<V>,
poles: NurbsPoleGrid<P>,
normal_reversed: bool,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(build_surface(ctx, u, v, poles, normal_reversed))
}
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(
ctx: &DecodeContext<'_>,
u: NurbsSurfaceAxis<Vec<FiniteReal>>,
v: NurbsSurfaceAxis<Vec<FiniteReal>>,
lanes: NurbsSurfaceLanes<FinitePoint3, FiniteReal>,
normal_reversed: bool,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish((|| {
let mut storage = None;
let NurbsSurfaceLanes {
control_points,
weights,
} = lanes;
let poles = pair_grid_lanes(
ctx,
control_points,
weights,
&mut storage,
|index, weight| admit_finite_weight(ctx, "pole grid row", index, weight),
)?;
build_surface(ctx, u, v, poles, normal_reversed)
})())
}
pub fn from_checked_lanes<P: PoleValue<FinitePoint3>, U: KnotValue, V: KnotValue>(
ctx: &DecodeContext<'_>,
u: NurbsSurfaceAxis<U>,
v: NurbsSurfaceAxis<V>,
lanes: NurbsSurfaceLanes<P, NonZeroReal>,
normal_reversed: bool,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish((|| {
let mut storage = if lanes.weights.is_some() && !P::RETAINS_POLE_STORAGE {
Some(ctx.reserve_scoped(0, "IR NURBS paired grid rows")?)
} else {
None
};
let NurbsSurfaceLanes {
control_points,
weights,
} = lanes;
let poles =
pair_grid_lanes(ctx, control_points, weights, &mut storage, |_, weight| {
Ok(weight)
})?;
build_surface(ctx, 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>,
ctx: &DecodeContext<'_>,
) -> Result<Result<(), E>, CodecError> {
let count = cadmpeg_core::decode::u64_from_index(self.u_count())
.checked_mul(cadmpeg_core::decode::u64_from_index(self.v_count()))
.ok_or_else(|| ctx.refuse_codec_limit("IR pole edit work", u64::MAX - 1, u64::MAX))?;
ctx.charge_work(count, "IR pole edit validation")?;
ctx.charge_work(count, "IR pole edit mutation")?;
Ok((|| {
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, ctx: &DecodeContext<'_>) -> Result<(), CodecError> {
fn transpose<T: Copy>(
ctx: &DecodeContext<'_>,
rows: &[Vec<T>],
width: usize,
) -> Result<Vec<Vec<T>>, CodecError> {
ctx.try_collect_retained_with(0..width, "IR NURBS transposed grid rows", |column| {
ctx.try_collect_retained_with(rows, "IR NURBS transposed grid poles", |row| {
Ok(row[column])
})
})
}
let width = self.v_count();
match &mut self.poles {
NurbsPoleGrid::Polynomial { rows } => *rows = transpose(ctx, rows, width)?,
NurbsPoleGrid::Rational { rows } => *rows = transpose(ctx, 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);
Ok(())
}
}
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)?;
build_surface(
&StandardNurbsAdmission,
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 try_clone_for_decode(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
) -> Result<Self, CodecError> {
let knots = self.knots.try_clone_for_decode(ctx, operation)?;
let poles = match &self.poles {
NurbsPoles3::Polynomial { points } => NurbsPoles3::Polynomial {
points: super::copy_decode_slice(points, ctx, operation)?,
},
NurbsPoles3::Rational { points } => NurbsPoles3::Rational {
points: super::copy_decode_slice(points, ctx, operation)?,
},
};
Ok(Self {
degree: self.degree,
knots,
poles,
periodic: self.periodic,
})
}
pub fn map_control_points(
&self,
ctx: &DecodeContext<'_>,
operation: &'static str,
mut map: impl FnMut(Point3) -> Point3,
) -> Result<Option<Self>, CodecError> {
let mut mapped = self.try_clone_for_decode(ctx, operation)?;
match &mut mapped.poles {
NurbsPoles3::Polynomial { points } => {
for point in points {
ctx.charge_work(1, operation)?;
let Some(next) = FinitePoint3::new(map(point.get())) else {
return Ok(None);
};
*point = next;
}
}
NurbsPoles3::Rational { points } => {
for pole in points {
ctx.charge_work(1, operation)?;
let Some(next) = FinitePoint3::new(map(pole.point.get())) else {
return Ok(None);
};
pole.point = next;
}
}
}
Ok(Some(mapped))
}
pub fn new<P: PoleValue<FinitePoint3>, K: KnotValue>(
ctx: &DecodeContext<'_>,
degree: u32,
knots: K,
poles: NurbsPoles3<P>,
periodic: bool,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish(build_curve(ctx, 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,
ctx: &DecodeContext<'_>,
edit: impl FnOnce(&mut [f64]),
) -> Result<Result<(), NurbsError>, CodecError> {
admitted::finish((|| {
let (mut values, storage) = ctx
.copy_temporary_slice(self.knots.as_slice(), "IR NURBS edited knots")
.map_err(CodecError::from)?;
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(values.len()),
"IR NURBS knot edit",
)?;
edit(&mut values);
let knots = build_raw_knots(ctx, values, "")?;
storage.commit()?;
self.knots = knots;
Ok(())
})())
}
pub fn with_knots(
mut self,
ctx: &DecodeContext<'_>,
knots: Vec<f64>,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish((|| {
require_curve_cardinality(
ctx,
self.degree,
knots.len(),
self.poles.count(),
"control_points",
)?;
self.knots = build_raw_knots(ctx, knots, "")?;
Ok(self)
})())
}
pub fn from_finite_lanes(
ctx: &DecodeContext<'_>,
degree: u32,
knots: Vec<FiniteReal>,
control_points: Vec<FinitePoint3>,
weights: Option<Vec<FiniteReal>>,
periodic: bool,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish((|| {
let mut storage = None;
let poles = pair_curve_lanes(
ctx,
control_points,
weights,
&mut storage,
|index, weight| admit_finite_weight(ctx, "poles", index, weight),
)?;
build_curve(ctx, degree, knots, poles, periodic)
})())
}
pub fn from_checked_lanes<P: PoleValue<FinitePoint3>, K: KnotValue>(
ctx: &DecodeContext<'_>,
degree: u32,
knots: K,
control_points: Vec<P>,
weights: Option<Vec<NonZeroReal>>,
periodic: bool,
) -> Result<Result<Self, NurbsError>, CodecError> {
admitted::finish((|| {
let mut storage = if weights.is_some() && !P::RETAINS_POLE_STORAGE {
Some(ctx.reserve_scoped(0, "IR NURBS paired poles")?)
} else {
None
};
let poles =
pair_curve_lanes(ctx, control_points, weights, &mut storage, |_, weight| {
Ok(weight)
})?;
build_curve(ctx, 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>,
ctx: &DecodeContext<'_>,
) -> Result<Result<(), E>, CodecError> {
let count = cadmpeg_core::decode::u64_from_index(self.poles.count());
ctx.charge_work(count, "IR pole edit validation")?;
ctx.charge_work(count, "IR pole edit mutation")?;
Ok((|| {
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, ctx: &DecodeContext<'_>) -> Result<(), CodecError> {
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(self.poles.count() / 2),
"IR signed pole reversal",
)?;
self.knots.reverse_negated(ctx)?;
self.poles.reverse();
Ok(())
}
pub fn reverse_parameterization_in_range(
&mut self,
ctx: &DecodeContext<'_>,
start: FiniteReal,
end: FiniteReal,
) -> Result<Option<()>, CodecError> {
let mut previous = None;
for knot in self.knots.0.iter().rev() {
ctx.charge_work(1, "IR NURBS reflected knot validation")?;
let Some(reflected) = FiniteReal::new(*knot)
.and_then(|knot| crate::math::reflect_parameter(knot, start, end))
else {
return Ok(None);
};
if previous.is_some_and(|previous| previous > reflected) {
return Ok(None);
}
previous = Some(reflected);
}
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(self.poles.count() / 2),
"IR NURBS reflected pole reversal",
)?;
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(self.knots.0.len() / 2),
"IR NURBS reflected knot reversal",
)?;
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(self.knots.0.len()),
"IR NURBS reflected knot edit",
)?;
self.poles.reverse();
self.knots.0.reverse();
for knot in &mut self.knots.0 {
let Some(reflected) = FiniteReal::new(*knot)
.and_then(|knot| crate::math::reflect_parameter(knot, start, end))
else {
return Ok(None);
};
*knot = reflected.get();
}
Ok(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)?;
build_curve(
&StandardNurbsAdmission,
wire.degree,
wire.knots,
wire.poles,
wire.periodic,
)
.map_err(serde::de::Error::custom)
}
}
pub fn knots_nondecreasing<E>(
knots: &[f64],
mut work: impl FnMut(u64) -> Result<(), E>,
) -> Result<bool, E> {
knots_ordered(knots, true, &mut work)
}
pub fn knots_strictly_increasing<E>(
knots: &[f64],
mut work: impl FnMut(u64) -> Result<(), E>,
) -> Result<bool, E> {
knots_ordered(knots, false, &mut work)
}
fn knots_ordered<E>(
knots: &[f64],
repeats: bool,
work: &mut impl FnMut(u64) -> Result<(), E>,
) -> Result<bool, E> {
work(0)?;
for pair in knots.windows(2) {
work(1)?;
match pair[0].partial_cmp(&pair[1]) {
Some(std::cmp::Ordering::Less) => {}
Some(std::cmp::Ordering::Equal) if repeats => {}
_ => return Ok(false),
}
}
Ok(true)
}
#[cfg(test)]
mod tests;
impl From<CodecError> for NurbsError {
fn from(error: CodecError) -> Self {
match error {
CodecError::ResourceLimit(limit) => Self::ResourceLimit(limit),
error => Self::Structure(error.to_string()),
}
}
}
mod identity_rewrite;