use std::cell::{Cell, RefCell};
use cadmpeg_core::decode::{u64_from_index, DecodeContext, ResourceLimit, ScopedReservation};
use super::admission::{EvaluationAdmission, EvaluationDepthGuard};
use super::{CurveDerivative, EvaluationFailure};
use crate::features::{FinitePoint3, FiniteVector3};
use crate::geometry::nurbs::NurbsCurve;
use crate::geometry::pcurve::PcurveGeometry;
use crate::geometry::{CurveGeometry, SolvedCurveGeometry, SolvedSurfaceGeometry, SurfaceGeometry};
use crate::math::{Point2, Point3};
use crate::scalar::FiniteReal;
use crate::units::FinitePoint2;
pub(super) const INLINE_SUPPORT: usize = 16;
#[derive(Debug, PartialEq)]
pub(super) enum SupportValues<T> {
Inline {
values: [T; INLINE_SUPPORT],
len: usize,
},
Heap(Vec<T>),
}
impl<T> std::ops::Deref for SupportValues<T> {
type Target = [T];
fn deref(&self) -> &[T] {
match self {
Self::Inline { values, len } => &values[..*len],
Self::Heap(values) => values,
}
}
}
impl<T> std::ops::DerefMut for SupportValues<T> {
fn deref_mut(&mut self) -> &mut [T] {
match self {
Self::Inline { values, len } => &mut values[..*len],
Self::Heap(values) => values,
}
}
}
pub(super) struct Scratch<'ctx, 'arena> {
pub(super) admission: EvaluationAdmission<'ctx, 'arena>,
independent_depth: Cell<usize>,
storage: RefCell<Option<ScopedReservation<'ctx>>>,
refusal: RefCell<Option<ResourceLimit>>,
}
impl<'ctx, 'arena> Scratch<'ctx, 'arena> {
pub(super) fn new(admission: impl Into<EvaluationAdmission<'ctx, 'arena>>) -> Self {
Self {
admission: admission.into(),
independent_depth: Cell::new(0),
storage: RefCell::new(None),
refusal: RefCell::new(None),
}
}
pub(super) fn admit<T>(&self, result: Result<T, ResourceLimit>) -> Option<T> {
match result {
Ok(value) => Some(value),
Err(error) => {
let mut refusal = self.refusal.borrow_mut();
if refusal.is_none() {
*refusal = Some(error);
}
None
}
}
}
pub(super) fn refused(&self) -> Option<ResourceLimit> {
if let Some(limit) = *self.refusal.borrow() {
return Some(limit);
}
let original = self
.admission
.work(0, "observe geometry evaluation refusal")
.err();
if let Some(limit) = original {
*self.refusal.borrow_mut() = Some(limit);
}
original
}
pub(super) fn unless_refused(&self) -> Result<(), ResourceLimit> {
self.refused().map_or(Ok(()), Err)
}
pub(super) fn failure<R>(&self, absent: EvaluationFailure<R>) -> EvaluationFailure<R> {
self.refused()
.map_or(absent, EvaluationFailure::ResourceLimit)
}
pub(super) fn settle<T, R>(
&self,
result: Result<T, EvaluationFailure<R>>,
) -> Result<T, EvaluationFailure<R>> {
self.refused()
.map_or(result, |limit| Err(EvaluationFailure::ResourceLimit(limit)))
}
pub(super) fn filled<T: Clone>(
&self,
count: usize,
value: T,
operation: &'static str,
work_operation: &'static str,
) -> Option<Vec<T>> {
if self.refusal.borrow().is_some() {
return None;
}
let mut values = Vec::new();
self.reserve(&mut values, count, operation)?;
for _ in 0..count {
self.work(1, work_operation)?;
values.push(value.clone());
}
Some(values)
}
pub(super) fn collect<T>(
&self,
values: impl IntoIterator<Item = Option<T>>,
operation: &'static str,
work_operation: &'static str,
) -> Option<Vec<T>> {
if self.refusal.borrow().is_some() {
return None;
}
self.work(0, work_operation)?;
let mut values = values.into_iter();
let mut output = Vec::new();
while values.size_hint() != (0, Some(0)) {
self.work(1, work_operation)?;
let Some(value) = values.next() else {
break;
};
self.reserve(&mut output, 1, operation)?;
output.push(value?);
}
Some(output)
}
pub(super) fn support_values<T: Copy + Default>(
&self,
values: impl ExactSizeIterator<Item = Option<T>>,
operation: &'static str,
work_operation: &'static str,
) -> Option<SupportValues<T>> {
let len = values.len();
if len > INLINE_SUPPORT {
return self
.collect(values, operation, work_operation)
.map(SupportValues::Heap);
}
self.work(0, work_operation)?;
let mut output = SupportValues::Inline {
values: [T::default(); INLINE_SUPPORT],
len,
};
for (index, value) in values.enumerate() {
self.work(1, work_operation)?;
output[index] = value?;
}
Some(output)
}
pub(super) fn reserve<T>(
&self,
values: &mut Vec<T>,
count: usize,
operation: &'static str,
) -> Option<()> {
self.work(0, operation)?;
match self.admission.context() {
Some(context) => {
let mut storage = self.storage.borrow_mut();
if storage.is_none() {
*storage = Some(self.admit(context.reserve_scoped_limit(0, operation))?);
}
let reservation = storage.as_mut()?;
self.admit(context.reserve_scoped_vec_limit(reservation, values, count, operation))
}
None => self.admit(crate::geometry::nurbs::scratch::reserve_exact(
values, count, operation,
)),
}
}
pub(super) fn temporary_vec<T>(
&self,
count: usize,
operation: &'static str,
) -> Option<(Vec<T>, Option<ScopedReservation<'ctx>>)> {
self.work(0, operation)?;
let mut values = Vec::new();
let storage = match self.admission.context() {
Some(context) => {
Some(self.admit(context.reserve_temporary_vec(&mut values, count, operation))?)
}
None => {
self.admit(crate::geometry::nurbs::scratch::reserve_exact(
&mut values,
count,
operation,
))?;
None
}
};
Some((values, storage))
}
pub(super) fn retained_copy<T: Copy>(
&self,
source: &[T],
operation: &'static str,
) -> Option<Vec<T>> {
self.work(0, operation)?;
let mut values = Vec::new();
match self.admission.context() {
Some(context) => {
self.admit(context.reserve_vec_limit(&mut values, source.len(), operation))?;
}
None => self.admit(crate::geometry::nurbs::scratch::reserve_exact(
&mut values,
source.len(),
operation,
))?,
}
for value in source {
self.work(std::mem::size_of::<T>(), operation)?;
values.push(*value);
}
Some(values)
}
pub(super) fn work(&self, count: usize, operation: &'static str) -> Option<()> {
if self.refusal.borrow().is_some() {
return None;
}
self.admit(self.admission.work(u64_from_index(count), operation))
}
pub(super) fn enter(&self) -> Option<EvaluationDepthGuard<'_>> {
if self.refusal.borrow().is_some() {
return None;
}
self.admit(self.admission.enter(&self.independent_depth))
}
pub(super) fn finish<T>(self, value: T) -> Result<T, ResourceLimit> {
self.unless_refused()?;
Ok(value)
}
pub(super) fn finish_evaluation<T, R>(
self,
result: Result<T, EvaluationFailure<R>>,
) -> Result<Result<T, EvaluationFailure<R>>, ResourceLimit> {
outer_refusal(self.finish(result)?)
}
}
impl<R> From<cadmpeg_core::CodecError> for EvaluationFailure<R> {
fn from(error: cadmpeg_core::CodecError) -> Self {
match error {
cadmpeg_core::CodecError::ResourceLimit(limit) => Self::ResourceLimit(limit),
_ => Self::NoValue,
}
}
}
pub fn outer_refusal<T, R>(
result: Result<T, EvaluationFailure<R>>,
) -> Result<Result<T, EvaluationFailure<R>>, ResourceLimit> {
match result {
Err(EvaluationFailure::ResourceLimit(limit)) => Err(limit),
result => Ok(result),
}
}
pub fn curve_point<'ctx, 'arena: 'ctx>(
admission: impl Into<EvaluationAdmission<'ctx, 'arena>>,
geometry: &CurveGeometry,
parameter: f64,
) -> Result<FinitePoint3, EvaluationFailure<Point3>> {
let scratch = Scratch::new(admission);
let result = geometry
.solved()
.ok_or(EvaluationFailure::NoValue)
.and_then(|geometry| super::curve_point_evaluation(&scratch, geometry, parameter));
scratch.settle(result)
}
pub fn nurbs_curve_point_at<'ctx, 'arena: 'ctx>(
admission: impl Into<EvaluationAdmission<'ctx, 'arena>>,
curve: &NurbsCurve,
parameter: f64,
) -> Result<FinitePoint3, EvaluationFailure<Point3>> {
let scratch = Scratch::new(admission);
let poles = curve.pole_rows();
let result = FiniteReal::new(parameter)
.ok_or(EvaluationFailure::NoValue)
.and_then(|parameter| {
super::nurbs_curve_point_evaluation(
&scratch,
curve.degree(),
curve.knots(),
poles.count(),
|index| poles.point_at(index),
|index| poles.weight_at(index),
parameter,
)
});
scratch.settle(result)
}
pub fn curve_tangent<'ctx, 'arena: 'ctx>(
admission: impl Into<EvaluationAdmission<'ctx, 'arena>>,
geometry: &CurveGeometry,
parameter: f64,
) -> Result<FiniteVector3, EvaluationFailure<()>> {
let scratch = Scratch::new(admission);
let result = geometry
.solved()
.ok_or(EvaluationFailure::NoValue)
.and_then(|geometry| {
super::curve_derivative_evaluation(
&scratch,
geometry,
parameter,
CurveDerivative::First,
)
});
scratch.settle(result)
}
pub fn surface_point<'ctx, 'arena: 'ctx>(
admission: impl Into<EvaluationAdmission<'ctx, 'arena>>,
geometry: &SurfaceGeometry,
u: f64,
v: f64,
) -> Result<FinitePoint3, EvaluationFailure<Point3>> {
let scratch = Scratch::new(admission);
let result = geometry
.solved()
.ok_or(EvaluationFailure::NoValue)
.and_then(|geometry| super::surface_point_evaluation(&scratch, geometry, u, v));
scratch.settle(result)
}
pub fn pcurve_uv<'ctx, 'arena: 'ctx>(
admission: impl Into<EvaluationAdmission<'ctx, 'arena>>,
geometry: &PcurveGeometry,
parameter: f64,
) -> Result<FinitePoint2, EvaluationFailure<Point2>> {
let scratch = Scratch::new(admission);
let result = FiniteReal::new(parameter)
.ok_or(EvaluationFailure::NoValue)
.and_then(|parameter| {
let evaluated = super::pcurve_uv_differential(&scratch, geometry, parameter)
.ok_or(EvaluationFailure::NoValue)?;
if let Some(limit) = evaluated.resource {
return Err(EvaluationFailure::ResourceLimit(limit));
}
evaluated.point.map_err(EvaluationFailure::NonFinite)
});
scratch.settle(result)
}
pub struct NurbsPointEvaluator<'curve, 'ctx> {
curve: &'curve NurbsCurve,
basis: SupportValues<f64>,
_storage: Option<ScopedReservation<'ctx>>,
}
impl<'curve, 'ctx> NurbsPointEvaluator<'curve, 'ctx> {
pub fn new(
ctx: &'ctx DecodeContext<'_>,
curve: &'curve NurbsCurve,
) -> Result<Self, ResourceLimit> {
let support = curve.knots().len() - curve.pole_count();
let (basis, storage) = if support <= INLINE_SUPPORT {
(
SupportValues::Inline {
values: [0.0; INLINE_SUPPORT],
len: support,
},
None,
)
} else {
{
let mut basis = Vec::new();
let storage =
ctx.reserve_temporary_vec(&mut basis, support, "IR B-spline basis")?;
ctx.charge_work_limit(u64_from_index(support), "IR B-spline basis work")?;
basis.extend(std::iter::repeat_n(0.0, support));
(SupportValues::Heap(basis), Some(storage))
}
};
Ok(Self {
curve,
basis,
_storage: storage,
})
}
pub fn point(
&mut self,
ctx: &DecodeContext<'_>,
parameter: f64,
) -> Result<Result<FinitePoint3, EvaluationFailure<Point3>>, ResourceLimit> {
let _depth = if matches!(&self.basis, SupportValues::Heap(_)) {
let depth = ctx.enter_nested_limit("geometry evaluation nesting")?;
Some(depth)
} else {
None
};
let degree = self.basis.len() - 1;
let result = (|| {
let parameter = FiniteReal::new(parameter).ok_or(EvaluationFailure::NoValue)?;
let span = super::basis::bspline_span(
ctx,
self.curve.knots(),
degree,
self.curve.pole_count(),
parameter.get(),
)
.map_err(EvaluationFailure::ResourceLimit)?
.ok_or(EvaluationFailure::NoValue)?;
let unreached = EvaluationFailure::NonFinite(super::UNREACHED_POINT);
super::basis::fill_bspline_basis(
ctx,
self.curve.knots(),
degree,
span,
parameter.get(),
&mut self.basis,
)
.map_err(EvaluationFailure::ResourceLimit)?
.ok_or(unreached)?;
let poles = self.curve.pole_rows();
let scratch = Scratch::new(ctx);
scratch.settle(super::nurbs_curve_point_from_basis(
&scratch,
&self.basis,
span,
|index| poles.point_at(index),
|index| poles.weight_at(index),
))
})();
outer_refusal(result)
}
}
#[cfg(test)]
mod tests;
pub fn curve_point_solved<'ctx, 'arena: 'ctx>(
admission: impl Into<EvaluationAdmission<'ctx, 'arena>>,
geometry: &SolvedCurveGeometry,
parameter: f64,
) -> Result<FinitePoint3, EvaluationFailure<Point3>> {
let scratch = Scratch::new(admission);
let result = super::curve_point_evaluation(&scratch, geometry, parameter);
scratch.settle(result)
}
pub fn surface_point_solved<'ctx, 'arena: 'ctx>(
admission: impl Into<EvaluationAdmission<'ctx, 'arena>>,
geometry: &SolvedSurfaceGeometry,
u: f64,
v: f64,
) -> Result<FinitePoint3, EvaluationFailure<Point3>> {
let scratch = Scratch::new(admission);
let result = super::surface_point_evaluation(&scratch, geometry, u, v);
scratch.settle(result)
}
pub fn nurbs_surface_point<'ctx, 'arena: 'ctx>(
admission: impl Into<EvaluationAdmission<'ctx, 'arena>>,
surface: &crate::geometry::nurbs::NurbsSurface,
u: f64,
v: f64,
) -> Result<FinitePoint3, EvaluationFailure<Point3>> {
let scratch = Scratch::new(admission);
let result = scratch
.admission
.independent_cost(super::nurbs_surface_evaluation_cost(surface))
.and_then(|()| super::nurbs_surface_local(&scratch, surface, u, v))
.map(|local| {
let [point_x, point_y, point_z] = local.point;
FinitePoint3::from_coordinates(point_x, point_y, point_z)
});
scratch.settle(result)
}