mod bspline;
mod elementary;
#[cfg(doc)]
use crate::Tolerance;
use crate::curves::{ParametricCurve, ParametricCurve2D, TrimmedCurve, TrimmedCurve2D};
use crate::{BSplineCurve, Circle, Circle2D, Ellipse, Point, Point2D};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiscretizationCriteriaError {
InvalidChordDeviation,
InvalidAngleDeviation,
}
impl fmt::Display for DiscretizationCriteriaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
DiscretizationCriteriaError::InvalidChordDeviation => {
"the maximum chord deviation must be positive and finite"
}
DiscretizationCriteriaError::InvalidAngleDeviation => {
"the maximum angle deviation must be positive and finite"
}
})
}
}
impl std::error::Error for DiscretizationCriteriaError {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DiscretizationCriteria {
max_chord_deviation: f64,
max_angle_deviation: Option<f64>,
}
impl DiscretizationCriteria {
pub fn new(max_chord_deviation: f64) -> Result<Self, DiscretizationCriteriaError> {
if !(max_chord_deviation.is_finite() && max_chord_deviation > 0.0) {
return Err(DiscretizationCriteriaError::InvalidChordDeviation);
}
Ok(DiscretizationCriteria {
max_chord_deviation,
max_angle_deviation: None,
})
}
pub fn with_max_angle_deviation(
self,
max_angle_deviation: f64,
) -> Result<Self, DiscretizationCriteriaError> {
if !(max_angle_deviation.is_finite() && max_angle_deviation > 0.0) {
return Err(DiscretizationCriteriaError::InvalidAngleDeviation);
}
Ok(DiscretizationCriteria {
max_angle_deviation: Some(max_angle_deviation),
..self
})
}
pub fn max_chord_deviation(&self) -> f64 {
self.max_chord_deviation
}
pub fn max_angle_deviation(&self) -> Option<f64> {
self.max_angle_deviation
}
fn angle_limit(&self) -> Option<f64> {
self.max_angle_deviation
.filter(|&a| a < std::f64::consts::PI)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct Discretization {
pub parameters: Vec<f64>,
pub points: Vec<Point>,
pub chord_deviations: Vec<f64>,
pub angle_deviations: Vec<f64>,
}
pub trait Discretize {
fn point_count(&self, criteria: DiscretizationCriteria) -> usize;
fn discretize_into(
&self,
criteria: DiscretizationCriteria,
parameters: &mut [f64],
points: &mut [Point],
chord_deviations: &mut [f64],
angle_deviations: &mut [f64],
);
fn discretize(&self, criteria: DiscretizationCriteria) -> Discretization {
let n = self.point_count(criteria);
let mut d = Discretization {
parameters: vec![0.0; n],
points: vec![Point::ORIGIN; n],
chord_deviations: vec![0.0; n - 1],
angle_deviations: vec![0.0; n - 1],
};
self.discretize_into(
criteria,
&mut d.parameters,
&mut d.points,
&mut d.chord_deviations,
&mut d.angle_deviations,
);
d
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct Discretization2D {
pub parameters: Vec<f64>,
pub points: Vec<Point2D>,
pub chord_deviations: Vec<f64>,
pub angle_deviations: Vec<f64>,
}
pub trait Discretize2D {
fn point_count(&self, criteria: DiscretizationCriteria) -> usize;
fn discretize_into(
&self,
criteria: DiscretizationCriteria,
parameters: &mut [f64],
points: &mut [Point2D],
chord_deviations: &mut [f64],
angle_deviations: &mut [f64],
);
fn discretize(&self, criteria: DiscretizationCriteria) -> Discretization2D {
let n = self.point_count(criteria);
let mut d = Discretization2D {
parameters: vec![0.0; n],
points: vec![Point2D::ORIGIN; n],
chord_deviations: vec![0.0; n - 1],
angle_deviations: vec![0.0; n - 1],
};
self.discretize_into(
criteria,
&mut d.parameters,
&mut d.points,
&mut d.chord_deviations,
&mut d.angle_deviations,
);
d
}
}
pub(crate) mod sealed {
use super::DiscretizationCriteria;
#[derive(Debug, Clone, Copy)]
pub struct Run {
pub(crate) first: f64,
pub(crate) last: f64,
pub(crate) segments: usize,
pub(crate) chord_deviation: f64,
pub(crate) angle_deviation: f64,
}
pub trait DiscretizeRange {
type Point: Copy;
fn for_each_run<F>(
&self,
first: f64,
last: f64,
criteria: &DiscretizationCriteria,
min_segments: usize,
visit: F,
) where
F: FnMut(Run, &dyn Fn(f64) -> Self::Point);
}
}
use sealed::{DiscretizeRange, Run};
fn plan<C: DiscretizeRange + ?Sized>(
curve: &C,
(first, last): (f64, f64),
closed: bool,
criteria: &DiscretizationCriteria,
) -> (usize, usize) {
let count = |min_segments| {
let (mut runs, mut segments) = (0usize, 0usize);
curve.for_each_run(first, last, criteria, min_segments, |run, _| {
runs += 1;
segments = segments.saturating_add(run.segments);
});
(runs, segments)
};
let (runs, segments) = count(1);
if closed && segments < 3 {
let min_segments = 3usize.div_ceil(runs);
(count(min_segments).1 + 1, min_segments)
} else {
(segments.saturating_add(1), 1)
}
}
struct Slices<'a, P> {
parameters: &'a mut [f64],
points: &'a mut [P],
chord_deviations: &'a mut [f64],
angle_deviations: &'a mut [f64],
}
impl<P> Slices<'_, P> {
fn check_lengths(&self, n: usize) {
assert_eq!(self.parameters.len(), n, "parameters: wrong length");
assert_eq!(self.points.len(), n, "points: wrong length");
let segments = n - 1;
let chords = self.chord_deviations.len();
assert_eq!(chords, segments, "chord deviations: wrong length");
let angles = self.angle_deviations.len();
assert_eq!(angles, segments, "angle deviations: wrong length");
}
}
fn fill<C: DiscretizeRange + ?Sized>(
curve: &C,
(first, last): (f64, f64),
closed: bool,
criteria: &DiscretizationCriteria,
point_at_parameter: impl Fn(f64) -> C::Point,
out: Slices<'_, C::Point>,
) {
let (n, min_segments) = plan(curve, (first, last), closed, criteria);
out.check_lengths(n);
let mut i = 0;
curve.for_each_run(first, last, criteria, min_segments, |run, point_at| {
let step = 1.0 / run.segments as f64;
for j in 0..run.segments {
let s = j as f64 * step;
out.parameters[i] = run.first + (run.last - run.first) * s;
out.points[i] = point_at(s);
out.chord_deviations[i] = run.chord_deviation;
out.angle_deviations[i] = run.angle_deviation;
i += 1;
}
});
out.parameters[0] = first;
out.parameters[n - 1] = last;
out.points[0] = point_at_parameter(first);
out.points[n - 1] = if closed {
out.points[0]
} else {
point_at_parameter(last)
};
}
type Buffers<P> = (Vec<f64>, Vec<P>, Vec<f64>, Vec<f64>);
fn collect<C: DiscretizeRange + ?Sized>(
curve: &C,
(first, last): (f64, f64),
closed: bool,
criteria: &DiscretizationCriteria,
point_at_parameter: impl Fn(f64) -> C::Point,
) -> Buffers<C::Point> {
let gather = |min_segments, out: &mut Buffers<C::Point>| {
let (parameters, points, chord_deviations, angle_deviations) = out;
parameters.clear();
points.clear();
chord_deviations.clear();
angle_deviations.clear();
let mut runs = 0usize;
curve.for_each_run(first, last, criteria, min_segments, |run, point_at| {
runs += 1;
let step = 1.0 / run.segments as f64;
for j in 0..run.segments {
let s = j as f64 * step;
parameters.push(run.first + (run.last - run.first) * s);
points.push(point_at(s));
}
chord_deviations.extend(std::iter::repeat_n(run.chord_deviation, run.segments));
angle_deviations.extend(std::iter::repeat_n(run.angle_deviation, run.segments));
});
runs
};
let mut out = Default::default();
let runs = gather(1, &mut out);
if closed && out.2.len() < 3 {
gather(3usize.div_ceil(runs), &mut out);
}
let (mut parameters, mut points, chord_deviations, angle_deviations) = out;
let start = point_at_parameter(first);
parameters.push(last);
points.push(start);
let n = points.len();
parameters[0] = first;
points[0] = start;
parameters[n - 1] = last;
if !closed {
points[n - 1] = point_at_parameter(last);
}
(parameters, points, chord_deviations, angle_deviations)
}
fn smallest_count(guess: f64, fits: impl Fn(usize) -> bool) -> usize {
let start = (guess.ceil() as usize).max(1);
let (mut low, mut high) = (0, start);
let mut step = 1usize;
if fits(start) {
while high > 1 {
let below = high.saturating_sub(step).max(1);
if !fits(below) {
low = below;
break;
}
high = below;
step = step.saturating_mul(2);
}
} else {
low = start;
loop {
if low == usize::MAX {
return usize::MAX;
}
let above = low.saturating_add(step);
if fits(above) {
high = above;
break;
}
low = above;
step = step.saturating_mul(2);
}
}
while high - low > 1 {
let middle = low + (high - low) / 2;
if fits(middle) {
high = middle;
} else {
low = middle;
}
}
high
}
macro_rules! discretize_bounded {
($discretize:ident, $discretization:ident, $point:ty: $($curve:ty),*) => {$(
impl $discretize for $curve {
fn discretize(&self, criteria: DiscretizationCriteria) -> $discretization {
let (parameters, points, chord_deviations, angle_deviations) = collect(
self,
self.bounds(),
self.is_periodic(),
&criteria,
|u| self.eval_point(u),
);
$discretization {
parameters,
points,
chord_deviations,
angle_deviations,
}
}
fn point_count(&self, criteria: DiscretizationCriteria) -> usize {
plan(self, self.bounds(), self.is_periodic(), &criteria).0
}
fn discretize_into(
&self,
criteria: DiscretizationCriteria,
parameters: &mut [f64],
points: &mut [$point],
chord_deviations: &mut [f64],
angle_deviations: &mut [f64],
) {
fill(
self,
self.bounds(),
self.is_periodic(),
&criteria,
|u| self.eval_point(u),
Slices {
parameters,
points,
chord_deviations,
angle_deviations,
},
);
}
}
)*};
}
discretize_bounded!(Discretize, Discretization, Point: Circle, Ellipse, BSplineCurve);
discretize_bounded!(Discretize2D, Discretization2D, Point2D: Circle2D);
macro_rules! discretize_trimmed {
($discretize:ident, $discretization:ident, $point:ty, $trimmed:ident, $parametric:ident) => {
impl<C: DiscretizeRange<Point = $point> + $parametric> $discretize for $trimmed<C> {
fn discretize(&self, criteria: DiscretizationCriteria) -> $discretization {
let (parameters, points, chord_deviations, angle_deviations) = collect(
self.basis(),
self.bounds(),
spans_period(self.bounds(), self.basis().period()),
&criteria,
|u| self.basis().eval_point(u),
);
$discretization {
parameters,
points,
chord_deviations,
angle_deviations,
}
}
fn point_count(&self, criteria: DiscretizationCriteria) -> usize {
let closed = spans_period(self.bounds(), self.basis().period());
plan(self.basis(), self.bounds(), closed, &criteria).0
}
fn discretize_into(
&self,
criteria: DiscretizationCriteria,
parameters: &mut [f64],
points: &mut [$point],
chord_deviations: &mut [f64],
angle_deviations: &mut [f64],
) {
fill(
self.basis(),
self.bounds(),
spans_period(self.bounds(), self.basis().period()),
&criteria,
|u| self.basis().eval_point(u),
Slices {
parameters,
points,
chord_deviations,
angle_deviations,
},
);
}
}
};
}
fn spans_period((first, last): (f64, f64), period: Option<f64>) -> bool {
period.is_some_and(|period| last - first >= period * (1.0 - 1e-12))
}
discretize_trimmed!(
Discretize,
Discretization,
Point,
TrimmedCurve,
ParametricCurve
);
discretize_trimmed!(
Discretize2D,
Discretization2D,
Point2D,
TrimmedCurve2D,
ParametricCurve2D
);
impl<C: DiscretizeRange + ?Sized> DiscretizeRange for &C {
type Point = C::Point;
fn for_each_run<F>(
&self,
first: f64,
last: f64,
criteria: &DiscretizationCriteria,
min_segments: usize,
visit: F,
) where
F: FnMut(Run, &dyn Fn(f64) -> Self::Point),
{
(**self).for_each_run(first, last, criteria, min_segments, visit);
}
}
#[deprecated(
since = "0.37.0",
note = "renamed `Discretization`: only 2D types carry a dimension suffix"
)]
pub type Discretization3D = Discretization;
#[doc(hidden)]
pub use Discretize as Discretize3D;