use super::intercept_scan_edge_iterator::*;
use crate::geo::*;
use smallvec::*;
use std::ops::{Range};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ContourSize(pub usize, pub usize);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ContourPosition(pub usize, pub usize);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ContourCell(pub (crate) u8);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ContourEdge(pub (crate) usize);
pub trait SampledContour : Sized {
fn contour_size(&self) -> ContourSize;
fn intercepts_on_line(&self, y: f64) -> SmallVec<[Range<f64>; 4]>;
#[inline] fn edge_cell_iterator<'a>(&'a self) -> InterceptScanEdgeIterator<ContourInterceptsIterator<'a, Self>> {
InterceptScanEdgeIterator::from_iterator(ContourInterceptsIterator::new(self))
}
#[inline]
fn rounded_intercepts_on_line(&self, y: f64) -> SmallVec<[Range<usize>; 4]> {
let intercepts = self.intercepts_on_line(y)
.into_iter()
.map(|intercept| {
let min_x_ceil = intercept.start.ceil();
let max_x_ceil = intercept.end.ceil();
let min_x = min_x_ceil as usize;
let max_x = max_x_ceil as usize;
min_x..max_x
})
.filter(|intercept| intercept.start != intercept.end)
.collect::<SmallVec<_>>();
if intercepts.len() <= 1 {
intercepts
} else {
merge_overlapping_intercepts(intercepts)
}
}
}
impl<'a, T> SampledContour for &'a T
where
T: SampledContour,
{
#[inline] fn contour_size(&self) -> ContourSize { (*self).contour_size() }
#[inline] fn intercepts_on_line(&self, y: f64) -> SmallVec<[Range<f64>; 4]> { (*self).intercepts_on_line(y) }
#[inline] fn rounded_intercepts_on_line(&self, y: f64) -> SmallVec<[Range<usize>; 4]> { (*self).rounded_intercepts_on_line(y) }
}
#[inline]
pub (crate) fn merge_overlapping_intercepts(intercepts: SmallVec<[Range<usize>; 4]>) -> SmallVec<[Range<usize>; 4]> {
let mut intercepts = intercepts;
let mut idx = 0;
while idx < intercepts.len()-1 {
if intercepts[idx].end >= intercepts[idx+1].start {
intercepts[idx].end = intercepts[idx+1].end;
intercepts.remove(idx+1);
} else {
idx += 1;
}
}
intercepts
}
impl ContourCell {
#[inline]
pub const fn from_corners(tl: bool, tr: bool, bl: bool, br: bool) -> ContourCell {
let tl = if tl { 1 } else { 0 };
let tr = if tr { 2 } else { 0 };
let bl = if bl { 4 } else { 0 };
let br = if br { 8 } else { 0 };
ContourCell(tl | tr | bl | br)
}
#[inline]
pub const fn is_on_edge(&self) -> bool {
self.0 != 0 && self.0 != 15
}
#[inline]
pub const fn merge(self, cell: ContourCell) -> ContourCell {
ContourCell(self.0 | cell.0)
}
#[inline]
pub const fn shift_left(self) -> ContourCell {
ContourCell((self.0 >> 1) & !2)
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.0 == 0
}
#[inline]
pub const fn is_full(&self) -> bool {
self.0 == 15
}
}
impl ContourEdge {
#[inline] pub (crate) const fn top() -> ContourEdge { ContourEdge(1) }
#[inline] pub (crate) const fn left() -> ContourEdge { ContourEdge(0) }
#[inline] pub (crate) const fn bottom() -> ContourEdge { ContourEdge(3) } #[inline] pub (crate) const fn right() -> ContourEdge { ContourEdge(2) }
#[inline] pub (crate) const fn at_coordinates(self, size: ContourSize, pos: ContourPosition) -> ContourEdge {
let edge_width = size.0 + 1;
let offset = edge_width * pos.1 + pos.0;
let offset = match self.0 {
0 => offset, 1 => offset, 2 => offset + 1, 3 => offset + edge_width, _ => unreachable!()
};
let offset = (offset<<1) | (self.0&1);
ContourEdge(offset)
}
#[inline]
pub fn is_horizontal(self) -> bool {
(self.0&1) == 1
}
#[inline]
pub fn is_vertical(self) -> bool {
(self.0&1) != 1
}
#[inline]
pub fn to_contour_coords(self, size: ContourSize) -> (ContourPosition, ContourPosition) {
let edge_width = size.0 + 1;
let x = (self.0 >> 1) % edge_width;
let y = (self.0 >> 1) / edge_width;
if self.is_horizontal() {
(ContourPosition(x, y), ContourPosition(x+1, y))
} else {
(ContourPosition(x, y), ContourPosition(x, y+1))
}
}
#[inline]
pub fn to_coords<TCoord>(self, size: ContourSize) -> TCoord
where
TCoord: Coordinate + Coordinate2D,
{
let edge_width = size.0 + 1;
let x = (self.0 >> 1) % edge_width;
let y = (self.0 >> 1) / edge_width;
if (self.0&1) == 1 {
TCoord::from_components(&[x as f64 + 0.5, y as f64])
} else {
TCoord::from_components(&[x as f64, y as f64 + 0.5])
}
}
}
impl ContourPosition {
#[inline]
pub fn x(&self) -> usize { self.0 }
#[inline]
pub fn y(&self) -> usize { self.1 }
}
impl ContourSize {
#[inline]
pub fn width(&self) -> usize { self.0 }
#[inline]
pub fn height(&self) -> usize { self.1 }
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct BoolSampledContour(pub ContourSize, pub Vec<bool>);
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct U8SampledContour(pub ContourSize, pub Vec<u8>);
impl BoolSampledContour {
#[inline]
pub fn point_is_inside(&self, pos: ContourPosition) -> bool {
let idx = pos.0 + (pos.1) * self.0.0;
self.1[idx]
}
}
impl U8SampledContour {
#[inline]
fn point_is_inside(&self, pos: ContourPosition) -> bool {
let idx = pos.0 + (pos.1) * self.0.0;
self.1[idx] != 0
}
}
impl SampledContour for BoolSampledContour {
#[inline]
fn contour_size(&self) -> ContourSize {
self.0
}
fn intercepts_on_line(&self, y: f64) -> SmallVec<[Range<f64>; 4]> {
let width = self.contour_size().width();
let y = y.floor() as usize;
let mut ranges = smallvec![];
let mut inside = None;
for x in 0..width {
match (inside, self.point_is_inside(ContourPosition(x, y))) {
(None, true) => { inside = Some(x); },
(Some(start_x), false) => {
inside = None;
ranges.push((start_x as f64)..(x as f64));
}
_ => { }
}
}
if let Some(start_x) = inside {
ranges.push((start_x as f64)..(width as f64));
}
ranges
}
}
impl SampledContour for U8SampledContour {
#[inline]
fn contour_size(&self) -> ContourSize {
self.0
}
fn intercepts_on_line(&self, y: f64) -> SmallVec<[Range<f64>; 4]> {
let width = self.contour_size().width();
let y = y.floor() as usize;
let mut ranges = smallvec![];
let mut inside = None;
for x in 0..width {
match (inside, self.point_is_inside(ContourPosition(x, y))) {
(None, true) => { inside = Some(x); },
(Some(start_x), false) => {
inside = None;
ranges.push((start_x as f64)..(x as f64));
}
_ => { }
}
}
if let Some(start_x) = inside {
ranges.push((start_x as f64)..(width as f64));
}
ranges
}
}
pub fn contour_point_is_inside(contour: &impl SampledContour, pos: ContourPosition) -> bool {
let size = contour.contour_size();
let x = pos.x() as f64;
let y = pos.y() as f64;
let width = size.width() as f64;
if x >= width {
return false;
}
for intercept in contour.intercepts_on_line(y) {
if intercept.start <= x && intercept.end > x {
return true;
}
if intercept.start > x {
return false;
}
}
false
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn left_at_coordinate() {
let size = ContourSize(80, 80);
let left = ContourEdge::left();
let at_coord = left.at_coordinates(size, ContourPosition(7, 8));
let (start, end) = at_coord.to_contour_coords(size);
assert!(start == ContourPosition(7, 8), "Start doesn't match {:?} {:?}", start, end);
assert!(end == ContourPosition(7, 9), "End doesn't match {:?} {:?}", start, end);
}
#[test]
fn right_at_coordinate() {
let size = ContourSize(80, 80);
let right = ContourEdge::right();
let at_coord = right.at_coordinates(size, ContourPosition(7, 8));
let (start, end) = at_coord.to_contour_coords(size);
assert!(start == ContourPosition(8, 8), "Start doesn't match {:?} {:?}", start, end);
assert!(end == ContourPosition(8, 9), "End doesn't match {:?} {:?}", start, end);
}
#[test]
fn top_at_coordinate() {
let size = ContourSize(80, 80);
let top = ContourEdge::top();
let at_coord = top.at_coordinates(size, ContourPosition(7, 8));
let (start, end) = at_coord.to_contour_coords(size);
assert!(start == ContourPosition(7, 8), "Start doesn't match {:?} {:?}", start, end);
assert!(end == ContourPosition(8, 8), "End doesn't match {:?} {:?}", start, end);
}
#[test]
fn bottom_at_coordinate() {
let size = ContourSize(80, 80);
let bottom = ContourEdge::bottom();
let at_coord = bottom.at_coordinates(size, ContourPosition(7, 8));
let (start, end) = at_coord.to_contour_coords(size);
assert!(start == ContourPosition(7, 9), "Start doesn't match {:?} {:?}", start, end);
assert!(end == ContourPosition(8, 9), "End doesn't match {:?} {:?}", start, end);
}
}