use super::{Edited, Kept, Lanes, Pole, Source, Weighted, from_homogeneous};
use crate::Point;
use crate::curve_math::bspline::{KnotVector, MAX_DEGREE};
use crate::curves::KnotRemovalError;
pub(crate) struct Removed {
pub(crate) knots: KnotVector,
pub(crate) points: Vec<Point>,
pub(crate) weights: Option<Vec<f64>>,
pub(crate) count: usize,
}
pub(crate) fn check_removal(
direction: &KnotVector,
remove: &[f64],
max_deviation: f64,
) -> Result<(), KnotRemovalError> {
if !(max_deviation.is_finite() && max_deviation >= 0.0) {
return Err(KnotRemovalError::InvalidDeviation);
}
if remove.iter().any(|x| !x.is_finite()) {
return Err(KnotRemovalError::NonFiniteKnot);
}
let (first, last) = direction.bounds();
let knots = direction.knots();
for &x in remove {
let inside = if direction.is_periodic() {
first <= x && x <= last
} else {
first < x && x < last
};
if !inside {
return Err(KnotRemovalError::KnotOutOfRange);
}
let i = knots.partition_point(|&k| k < x);
if i == knots.len() || knots[i] != x {
return Err(KnotRemovalError::KnotNotFound);
}
}
Ok(())
}
pub(crate) fn remove_knots(
direction: &KnotVector,
points: &[Point],
weights: Option<&[f64]>,
lanes: Lanes,
remove: &[f64],
max_deviation: f64,
) -> Option<Removed> {
match weights {
None => {
let removal = run(direction, points.to_vec(), lanes, remove, max_deviation)?;
Some(Removed {
knots: removal.knots,
points: removal.poles,
weights: None,
count: removal.count,
})
}
Some(weights) => {
let source = Weighted { points, weights };
let mut poles = Vec::with_capacity(points.len());
source.extend_to(0..points.len(), &mut poles);
let removal = run(direction, poles, lanes, remove, max_deviation)?;
let edited = Edited {
knots: removal.knots,
poles: removal.poles,
kept: removal.kept,
start: 0,
};
let (points, weights) = from_homogeneous(&edited, points, weights, lanes);
Some(Removed {
knots: edited.knots,
points,
weights: Some(weights),
count: removal.count,
})
}
}
}
trait Deviation: Pole {
fn bound(
diffs: &[Self],
coefficient: f64,
centre: Self,
reach: impl Iterator<Item = Self>,
max: f64,
) -> Option<f64>;
fn is_valid(self) -> bool;
}
impl Deviation for Point {
#[inline]
fn bound(
diffs: &[Self],
coefficient: f64,
_: Self,
_: impl Iterator<Item = Self>,
_: f64,
) -> Option<f64> {
let squared = diffs
.iter()
.map(|d| d.x * d.x + d.y * d.y + d.z * d.z)
.fold(0.0, f64::max);
squared.is_finite().then(|| coefficient * squared.sqrt())
}
#[inline]
fn is_valid(self) -> bool {
true
}
}
impl Deviation for [f64; 4] {
fn bound(
diffs: &[Self],
coefficient: f64,
centre: Self,
reach: impl Iterator<Item = Self>,
max: f64,
) -> Option<f64> {
let weight = centre[3];
let c = [centre[0] / weight, centre[1] / weight, centre[2] / weight];
let length_squared = |[x, y, z, w]: [f64; 4]| {
let (dx, dy, dz) = (x - c[0] * w, y - c[1] * w, z - c[2] * w);
dx * dx + dy * dy + dz * dz
};
let squared = diffs
.iter()
.map(|&d| length_squared(d) + d[3] * d[3])
.fold(0.0, f64::max);
let e = coefficient * squared.sqrt();
if !(e < weight && e <= max * (weight - e)) {
return None;
}
let (mut min_weight, mut radius_squared) = (weight, 0.0_f64);
for pole in reach {
let w = pole[3];
min_weight = min_weight.min(w);
radius_squared = radius_squared.max(length_squared(pole) / (w * w));
}
let bound = if e < min_weight {
e * (1.0 + radius_squared).sqrt() / (min_weight - e)
} else {
f64::INFINITY
};
(bound <= max).then_some(bound)
}
#[inline]
fn is_valid(self) -> bool {
self[3] > 0.0
}
}
struct Run<P> {
knots: KnotVector,
poles: Vec<P>,
kept: Kept,
count: usize,
}
fn run<P: Deviation>(
direction: &KnotVector,
poles: Vec<P>,
lanes: Lanes,
remove: &[f64],
max: f64,
) -> Option<Run<P>> {
let (rows, width) = lanes.rows_and_width();
let (first, last) = direction.bounds();
let flat = direction.flat().to_vec();
let mut removal = Removal {
degree: direction.degree(),
periodic: direction.is_periodic(),
rows,
width,
first,
last,
seam: flat.iter().take_while(|&&k| k == flat[0]).count(),
n: direction.pole_count(),
flat,
poles,
bounds: Vec::new(),
scratch: vec![P::default(); rows * (direction.degree() + 1) * width],
diffs: vec![P::default(); lanes.count],
spare: Vec::new(),
changed: None,
};
let mut count = 0;
let mut failed = f64::NAN;
for (i, &u) in remove.iter().enumerate() {
if u == failed {
continue;
}
match removal.solve(u, max) {
Some(solved) => {
removal.commit(&solved, i + 1 < remove.len());
count += 1;
failed = f64::NAN;
}
None => failed = u,
}
}
let changed = removal.changed?;
let kept = Kept {
leading: changed.start,
trailing: removal.n - changed.end,
};
let knots = KnotVector::from_flat(removal.degree, removal.flat, removal.periodic);
Some(Run {
knots,
poles: removal.poles,
kept,
count,
})
}
struct Removal<P> {
degree: usize,
periodic: bool,
rows: usize,
width: usize,
first: f64,
last: f64,
seam: usize,
n: usize,
flat: Vec<f64>,
poles: Vec<P>,
bounds: Vec<f64>,
scratch: Vec<P>,
diffs: Vec<P>,
spare: Vec<P>,
changed: Option<std::ops::Range<usize>>,
}
struct Solved {
r: isize,
c: usize,
s: usize,
bound: f64,
start: isize,
count: usize,
}
impl<P: Deviation> Removal<P> {
#[inline]
fn knot(&self, k: isize) -> f64 {
if self.periodic {
let n = self.n as isize;
self.flat[k.rem_euclid(n) as usize] + (self.last - self.first) * k.div_euclid(n) as f64
} else {
self.flat[k as usize]
}
}
#[inline]
fn slot(&self, f: isize) -> usize {
if self.periodic {
let shift = self.degree as isize + 1 - self.seam as isize;
(f + shift).rem_euclid(self.n as isize) as usize
} else {
f as usize
}
}
#[inline]
fn poles_at(&self, row: usize, f: isize) -> &[P] {
&self.poles[(row * self.n + self.slot(f)) * self.width..][..self.width]
}
fn solve(&mut self, u: f64, max: f64) -> Option<Solved> {
let (p, n, width) = (self.degree, self.n, self.width);
let (u, c, s, kept) = if self.periodic && (u == self.first || u == self.last) {
(self.first, 0, self.seam, 1)
} else {
let c = self.flat.partition_point(|&k| k < u);
let s = self.flat[c..].iter().take_while(|&&k| k == u).count();
(u, c, s, 0)
};
if s <= kept || (self.periodic && n < p - s + 2) {
return None;
}
let w = p - s;
let r = (c + s - 1) as isize;
let (a, b) = (r - p as isize, r - s as isize);
let mut alpha = [0.0; MAX_DEGREE + 1];
for (k, i) in (a..=b).enumerate() {
let left = self.knot(i);
alpha[k] = (u - left) / (self.knot(i + p as isize + 1) - left);
}
let run_slots = w + 2;
let steps = w / 2;
let lo = steps;
let odd = w % 2 == 1;
let start = a + lo as isize;
let count = p + 1 + usize::from(odd);
let (from, to) = (start - p as isize, start + count as isize - 1);
let reach = if self.periodic {
from..=to
} else {
from.max(0)..=to.min(n as isize - 1)
};
let (al, ah) = (alpha[lo], alpha[lo + 1]);
let lambda = al / (al + 1.0 - ah);
let coefficient = if odd {
al * (1.0 - ah) / (al + 1.0 - ah)
} else {
1.0
};
let (left_coefficient, right_coefficient) = (1.0 / al, 1.0 / (1.0 - ah));
let mut scratch = std::mem::take(&mut self.scratch);
let mut diffs = std::mem::take(&mut self.diffs);
for row in 0..self.rows {
let run = &mut scratch[row * run_slots * width..][..run_slots * width];
run[..width].copy_from_slice(self.poles_at(row, a - 1));
run[(w + 1) * width..].copy_from_slice(self.poles_at(row, b + 1));
for k in 0..steps {
let (i, j) = (k, w - k);
let (x, from) = (alpha[i], self.poles_at(row, a + i as isize));
let (y, inverse) = (1.0 - x, 1.0 / x);
let (prev, out) = run[i * width..(i + 2) * width].split_at_mut(width);
for ((out, &prev), &from) in out.iter_mut().zip(&*prev).zip(from) {
*out = from.combine(inverse, prev, -y * inverse);
}
let (x, from) = (alpha[j], self.poles_at(row, a + j as isize));
let inverse = 1.0 / (1.0 - x);
let (out, next) = run[j * width..(j + 2) * width].split_at_mut(width);
for ((out, &next), &from) in out.iter_mut().zip(&*next).zip(from) {
*out = from.combine(inverse, next, -x * inverse);
}
}
let (low, high) = (self.poles_at(row, start), self.poles_at(row, start + 1));
let row_diffs = &mut diffs[row * width..][..width];
for lane in 0..width {
let prev = run[lo * width + lane];
row_diffs[lane] = if odd {
let next = run[(lo + 2) * width + lane];
let left =
low[lane].combine(left_coefficient, prev, -(1.0 - al) * left_coefficient);
let right =
high[lane].combine(right_coefficient, next, -ah * right_coefficient);
run[(lo + 1) * width + lane] = left.combine(lambda, right, 1.0 - lambda);
left.combine(1.0, right, -1.0)
} else {
let mid = run[(lo + 1) * width + lane];
low[lane].combine(1.0, mid.combine(al, prev, 1.0 - al), -1.0)
};
}
if !run[width..(w + 1) * width]
.iter()
.all(|&pole| pole.is_valid())
{
self.scratch = scratch;
self.diffs = diffs;
return None;
}
}
let this = &*self;
let reach_poles = reach.flat_map(|f| {
(0..this.rows).flat_map(move |row| this.poles_at(row, f).iter().copied())
});
let bound = P::bound(
&diffs,
coefficient,
this.poles_at(0, start)[0],
reach_poles,
max,
);
self.scratch = scratch;
self.diffs = diffs;
let bound = bound?;
let fits = if self.bounds.is_empty() {
bound <= max
} else {
self.intervals(start, count)
.all(|k| self.bounds[k] + bound <= max)
};
fits.then_some(Solved {
r,
c,
s,
bound,
start,
count,
})
}
fn intervals(&self, start: isize, count: usize) -> impl Iterator<Item = usize> + use<P> {
let (modulus, start, count) = if self.periodic {
let n = self.n;
(n, start.rem_euclid(n as isize) as usize, count.min(n))
} else {
(usize::MAX, start as usize, count)
};
(start..start + count).map(move |k| k % modulus)
}
fn commit(&mut self, solved: &Solved, more: bool) {
let Solved {
r,
c,
s,
bound,
start,
count,
} = *solved;
let (p, width, n) = (self.degree, self.width, self.n);
let w = p - s;
if more && self.bounds.is_empty() {
let intervals = if self.periodic {
n
} else {
self.flat.len() - 1
};
self.bounds = vec![0.0; intervals];
}
if !self.bounds.is_empty() {
for k in self.intervals(start, count) {
self.bounds[k] += bound;
}
let before = if self.periodic {
(c + n - 1) % n
} else {
c - 1
};
self.bounds[before] = self.bounds[before].max(self.bounds[c]);
self.bounds.remove(c);
}
if self.periodic {
self.rebuild_periodic(r, c, s);
self.changed = Some(0..n - 1);
} else {
let a = (r - p as isize) as usize;
let run_slots = w + 2;
for row in 0..self.rows {
let (from, to) = (row * n * width, row * (n - 1) * width);
if from != to {
self.poles.copy_within(from..from + a * width, to);
}
self.poles[to + a * width..][..w * width]
.copy_from_slice(&self.scratch[(row * run_slots + 1) * width..][..w * width]);
self.poles.copy_within(
from + (a + w + 1) * width..from + n * width,
to + (a + w) * width,
);
}
self.poles.truncate(self.rows * (n - 1) * width);
self.changed = Some(match self.changed.take() {
None => a..a + w,
Some(changed) => {
let end = if changed.end > a {
changed.end - 1
} else {
changed.end
};
changed.start.min(a)..end.max(a + w)
}
});
}
if self.periodic && c == 0 {
self.flat.pop();
self.flat.remove(0);
self.seam -= 1;
} else {
self.flat.remove(c);
}
self.n -= 1;
}
fn rebuild_periodic(&mut self, r: isize, c: usize, s: usize) {
let (p, width, n) = (self.degree as isize, self.width, self.n);
let (new_n, new_seam) = (n - 1, self.seam - usize::from(c == 0));
let run_slots = p as usize - s + 2;
let (a, b) = (r - p, r - s as isize);
let mut next = std::mem::take(&mut self.spare);
next.clear();
next.resize(self.rows * new_n * width, P::default());
let base = a - 1;
for slot in 0..new_n {
let f = base
+ (slot as isize + new_seam as isize - p - 1 - base).rem_euclid(new_n as isize);
for row in 0..self.rows {
let to = &mut next[(row * new_n + slot) * width..][..width];
if a <= f && f < b {
let at = (row * run_slots + (f - base) as usize) * width;
to.copy_from_slice(&self.scratch[at..][..width]);
} else {
to.copy_from_slice(self.poles_at(row, if f < a { f } else { f + 1 }));
}
}
}
self.spare = std::mem::replace(&mut self.poles, next);
}
}