summaryrefslogtreecommitdiffstats
path: root/svgbob/src/buffer/cell_buffer/cell.rs
diff options
context:
space:
mode:
Diffstat (limited to 'svgbob/src/buffer/cell_buffer/cell.rs')
-rw-r--r--svgbob/src/buffer/cell_buffer/cell.rs433
1 files changed, 433 insertions, 0 deletions
diff --git a/svgbob/src/buffer/cell_buffer/cell.rs b/svgbob/src/buffer/cell_buffer/cell.rs
new file mode 100644
index 0000000..ce2c85c
--- /dev/null
+++ b/svgbob/src/buffer/cell_buffer/cell.rs
@@ -0,0 +1,433 @@
+use crate::{util, Point};
+use ncollide2d::{
+ bounding_volume::AABB,
+ math::Isometry,
+ query::{proximity, Proximity},
+ shape::{Polyline, Segment},
+};
+use std::{cmp, cmp::Ordering, fmt};
+
+mod cell_grid;
+
+pub use cell_grid::CellGrid;
+
+/// ```ignore
+/// 0 1 2 3 4 B C D
+/// 0┌─┬─┬─┬─┐ A┌─┬─┬─┬─┐E
+/// 1├─┼─┼─┼─┤ │ │ │ │ │
+/// 2├─┼─┼─┼─┤ F├─G─H─I─┤J
+/// 3├─┼─┼─┼─┤ │ │ │ │ │
+/// 4├─┼─┼─┼─┤ K├─L─M─N─┤O
+/// 5├─┼─┼─┼─┤ │ │ │ │ │
+/// 6├─┼─┼─┼─┤ P├─Q─R─S─┤T
+/// 7├─┼─┼─┼─┤ │ │ │ │ │
+/// 8└─┴─┴─┴─┘ U└─┴─┴─┴─┘Y
+/// ``` V W X
+
+/// A single element in the terminal that
+/// can fit 1 character.
+/// Describe the exact location of a point/subcell in a grid.
+#[derive(Debug, PartialEq, Hash, PartialOrd, Clone, Copy)]
+pub struct Cell {
+ pub x: i32,
+ pub y: i32,
+}
+
+impl fmt::Display for Cell {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "({},{})", self.x, self.y)
+ }
+}
+
+impl Eq for Cell {}
+impl Ord for Cell {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.y.cmp(&other.y).then(self.x.cmp(&other.x))
+ }
+}
+
+macro_rules! cell_grid {
+ ($($a:ident),*) => {
+ /// The point at sepcific cell grid of this cell
+ $(pub fn $a(&self) -> Point {
+ self.top_left_most() + CellGrid::$a()
+ })*
+ }
+}
+
+impl Cell {
+ cell_grid!(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y);
+
+ pub fn new(x: i32, y: i32) -> Self {
+ Cell { x, y }
+ }
+
+ /// returns true if the other cell is at: top_left, top, top_right, left, right, bottom_left,
+ /// bottom, bottom_right of self
+ pub fn is_adjacent(&self, other: &Self) -> bool {
+ (other.x - self.x).abs() <= 1 && (other.y - self.y).abs() <= 1
+ }
+
+ /// Derive which cell this points falls into and snap the point closes to any
+ /// intersection in the cell grid.
+ /// FIXME: need to find a way to snap a group of point that lies in boundaries to
+ /// snap together to a common cell.
+ pub fn snap_point(point: Point) -> (Self, Point) {
+ let x = (point.x / Self::width()).floor();
+ let y = (point.y / Self::height()).floor();
+ let cell = Self::new(x as i32, y as i32);
+ let snap = Self::snap(cell.localize_point(point));
+ (cell, snap)
+ }
+
+ pub fn snap_group(points: &[Point]) -> Self {
+ let snaps: Vec<(Self, Point)> =
+ points.iter().map(|point| Self::snap_point(*point)).collect();
+ let (cells, _snap_points): (Vec<Self>, Vec<Point>) = snaps.into_iter().unzip();
+ let min_cell: Self = cells.into_iter().min().expect("should have a min cell");
+ min_cell
+ }
+
+ pub(in crate) fn absolute_position(&self, point: Point) -> Point {
+ self.top_left_most() + point
+ }
+
+ /// The point at the top right of this cell
+ pub fn top_left_most(&self) -> Point {
+ let px = self.x as f32 * CellGrid::width();
+ let py = self.y as f32 * CellGrid::height();
+ Point::new(px, py)
+ }
+
+ pub fn bottom_right_most(&self) -> Point {
+ let px = (self.x + 1) as f32 * CellGrid::width();
+ let py = (self.y + 1) as f32 * CellGrid::height();
+ Point::new(px, py)
+ }
+
+ /// turn point into relative distance from the top-left of this cell
+ /// by simply deducting the point p with this cell's top_left_most point
+ pub fn localize_point(&self, point: Point) -> Point {
+ point - self.top_left_most()
+ }
+
+ pub fn localize_cell(&self, cell: Cell) -> Cell {
+ Cell::new(cell.x - self.x, cell.y - self.y)
+ }
+
+ /// the bounding box of this cell
+ #[inline]
+ fn bounding_box(&self) -> AABB<f32> {
+ let start = Point::new(self.x as f32 * Self::width(), self.y as f32 * Self::height());
+ let end =
+ Point::new((self.x + 1) as f32 * Self::width(), (self.y + 1) as f32 * Self::height());
+ AABB::new(*start, *end)
+ }
+
+ /// Convert the bounding box aabb to polyline segment
+ /// the dots from top-left, top-right, bottom-right, bottom-left then closing to top-left
+ /// The polyline is then used to testing for intersection with the line segment
+ fn polyline(&self) -> Polyline<f32> {
+ let aabb = self.bounding_box();
+ let min = aabb.mins();
+ let max = aabb.maxs();
+ let x1 = min.x;
+ let y1 = min.y;
+ let x2 = max.x;
+ let y2 = max.y;
+ let c1 = Point::new(x1, y1); // top-left
+ let c2 = Point::new(x2, y1); // top-right
+ let c3 = Point::new(x2, y2); // bottom-right
+ let c4 = Point::new(x1, y2); // bottom-left
+ Polyline::new(vec![*c1, *c2, *c3, *c4, *c1], None)
+ }
+
+ pub fn width() -> f32 {
+ CellGrid::width()
+ }
+
+ pub fn height() -> f32 {
+ CellGrid::height()
+ }
+
+ pub fn unit(l: i32) -> f32 {
+ CellGrid::unit_x() * l as f32
+ }
+
+ /// test whether this cell is intersected with the line segment
+ /// with point `start` and `end`
+ pub fn is_intersected(&self, start: Point, end: Point) -> bool {
+ let pl = self.polyline();
+ let segment = Segment::new(*start, *end);
+ let prox = proximity(&Isometry::identity(), &pl, &Isometry::identity(), &segment, 0.0);
+ prox == Proximity::Intersecting
+ }
+
+ /// check if this cell is bounded by the lower bound and upper bound
+ pub fn is_bounded(&self, bound1: Cell, bound2: Cell) -> bool {
+ let (lower_bound, upper_bound) = rearrange_bound(bound1, bound2);
+ self.x >= lower_bound.x
+ && self.y >= lower_bound.y
+ && self.x <= upper_bound.x
+ && self.y <= upper_bound.y
+ }
+
+ /// snap a point closest to any of the intersection of this cellgrid
+ #[inline]
+ fn snap_xy(x: f32, y: f32) -> Point {
+ let tx = (x * 4.0).round() / 4.0;
+ let ty = (y * 8.0).round() / 8.0;
+ Point::new(tx, ty)
+ }
+
+ /// snap point to a closest intersection of this cellgrid
+ #[inline]
+ pub fn snap(p: Point) -> Point {
+ Self::snap_xy(p.x, p.y)
+ }
+
+ /// clip a line segment within the bounding box of this cell
+ fn clip_line(&self, start: Point, end: Point) -> Option<(Point, Point)> {
+ let aabb = self.bounding_box();
+ util::clip_line(&aabb, start, end)
+ }
+
+ pub fn clip_line_snap(&self, start: Point, end: Point) -> Option<(Point, Point)> {
+ self.clip_line(start, end).map(|(s, e)| (Self::snap(s), Self::snap(e)))
+ }
+
+ /// clip line then localize the points and snap to the nearest cell grid intersection
+ pub fn clip_line_localize(&self, start: Point, end: Point) -> Option<(Point, Point)> {
+ self.clip_line_snap(start, end)
+ .map(|(s, e)| (self.localize_point(s), self.localize_point(e)))
+ }
+
+ /// The cell at the top left of this cell
+ #[inline]
+ pub fn top_left(&self) -> Self {
+ Cell { x: self.x - 1, y: self.y - 1 }
+ }
+
+ #[inline]
+ pub fn top(&self) -> Self {
+ Cell { x: self.x, y: self.y - 1 }
+ }
+
+ #[inline]
+ pub fn top_right(&self) -> Self {
+ Cell { x: self.x + 1, y: self.y - 1 }
+ }
+
+ /// The cell at the left of this cell
+ #[inline]
+ pub fn left(&self) -> Self {
+ Cell { x: self.x - 1, y: self.y }
+ }
+
+ #[inline]
+ pub fn right(&self) -> Self {
+ Cell { x: self.x + 1, y: self.y }
+ }
+
+ #[inline]
+ pub fn bottom_left(&self) -> Self {
+ Cell { x: self.x - 1, y: self.y + 1 }
+ }
+
+ #[inline]
+ pub fn bottom(&self) -> Self {
+ Cell { x: self.x, y: self.y + 1 }
+ }
+
+ #[inline]
+ pub fn bottom_right(&self) -> Self {
+ Cell { x: self.x + 1, y: self.y + 1 }
+ }
+}
+
+
+/// rearrange the bound of 2 cells
+pub fn rearrange_bound(bound1: Cell, bound2: Cell) -> (Cell, Cell) {
+ let min_x = cmp::min(bound1.x, bound2.x);
+ let min_y = cmp::min(bound1.y, bound2.y);
+ let max_x = cmp::max(bound1.x, bound2.x);
+ let max_y = cmp::max(bound1.y, bound2.y);
+ (Cell::new(min_x, min_y), Cell::new(max_x, max_y))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ extern crate test;
+ use test::{black_box, Bencher};
+
+ #[bench]
+ fn bench_adjacent(bencher: &mut Bencher) {
+ bencher.iter(|| {
+ for i in 0..1000 {
+ for j in 0..1000 {
+ let cell1 = Cell::new(i, j);
+ let cell2 = Cell::new(i + 1, j + 1);
+ black_box(cell1.is_adjacent(&cell2));
+ }
+ }
+ })
+ }
+
+ #[test]
+ fn test_bottom_right() {
+ let cell = Cell::new(2, 0);
+ assert_eq!(cell.bottom_right(), Cell::new(3, 1));
+ }
+
+ #[test]
+ fn test_adjacent() {
+ let cell = Cell::new(4, 4);
+ let top = Cell::new(4, 3);
+ let top2 = Cell::new(4, 2);
+ assert!(cell.is_adjacent(&top));
+ assert!(top.is_adjacent(&cell));
+ assert!(cell.is_adjacent(&cell.top_left()));
+ assert!(cell.is_adjacent(&cell.top()));
+ assert!(cell.is_adjacent(&cell.top_right()));
+ assert!(cell.is_adjacent(&cell.left()));
+ assert!(cell.is_adjacent(&cell.right()));
+ assert!(cell.is_adjacent(&cell.bottom_left()));
+ assert!(cell.is_adjacent(&cell.bottom()));
+ assert!(cell.is_adjacent(&cell.bottom_right()));
+
+ assert!(!cell.is_adjacent(&top2));
+ assert!(!cell.is_adjacent(&cell.top_left().top()));
+ assert!(cell.is_adjacent(&cell.top().left()));
+ assert!(!cell.is_adjacent(&cell.top_right().right()));
+ assert!(!cell.is_adjacent(&cell.left().left()));
+ assert!(!cell.is_adjacent(&cell.right().right()));
+ assert!(!cell.is_adjacent(&cell.bottom_left().bottom_left()));
+ assert!(!cell.is_adjacent(&cell.bottom().bottom_right()));
+ assert!(!cell.is_adjacent(&cell.bottom_right().right()));
+ }
+
+ #[test]
+ fn test_location() {
+ let cell = Cell::new(5, 5);
+ assert_eq!(cell.left(), Cell::new(4, 5));
+ assert_eq!(cell.right(), Cell::new(6, 5));
+ assert_eq!(cell.top(), Cell::new(5, 4));
+ assert_eq!(cell.bottom(), Cell::new(5, 6));
+ assert_eq!(cell.top_left(), Cell::new(4, 4));
+ assert_eq!(cell.top_right(), Cell::new(6, 4));
+ assert_eq!(cell.bottom_left(), Cell::new(4, 6));
+ assert_eq!(cell.bottom_right(), Cell::new(6, 6));
+ }
+
+ #[test]
+ fn cell_from_snap_group_point() {
+ let p1 = Point::new(11.0, 11.0);
+ let p2 = Point::new(10.0, 11.0);
+ let (cell1, snap1) = Cell::snap_point(p1);
+ assert_eq!(Cell::new(11, 5), cell1);
+ assert_eq!(snap1, Point::new(0.0, 1.0));
+
+ let (cell2, _snap2) = Cell::snap_point(p2);
+ assert_eq!(Cell::new(10, 5), cell2);
+
+ let cell_group = Cell::snap_group(&[p1, p2]);
+ assert_eq!(Cell::new(10, 5), cell_group);
+ let local1 = cell_group.localize_point(p1);
+ let local2 = cell_group.localize_point(p2);
+ println!("local1: {:#?}", local1);
+ println!("local2: {:#?}", local2);
+ assert_eq!(local1, Point::new(1.0, 1.0));
+ assert_eq!(local2, Point::new(0.0, 1.0));
+ }
+
+ #[test]
+ fn cell_from_point() {
+ let (cell, snap) = Cell::snap_point(Point::new(10.0, 11.0));
+ assert_eq!(Cell::new(10, 5), cell);
+ assert_eq!(snap, Point::new(0.0, 1.0));
+ }
+
+ #[test]
+ fn test_localize() {
+ let cell = Cell::new(3, 2);
+ let m = cell.m();
+ assert_eq!(m, Point::new(3.5, 5.0));
+ let local_m = cell.localize_point(m);
+ assert_eq!(local_m, Point::new(0.5, 1.0));
+ }
+
+ #[test]
+ fn test_locations() {
+ assert_eq!(Cell::new(0, 0).top_left_most(), Point::new(0.0, 0.0));
+ assert_eq!(Cell::new(0, 0).a(), Point::new(0.0, 0.0));
+ assert_eq!(Cell::new(0, 0).y(), Point::new(1.0, 2.0));
+ assert_eq!(Cell::new(1, 0).y(), Point::new(2.0, 2.0));
+ assert_eq!(Cell::new(1, 1).m(), Point::new(1.5, 3.0));
+ assert_eq!(Cell::new(9, 9).o(), Point::new(10.0, 19.0));
+ }
+
+ #[test]
+ fn text_proximity() {
+ assert_eq!(Cell::snap_xy(0.1, 0.05), CellGrid::a());
+ assert_eq!(Cell::snap_xy(1.1, 2.05), CellGrid::y());
+ }
+
+ #[test]
+ fn test_aabb() {
+ assert_eq!(
+ AABB::new(*Point::new(0.0, 0.0), *Point::new(1.0, 2.0)),
+ Cell::new(0, 0).bounding_box()
+ );
+ }
+
+ #[test]
+ fn test_clip_line() {
+ assert_eq!(
+ Cell::new(0, 0).clip_line_snap(Point::new(-0.01, -0.01), Point::new(1.01, 2.01)),
+ Some((CellGrid::a(), CellGrid::y()))
+ );
+
+ assert_eq!(
+ Cell::new(0, 0).clip_line_snap(Point::new(0.0, 0.0), Point::new(1.00, 0.0)),
+ Some((CellGrid::a(), CellGrid::e()))
+ );
+
+ let clipped = Cell::new(0, 0).clip_line_snap(Point::new(0.0, 1.0), Point::new(1.0, 1.0));
+ assert_eq!(clipped, Some((CellGrid::k(), CellGrid::o())));
+
+ let clipped =
+ Cell::new(0, 0).clip_line_snap(Point::new(-0.01, 1.01), Point::new(1.01, 0.95));
+ assert_eq!(clipped, Some((CellGrid::k(), CellGrid::o())));
+
+ assert_eq!(
+ Cell::new(0, 0).clip_line_snap(Point::new(0.0, 2.0), Point::new(1.0, 2.0)),
+ Some((CellGrid::u(), CellGrid::y()))
+ );
+
+ assert_eq!(
+ Cell::new(0, 0).clip_line_snap(Point::new(0.0, 0.0), Point::new(0.0, 2.0)),
+ Some((CellGrid::a(), CellGrid::u()))
+ );
+
+ assert_eq!(
+ Cell::new(0, 0).clip_line_snap(Point::new(1.0, 0.0), Point::new(1.0, 2.0)),
+ Some((CellGrid::e(), CellGrid::y()))
+ );
+
+ assert_eq!(
+ Cell::new(0, 0).clip_line_snap(Point::new(1.0, 0.0), Point::new(0.0, 0.0)),
+ Some((CellGrid::e(), CellGrid::a()))
+ );
+
+ assert_eq!(
+ Cell::new(0, 0).clip_line_snap(Point::new(0.5, 1.0), Point::new(1.0, 1.0)),
+ Some((CellGrid::m(), CellGrid::o()))
+ );
+
+ assert_eq!(
+ Cell::new(0, 0).clip_line_snap(Point::new(0.25, 1.0), Point::new(1.0, 1.0)),
+ Some((CellGrid::l(), CellGrid::o()))
+ );
+ }
+}