use std::borrow::Cow;
use super::{Count, Lanes, One, Pole, Source};
use crate::curve_math::bspline::{KnotVector, LARGE};
pub(crate) fn bezier_pieces<S: Source>(
knots: &KnotVector,
poles: S,
lanes: Lanes,
) -> (Vec<f64>, Vec<S::Pole>) {
let p = knots.degree();
let breaks = knots.breaks();
let mut out = vec![S::Pole::default(); (breaks.len() - 1) * (p + 1) * lanes.count];
if lanes.consecutive {
let lane_n = poles.len() / lanes.count;
let piece_len = (p + 1) * lanes.count;
let mut walker = Walker::new(knots, 1);
for lane in 0..lanes.count {
let lane_poles = poles.slice(lane * lane_n..(lane + 1) * lane_n);
walker.for_each_piece(lane_poles, One, |k, _, _, piece| {
let at = k * piece_len + lane * (p + 1);
out[at..at + p + 1].copy_from_slice(piece);
});
}
} else {
let w = lanes.count;
let piece_len = (p + 1) * w;
Walker::new(knots, w).for_each_piece(poles, w, |k, _, _, piece| {
out[k * piece_len..(k + 1) * piece_len].copy_from_slice(piece);
});
}
(breaks, out)
}
pub(crate) struct Walker<'a, P> {
degree: usize,
flat: Cow<'a, [f64]>,
period: Option<usize>,
scratch: Vec<P>,
unrolled: Vec<P>,
}
impl<'a, P: Pole> Walker<'a, P> {
pub(crate) fn new(knots: &'a KnotVector, width: usize) -> Walker<'a, P> {
let p = knots.degree();
let periodic = knots.is_periodic();
Walker {
degree: p,
flat: match periodic {
true => Cow::Owned(unroll(knots)),
false => Cow::Borrowed(knots.flat()),
},
period: periodic.then(|| knots.pole_count()),
scratch: vec![P::default(); 2 * (p + 1) * width],
unrolled: Vec::new(),
}
}
pub(crate) fn for_each_piece<S: Source<Pole = P>>(
&mut self,
poles: S,
width: impl Count,
mut visit: impl FnMut(usize, f64, f64, &[P]),
) {
let (p, flat, scratch) = (self.degree, &self.flat[..], &mut self.scratch[..]);
match self.period {
None => walk(p, flat, poles, width, scratch, |k, x, y, _, piece| {
visit(k, x, y, piece)
}),
Some(n) => {
let w = width.get();
self.unrolled.resize((flat.len() - p - 1) * w, P::default());
for (i, slot) in self.unrolled.chunks_exact_mut(w).enumerate() {
poles.copy_to((i % n) * w, slot);
}
let unrolled = &self.unrolled[..];
walk(p, flat, unrolled, width, scratch, |k, x, y, _, piece| {
visit(k, x, y, piece)
});
}
}
}
}
fn unroll(knots: &KnotVector) -> Vec<f64> {
let (p, n, flat) = (knots.degree(), knots.pole_count(), knots.flat());
let (first, last) = knots.bounds();
let period = last - first;
let seam = knots.multiplicities()[0] as isize;
let tiled = |j: isize| {
let (k, shift) = (j.rem_euclid(n as isize) as usize, j.div_euclid(n as isize));
match shift {
0 => flat[k],
1 if flat[k] == first => last,
_ => flat[k] + period * shift as f64,
}
};
let start = seam - 1 - p as isize;
(0..n + 2 * p + 1 - seam as usize + 1)
.map(|i| tiled(start + i as isize))
.collect()
}
pub(super) fn walk<S: Source>(
p: usize,
u: &[f64],
poles: S,
width: impl Count,
scratch: &mut [S::Pole],
mut visit: impl FnMut(usize, f64, f64, usize, &[S::Pole]),
) {
let w = width.get();
let n = poles.len() / w;
let (lo, hi) = (u[p], u[n]);
let mut s = p;
while u[s + 1] <= lo {
s += 1;
}
let (piece, next) = scratch[..2 * (p + 1) * w].split_at_mut((p + 1) * w);
poles.copy_to((s - p) * w, piece);
clamp_start(p, &u[s + 1 - p..s + 1 + p], piece, next, width);
let mut alphas = [0.0; LARGE];
let mut x_copies = 0;
for k in 0.. {
let (x, y) = (u[s], u[s + 1]);
let mut mult = 1;
while mult < p && u[s + 1 + mult] == y {
mult += 1;
}
let inserts = p - mult;
for (k, alpha) in alphas.iter_mut().enumerate().take(p).skip(mult) {
*alpha = (y - x) / (u[s + k + 1] - x);
}
for j in 1..=inserts {
for k in (mult + j..=p).rev() {
let a = alphas[k - j];
for c in 0..w {
piece[k * w + c] = piece[k * w + c].combine(a, piece[(k - 1) * w + c], 1.0 - a);
}
}
for c in 0..w {
next[(inserts - j) * w + c] = piece[p * w + c];
}
}
visit(k, x, y, x_copies, piece);
if y == hi {
return;
}
let s_next = s + mult;
piece[..inserts * w].copy_from_slice(&next[..inserts * w]);
poles.copy_to(s * w, &mut piece[inserts * w..]);
x_copies = mult;
s = s_next;
}
}
fn clamp_start<P: Pole>(
p: usize,
knots: &[f64],
piece: &mut [P],
spare: &mut [P],
width: impl Count,
) {
let w = width.get();
let x = knots[p - 1];
if knots[0] == x {
return;
}
for c in 0..w {
spare[p * w + c] = piece[p * w + c];
}
for r in 1..=p {
for i in (r..=p).rev() {
let a = (x - knots[i - 1]) / (knots[i + p - r] - knots[i - 1]);
for c in 0..w {
piece[i * w + c] = piece[(i - 1) * w + c].combine(1.0 - a, piece[i * w + c], a);
}
}
for c in 0..w {
spare[(p - r) * w + c] = piece[p * w + c];
}
}
piece.copy_from_slice(&spare[..piece.len()]);
}