summaryrefslogtreecommitdiffstats
path: root/packages/svgbob/src/buffer/cell_buffer/cell.rs
diff options
context:
space:
mode:
Diffstat (limited to 'packages/svgbob/src/buffer/cell_buffer/cell.rs')
-rw-r--r--packages/svgbob/src/buffer/cell_buffer/cell.rs498
1 files changed, 498 insertions, 0 deletions
diff --git a/packages/svgbob/src/buffer/cell_buffer/cell.rs b/packages/svgbob/src/buffer/cell_buffer/cell.rs
new file mode 100644
index 0000000..e2fa1ca
--- /dev/null
+++ b/packages/svgbob/src/buffer/cell_buffer/cell.rs
@@ -0,0 +1,498 @@
+use crate::{util, Point};
+use parry2d::query::PointQuery;
+use parry2d::{
+ bounding_volume::AABB,
+ math::Isometry,
+ query::intersection_test,
+ shape::{Polyline, Segment},
+};
+use std::ops::{Add, Sub};
+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 {
+ 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 {
+ 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);
+ intersection_test(
+ &Isometry::identity(),
+ &pl,
+ &Isometry::identity(),
+ &segment,
+ )
+ .expect("must pass intersection test")
+ }
+
+ /// 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) = Self::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
+ /// Note: There are 4 horizontal mini cell for each cell
+ /// There are 8 vertical mini cell for each cell
+ #[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))
+ }
+}
+
+impl Add for Cell {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self::Output {
+ Self::new(self.x + other.x, self.y + other.y)
+ }
+}
+
+impl Sub for Cell {
+ type Output = Self;
+
+ fn sub(self, other: Self) -> Self::Output {
+ Self::new(self.x - other.x, self.y - other.y)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[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()))
+ );
+ }
+}