use crate::{
buffer::{fragment_buffer::fragment::polygon::Polygon, Cell, Fragment},
fragment::{marker_line, Bounds, Circle, Marker, MarkerLine},
util, Direction, Point,
};
use parry2d::{
bounding_volume::Aabb,
math::Isometry,
query::PointQuery,
shape::{Polyline, Segment, Shape},
};
use std::{cmp::Ordering, fmt};
use crate::fragment::Arc;
use sauron::{html::attributes::*, svg, svg::attributes::*, Node};
#[derive(Debug, Clone)]
pub struct Line {
pub start: Point,
pub end: Point,
pub is_broken: bool,
}
impl Line {
pub fn new(start: Point, end: Point, is_broken: bool) -> Self {
let mut line = Line {
start,
end,
is_broken,
};
line.sort_reorder_end_points();
line
}
pub(crate) fn new_noswap(
start: Point,
end: Point,
is_broken: bool,
) -> Self {
Line {
start,
end,
is_broken,
}
}
pub(crate) fn sort_reorder_end_points(&mut self) {
if self.start > self.end {
self.swap()
}
}
fn swap(&mut self) {
std::mem::swap(&mut self.start, &mut self.end);
}
pub(crate) fn overlaps(&self, a: Point, b: Point) -> bool {
let segment = Segment::new(*self.start, *self.end);
let identity = &Isometry::identity();
segment.contains_point(identity, &a)
&& segment.contains_point(identity, &b)
}
fn contains_point(&self, p: Point) -> bool {
let segment = Segment::new(*self.start, *self.end);
let identity = &Isometry::identity();
segment.contains_point(identity, &p)
}
fn touching_line(&self, other: &Self) -> bool {
self.contains_point(other.start) || self.contains_point(other.end)
}
fn octant(&self) -> u8 {
let mut dx = self.end.x - self.start.x;
let mut dy = -(self.end.y * 2.0 - self.start.y * 2.0);
let mut octant = 0;
if dy < 0.0 {
dx = -dx;
dy = -dy;
octant += 4;
}
if dx < 0.0 {
let tmp = dx;
dx = dy;
dy = -tmp;
octant += 2
}
if dx < dy {
octant += 1
}
octant
}
fn angle_rad(&self) -> f32 {
let m1 = self.slope();
0.0 - m1.atan()
}
fn angle_deg(&self) -> f32 {
self.angle_rad().to_degrees()
}
fn slope(&self) -> f32 {
(2.0 * self.end.y as f32 - 2.0 * self.start.y as f32)
/ (self.end.x as f32 - self.start.x as f32)
}
fn full_angle(&self) -> f32 {
let angle = self.angle_deg().abs();
match self.octant() {
0..=1 => angle,
2..=3 => 180.0 - angle,
4..=5 => 180.0 + angle,
6..=7 => 360.0 - angle,
_ => angle,
}
}
fn line_angle(&self) -> f32 {
let angle = self.full_angle().round() as i32;
match angle {
0..=10 => 0.0,
11..=50 => 63.435, 51..=80 => 63.435,
81..=100 => 90.0,
101..=130 => 116.565,
131..=170 => 116.565, 171..=190 => 180.0,
191..=230 => 243.435, 231..=260 => 243.435,
261..=280 => 270.0,
281..=310 => 296.565,
311..=350 => 296.565, 351..=360 => 0.0,
_ => 0.0,
}
}
pub(crate) fn heading(&self) -> Direction {
match self.line_angle().round() as i32 {
0 => Direction::Right,
45 => Direction::TopRight,
63 => Direction::TopRight,
90 => Direction::Top,
117 => Direction::TopLeft,
135 => Direction::TopLeft,
180 => Direction::Left,
225 => Direction::BottomLeft,
243 => Direction::BottomLeft,
270 => Direction::Bottom,
297 => Direction::BottomRight,
315 => Direction::BottomRight,
_ => unreachable!(),
}
}
pub(crate) fn is_touching_circle(&self, circle: &Circle) -> bool {
let center = circle.center;
let distance_end_center = self.end.distance(¢er);
let distance_start_center = self.start.distance(¢er);
let _threshold_length = self.heading().threshold_length();
let is_close_start_point = distance_start_center < (circle.radius);
let is_close_end_point = distance_end_center < (circle.radius);
is_close_start_point || is_close_end_point
}
pub(crate) fn can_merge(&self, other: &Self) -> bool {
self.is_touching(other)
&& util::is_collinear(&self.start, &self.end, &other.start)
&& util::is_collinear(&self.start, &self.end, &other.end)
}
pub(crate) fn merge(&self, other: &Self) -> Option<Self> {
if self.can_merge(other) {
let start = std::cmp::min(self.start, other.start);
let end = std::cmp::max(self.end, other.end);
Some(Line::new(start, end, self.is_broken || other.is_broken))
} else {
None
}
}
pub(crate) fn merge_circle(&self, circle: &Circle) -> Option<Fragment> {
let distance_end_center = self.end.distance(&circle.center);
let distance_start_center = self.start.distance(&circle.center);
let threshold_length = self.heading().threshold_length();
let is_close_start_point =
distance_start_center <= threshold_length * 0.75;
let is_close_end_point = distance_end_center <= threshold_length * 0.75;
let can_merge = circle.radius <= Cell::unit(3)
&& (is_close_start_point || is_close_end_point);
if can_merge {
let marker = if circle.is_filled {
Some(Marker::Circle)
} else if circle.radius >= Cell::unit(2) {
Some(Marker::BigOpenCircle)
} else {
Some(Marker::OpenCircle)
};
let new_line = if is_close_end_point {
Line::new_noswap(self.start, circle.center, self.is_broken)
} else if is_close_start_point {
Line::new_noswap(self.end, circle.center, self.is_broken)
} else {
panic!("There is no endpoint of the line is that close to the arrow");
};
let marker_line = marker_line(
new_line.start,
new_line.end,
new_line.is_broken,
None,
marker,
);
Some(marker_line)
} else {
None
}
}
pub(crate) fn is_touching(&self, other: &Self) -> bool {
self.touching_line(other) || other.touching_line(self)
}
pub(crate) fn has_endpoint(&self, p: Point) -> bool {
self.start == p || self.end == p
}
pub(crate) fn is_touching_arc(&self, other: &Arc) -> bool {
self.start == other.start
|| self.end == other.end
|| self.start == other.end
|| self.end == other.start
}
fn is_horizontal(&self) -> bool {
self.start.y == self.end.y
}
fn is_vertical(&self) -> bool {
self.start.x == self.end.x
}
pub(crate) fn is_aabb_parallel(&self, other: &Self) -> bool {
(self.is_horizontal()
&& other.is_horizontal()
&& self.start.x == other.start.x
&& self.end.x == other.end.x)
|| (self.is_vertical()
&& other.is_vertical()
&& self.start.y == other.start.y
&& self.end.y == other.end.y)
}
pub(crate) fn is_aabb_perpendicular(&self, other: &Self) -> bool {
(self.is_horizontal() && other.is_vertical())
|| (self.is_vertical() && other.is_horizontal())
}
pub(crate) fn is_touching_aabb_perpendicular(&self, other: &Self) -> bool {
self.is_touching(other) && self.is_aabb_perpendicular(other)
}
pub(crate) fn absolute_position(&self, cell: Cell) -> Self {
Line {
start: cell.absolute_position(self.start),
end: cell.absolute_position(self.end),
is_broken: self.is_broken,
}
}
pub fn scale(&self, scale: f32) -> Self {
Line {
start: self.start.scale(scale),
end: self.end.scale(scale),
is_broken: self.is_broken,
}
}
pub(crate) fn is_broken(&self) -> bool {
self.is_broken
}
pub fn localize(&self, cell: Cell) -> Self {
Line {
start: cell.localize_point(self.start),
end: cell.localize_point(self.end),
is_broken: self.is_broken,
}
}
pub(crate) fn align(&self) -> Self {
Line {
start: self.start.align(),
end: self.end.align(),
is_broken: self.is_broken,
}
}
pub fn extend(&self, length: f32) -> Self {
let d = self.start.distance(&self.end);
let cx = self.end.x + (self.end.x - self.start.x) / d * length;
let cy = self.end.y + (self.end.y - self.start.y) / d * length;
Line::new_noswap(self.start, Point::new(cx, cy), self.is_broken)
}
pub fn extend_start(&self, length: f32) -> Self {
let mut tmp_line = self.clone();
tmp_line.swap();
let mut new_line = tmp_line.extend(length);
new_line.swap();
new_line
}
}
impl Bounds for Line {
fn bounds(&self) -> (Point, Point) {
let aabb = Segment::new(*self.start, *self.end).local_aabb();
(Point::from(*aabb.mins), Point::from(*aabb.maxs))
}
}
impl fmt::Display for Line {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "L {} {} {}", self.start, self.end, self.is_broken)
}
}
impl<MSG> From<Line> for Node<MSG> {
fn from(line: Line) -> Node<MSG> {
svg::line(
[
x1(line.start.x),
y1(line.start.y),
x2(line.end.x),
y2(line.end.y),
classes_flag([
("broken", line.is_broken),
("solid", !line.is_broken),
]),
],
[],
)
}
}
impl From<Line> for Segment {
fn from(line: Line) -> Segment {
Segment::new(*line.start, *line.end)
}
}
impl Eq for Line {}
impl Ord for Line {
fn cmp(&self, other: &Self) -> Ordering {
self.start
.cmp(&other.start)
.then(self.end.cmp(&other.end))
.then(self.is_broken.cmp(&other.is_broken))
}
}
impl PartialOrd for Line {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Line {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::buffer::{
fragment_buffer::fragment::polygon::PolygonTag, CellGrid,
};
#[test]
fn test_extend_line() {
let line1 = Line::new_noswap(
Point::new(0.0, 0.0),
Point::new(10.0, 0.0),
false,
);
let extended = line1.extend(1.0);
assert_eq!(
extended,
Line::new(Point::new(0.0, 0.0), Point::new(11.0, 0.0), false)
);
let extended2 = line1.extend(2.0);
assert_eq!(
extended2,
Line::new(Point::new(0.0, 0.0), Point::new(12.0, 0.0), false)
);
}
#[test]
fn test_extend_line_start() {
let line1 = Line::new_noswap(
Point::new(0.0, 0.0),
Point::new(10.0, 0.0),
false,
);
let extended = line1.extend_start(1.0);
assert_eq!(
extended,
Line::new(Point::new(-1.0, 0.0), Point::new(10.0, 0.0), false)
);
let extended2 = line1.extend_start(2.0);
assert_eq!(
extended2,
Line::new(Point::new(-2.0, 0.0), Point::new(10.0, 0.0), false)
);
}
#[test]
fn test_extend_line_vertical() {
let line1 = Line::new_noswap(
Point::new(0.0, 0.0),
Point::new(0.0, 10.0),
false,
);
let extended = line1.extend(1.0);
assert_eq!(
extended,
Line::new(Point::new(0.0, 0.0), Point::new(0.0, 11.0), false)
);
let extended2 = line1.extend(2.0);
assert_eq!(
extended2,
Line::new(Point::new(0.0, 0.0), Point::new(0.0, 12.0), false)
);
}
#[test]
fn line_merge() {
let line1 =
Line::new(Point::new(4.0, 0.0), Point::new(2.0, 4.0), false);
let line2 =
Line::new(Point::new(2.0, 4.0), Point::new(1.0, 6.0), false);
assert!(line1.is_touching(&line2));
assert!(line2.is_touching(&line1));
assert!(util::is_collinear(&line1.start, &line1.end, &line2.start));
assert!(util::is_collinear(&line2.start, &line2.end, &line1.end));
assert!(line1.can_merge(&line2));
}
#[test]
fn test_angle() {
let m = CellGrid::m();
let k = CellGrid::k();
let c = CellGrid::c();
let o = CellGrid::o();
let e = CellGrid::e();
let a = CellGrid::a();
let y = CellGrid::y();
let u = CellGrid::u();
assert_eq!(0.0, 0.0f32.atan());
let line = Line::new(c, m, false);
assert_eq!(line.line_angle(), 270.0);
let line2 = Line::new(m, o, false);
assert_eq!(line2.line_angle(), 0.0);
let line3 = Line::new(a, y, false);
assert_eq!(line3.line_angle(), 296.565);
let line4 = Line::new(k, o, false);
assert_eq!(line4.line_angle(), 0.0);
let line6 = Line::new(u, e, false);
assert_eq!(line6.line_angle(), 243.435);
let line5 = Line::new(e, u, false);
assert_eq!(line5.line_angle(), 243.435);
}
#[test]
fn test_bounds() {
let d = CellGrid::d();
let e = CellGrid::e();
let line = Line::new(e, d, false);
assert_eq!(line.bounds(), (d, e));
}
#[test]
fn test_merge() {
let a = CellGrid::a();
let b = CellGrid::b();
let c = CellGrid::c();
let d = CellGrid::d();
assert!(Line::new(a, b, false).can_merge(&Line::new(b, c, false)));
assert!(Line::new(b, c, false).can_merge(&Line::new(b, c, false)));
assert!(Line::new(b, c, false).can_merge(&Line::new(c, b, false)));
assert!(Line::new(b, c, false).can_merge(&Line::new(c, d, false)));
}
#[test]
fn test_merge_kmo() {
let k = CellGrid::k();
let m = CellGrid::m();
let o = CellGrid::o();
assert!(Line::new(k, m, false).can_merge(&Line::new(m, o, false)));
assert_eq!(
Some(Line::new(k, o, false)),
Line::new(k, m, false).merge(&Line::new(m, o, false))
);
}
#[test]
fn test_merge_cmw() {
let c = CellGrid::c();
let m = CellGrid::m();
let w = CellGrid::w();
assert!(Line::new(c, m, false).can_merge(&Line::new(m, w, false)));
assert_eq!(
Some(Line::new(c, w, false)),
Line::new(c, m, false).merge(&Line::new(m, w, false))
);
}
}