use std::marker::PhantomData;
use num_traits::real::Real;
use smallvec::{SmallVec, smallvec};
use crate::{FanFormat, FanBuilderState, PolygonList, PolygonListExt, TriangleWinding, VertexIndex, errors::{TriangulationError, InternalError}, math::is_left_of_line, FanBuilder, Coords};
pub(crate) struct MonotoneBuilder<Index: VertexIndex, C: Real> {
vec: SmallVec<[(Index, Coords<C>); 16]>,
diff_x: bool,
diff_y: bool,
}
impl<Index: VertexIndex, C: Real> MonotoneBuilder<Index, C> {
pub fn new(vi: Index, c: Coords<C>) -> Self {
Self {
vec: smallvec![(vi, c)],
diff_x: false,
diff_y: false,
}
}
pub fn add_vertex(&mut self, vi: Index, c: Coords<C>) {
if !self.diff_x && c.x() != self.vec[0].1.x() {
self.diff_x = true;
}
if !self.diff_y && c.y() != self.vec[0].1.y() {
self.diff_y = true;
}
self.vec.push((vi, c));
}
pub fn build(self) -> Result<Option<Monotone<Index, C>>, InternalError> {
if self.vec.len() < 3 {
return Err(InternalError::new(format!("Monotone needs at least 3 vertices, has {}", self.vec.len())));
}
if self.diff_x && self.diff_y {
let is_left_chain = is_left_of_line(self.vec[self.vec.len() - 1].1, self.vec[0].1, self.vec[1].1);
Ok(Some(Monotone::new(self.vec, is_left_chain)))
} else {
Ok(None)
}
}
}
pub struct Monotone<Index: VertexIndex, C: Real> {
pub(crate) skipped_and_pending: SmallVec<[(Index, Coords<C>); 16]>,
skipped_top: usize,
pending_top: usize,
is_left_chain: bool,
}
#[cfg(feature = "_debugging")]
impl<Index: VertexIndex, C: Real> std::fmt::Display for Monotone<Index, C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "is_left_chain: {}", self.is_left_chain)?;
write!(f, "[ ")?;
for i in 0..self.skipped_top {
write!(f, "{:03?} ", self.skipped_and_pending[i].0)?;
}
write!(f, "] ")?;
for i in self.skipped_top..self.pending_top {
write!(f, "{:03?} ", self.skipped_and_pending[i].0)?;
}
write!(f, "[ ")?;
for i in self.pending_top..self.skipped_and_pending.len() {
write!(f, "{:03?} ", self.skipped_and_pending[i].0)?;
}
write!(f, "]")
}
}
impl<Index: VertexIndex, C: Real> Monotone<Index, C> {
fn new(vertices: SmallVec<[(Index, Coords<C>); 16]>, is_left_chain: bool) -> Self {
Self {
skipped_and_pending: vertices,
skipped_top: 2,
pending_top: 2,
is_left_chain,
}
}
pub(crate) fn build_fans<'z, 'p, P: PolygonList<'p, Index=Index> + ?Sized, FB: FanFormat<'p, P>>(mut self, ps: PolygonListExt<'p, P>, fbs: &'z mut FanBuilderState<'p, P, FB>) -> Result<(), TriangulationError<<FB::Builder as FanBuilder<'p, P>>::Error>> {
enum BuilderOrDeferredTris<'z, 'p, P: PolygonList<'p> + ?Sized, FB: FanFormat<'p, P>> {
Builder(&'z mut FB::Builder),
DeferredTris(&'z mut FanBuilderState<'p, P, FB>, usize, PhantomData<&'p ()>),
}
impl<'z, 'p, P: PolygonList<'p> + ?Sized, FB: FanFormat<'p, P>> BuilderOrDeferredTris<'z, 'p, P, FB> {
fn add_triangle(&mut self, vi: P::Index) -> Result<(), <FB::Builder as FanBuilder<'p, P>>::Error> {
match self {
BuilderOrDeferredTris::Builder(fb) => fb.extend_fan(vi)?,
BuilderOrDeferredTris::DeferredTris(_, dt, _) => *dt += 1,
};
Ok(())
}
}
while self.remaining_vertices() >= 3 {
if self.can_triangulate() {
let vi1 = self.skipped_pop().0;
let mut vi0 = self.skipped_peek().0;
let mut vi2 = self.pending_peek().0;
let is_backtracking = self.can_triangulate();
let mut bodt: BuilderOrDeferredTris<'_, '_, P, FB> = if is_backtracking ^ self.is_left_chain ^ (FB::Builder::WINDING == TriangleWinding::Clockwise) {
if !self.is_left_chain {
std::mem::swap(&mut vi0, &mut vi2);
}
BuilderOrDeferredTris::Builder(fbs.new_fan(ps.polygon_list(), vi0, vi1, vi2)?)
} else {
BuilderOrDeferredTris::DeferredTris(fbs, 1, PhantomData)
};
if is_backtracking {
self.skipped_pop();
bodt.add_triangle(self.skipped_peek().0).map_err(TriangulationError::from)?;
while self.can_triangulate() {
self.skipped_pop();
bodt.add_triangle(self.skipped_peek().0).map_err(TriangulationError::from)?;
}
} else {
self.transfer_pending();
while self.can_triangulate() {
self.skipped_pop();
bodt.add_triangle(self.pending_peek().0).map_err(TriangulationError::from)?;
self.transfer_pending();
}
}
if let BuilderOrDeferredTris::DeferredTris(fbs, dt, _) = bodt {
let (vi0, vi1, vi2) = if is_backtracking {
(self.pending_peek().0, self.skipped_peek().0, self.deferred_index(is_backtracking, 0))
} else {
let ((vi0, _), (vi1, _)) = self.skipped_peek2();
(vi0, vi1, self.deferred_index(is_backtracking, 0))
};
let fb = fbs.new_fan(ps.polygon_list(), vi0, vi1, vi2)?;
for i in 1..dt {
if let Err(e) = fb.extend_fan(self.deferred_index(is_backtracking, i)) {
return Err(e.into());
}
}
}
}
if self.has_pending() {
self.transfer_pending();
} else {
self.reset_position();
}
}
let remaining_vertices = self.skipped_and_pending.len() - self.pending_top + self.skipped_top;
if remaining_vertices == 2 {
Ok(())
} else {
Err(TriangulationError::internal(format!("Expected 2 remaining vertices, found {remaining_vertices}")))
}
}
#[inline(always)]
fn skipped_peek(&self) -> (Index, Coords<C>) {
self.skipped_and_pending[self.skipped_top - 1].clone()
}
#[inline(always)]
fn skipped_peek2(&self) -> ((Index, Coords<C>), (Index, Coords<C>)) {
(self.skipped_and_pending[self.skipped_top - 2].clone(), self.skipped_and_pending[self.skipped_top - 1].clone())
}
#[inline(always)]
fn skipped_pop(&mut self) -> (Index, Coords<C>) {
self.skipped_top -= 1;
self.skipped_and_pending[self.skipped_top].clone()
}
#[inline(always)]
fn pending_peek(&self) -> (Index, Coords<C>) {
self.skipped_and_pending[self.pending_top].clone()
}
fn remaining_vertices(&self) -> usize {
self.skipped_and_pending.len() - self.pending_top + self.skipped_top
}
fn transfer_pending(&mut self) {
if self.skipped_top != self.pending_top {
self.skipped_and_pending.swap(self.skipped_top, self.pending_top);
}
self.skipped_top += 1;
self.pending_top += 1;
}
fn reset_position(&mut self) {
self.skipped_and_pending.truncate(self.skipped_top);
self.skipped_top = 2;
self.pending_top = 2;
}
fn has_pending(&self) -> bool { self.pending_top < self.skipped_and_pending.len() }
fn deferred_index(&self, is_backtracking: bool, i: usize) -> Index {
let index = if is_backtracking {
self.skipped_top + i
} else {
self.pending_top - 1 - i
};
self.skipped_and_pending[index].0.clone()
}
fn can_triangulate(&self) -> bool {
self.skipped_top >= 2 && self.has_pending() && {
let c_min = self.pending_peek().1;
let ((_, c_max), (_, c)) = self.skipped_peek2();
self.is_left_chain == is_left_of_line(c_min, c_max, c)
}
}
}