1use crate::{
2 buffer::{fragment_buffer::fragment::polygon::Polygon, Cell, Fragment},
3 fragment::{marker_line, Bounds, Circle, Marker, MarkerLine},
4 util, Direction, Point,
5};
6use parry2d::{
7 bounding_volume::Aabb,
8 math::Isometry,
9 query::PointQuery,
10 shape::{Polyline, Segment, Shape},
11};
12use std::{cmp::Ordering, fmt};
13
14use crate::fragment::Arc;
15use sauron::{html::attributes::*, svg, svg::attributes::*, Node};
16
17#[derive(Debug, Clone)]
18pub struct Line {
19 pub start: Point,
20 pub end: Point,
21 pub is_broken: bool,
22}
23
24impl Line {
25 pub fn new(start: Point, end: Point, is_broken: bool) -> Self {
28 let mut line = Line {
29 start,
30 end,
31 is_broken,
32 };
33 line.sort_reorder_end_points();
34 line
35 }
36
37 pub(crate) fn new_noswap(
39 start: Point,
40 end: Point,
41 is_broken: bool,
42 ) -> Self {
43 Line {
44 start,
45 end,
46 is_broken,
47 }
48 }
49
50 pub(crate) fn sort_reorder_end_points(&mut self) {
53 if self.start > self.end {
54 self.swap()
55 }
56 }
57
58 fn swap(&mut self) {
59 std::mem::swap(&mut self.start, &mut self.end);
60 }
61
62 pub(crate) fn overlaps(&self, a: Point, b: Point) -> bool {
64 let segment = Segment::new(*self.start, *self.end);
65 let identity = &Isometry::identity();
66 segment.contains_point(identity, &a)
67 && segment.contains_point(identity, &b)
68 }
69
70 fn contains_point(&self, p: Point) -> bool {
71 let segment = Segment::new(*self.start, *self.end);
72 let identity = &Isometry::identity();
73 segment.contains_point(identity, &p)
74 }
75
76 fn touching_line(&self, other: &Self) -> bool {
77 self.contains_point(other.start) || self.contains_point(other.end)
78 }
79
80 fn octant(&self) -> u8 {
81 let mut dx = self.end.x - self.start.x;
82 let mut dy = -(self.end.y * 2.0 - self.start.y * 2.0);
83
84 let mut octant = 0;
85
86 if dy < 0.0 {
87 dx = -dx;
88 dy = -dy;
89 octant += 4;
90 }
91
92 if dx < 0.0 {
93 let tmp = dx;
94 dx = dy;
95 dy = -tmp;
96 octant += 2
97 }
98
99 if dx < dy {
100 octant += 1
101 }
102 octant
103 }
104
105 fn angle_rad(&self) -> f32 {
107 let m1 = self.slope();
108 0.0 - m1.atan()
109 }
110
111 fn angle_deg(&self) -> f32 {
112 self.angle_rad().to_degrees()
113 }
114
115 fn slope(&self) -> f32 {
116 (2.0 * self.end.y as f32 - 2.0 * self.start.y as f32)
117 / (self.end.x as f32 - self.start.x as f32)
118 }
119
120 fn full_angle(&self) -> f32 {
121 let angle = self.angle_deg().abs();
122 match self.octant() {
123 0..=1 => angle,
124 2..=3 => 180.0 - angle,
125 4..=5 => 180.0 + angle,
126 6..=7 => 360.0 - angle,
127 _ => angle,
128 }
129 }
130
131 fn line_angle(&self) -> f32 {
141 let angle = self.full_angle().round() as i32;
142 match angle {
143 0..=10 => 0.0,
144 11..=50 => 63.435, 51..=80 => 63.435,
146 81..=100 => 90.0,
147 101..=130 => 116.565,
148 131..=170 => 116.565, 171..=190 => 180.0,
150 191..=230 => 243.435, 231..=260 => 243.435,
152 261..=280 => 270.0,
153 281..=310 => 296.565,
154 311..=350 => 296.565, 351..=360 => 0.0,
156 _ => 0.0,
157 }
158 }
159
160 pub(crate) fn heading(&self) -> Direction {
174 match self.line_angle().round() as i32 {
175 0 => Direction::Right,
176 45 => Direction::TopRight,
177 63 => Direction::TopRight,
178 90 => Direction::Top,
179 117 => Direction::TopLeft,
180 135 => Direction::TopLeft,
181 180 => Direction::Left,
182 225 => Direction::BottomLeft,
183 243 => Direction::BottomLeft,
184 270 => Direction::Bottom,
185 297 => Direction::BottomRight,
186 315 => Direction::BottomRight,
187 _ => unreachable!(),
188 }
189 }
190
191 pub(crate) fn is_touching_circle(&self, circle: &Circle) -> bool {
192 let center = circle.center;
193 let distance_end_center = self.end.distance(¢er);
194 let distance_start_center = self.start.distance(¢er);
195
196 let _threshold_length = self.heading().threshold_length();
197 let is_close_start_point = distance_start_center < (circle.radius);
198 let is_close_end_point = distance_end_center < (circle.radius);
199 is_close_start_point || is_close_end_point
200 }
201
202 pub(crate) fn can_merge(&self, other: &Self) -> bool {
208 self.is_touching(other)
209 && util::is_collinear(&self.start, &self.end, &other.start)
210 && util::is_collinear(&self.start, &self.end, &other.end)
211 }
212
213 pub(crate) fn merge(&self, other: &Self) -> Option<Self> {
217 if self.can_merge(other) {
218 let start = std::cmp::min(self.start, other.start);
219 let end = std::cmp::max(self.end, other.end);
220 Some(Line::new(start, end, self.is_broken || other.is_broken))
222 } else {
223 None
224 }
225 }
226
227 pub(crate) fn merge_circle(&self, circle: &Circle) -> Option<Fragment> {
228 let distance_end_center = self.end.distance(&circle.center);
229 let distance_start_center = self.start.distance(&circle.center);
230
231 let threshold_length = self.heading().threshold_length();
232 let is_close_start_point =
233 distance_start_center <= threshold_length * 0.75;
234 let is_close_end_point = distance_end_center <= threshold_length * 0.75;
235
236 let can_merge = circle.radius <= Cell::unit(3)
237 && (is_close_start_point || is_close_end_point);
238
239 if can_merge {
240 let marker = if circle.is_filled {
241 Some(Marker::Circle)
242 } else if circle.radius >= Cell::unit(2) {
243 Some(Marker::BigOpenCircle)
244 } else {
245 Some(Marker::OpenCircle)
246 };
247 let new_line = if is_close_end_point {
248 Line::new_noswap(self.start, circle.center, self.is_broken)
249 } else if is_close_start_point {
250 Line::new_noswap(self.end, circle.center, self.is_broken)
252 } else {
253 panic!("There is no endpoint of the line is that close to the arrow");
254 };
255
256 let marker_line = marker_line(
257 new_line.start,
258 new_line.end,
259 new_line.is_broken,
260 None,
261 marker,
262 );
263 Some(marker_line)
264 } else {
265 None
266 }
267 }
268
269 pub(crate) fn is_touching(&self, other: &Self) -> bool {
273 self.touching_line(other) || other.touching_line(self)
274 }
275
276 pub(crate) fn has_endpoint(&self, p: Point) -> bool {
277 self.start == p || self.end == p
278 }
279
280 pub(crate) fn is_touching_arc(&self, other: &Arc) -> bool {
281 self.start == other.start
282 || self.end == other.end
283 || self.start == other.end
284 || self.end == other.start
285 }
286
287 fn is_horizontal(&self) -> bool {
289 self.start.y == self.end.y
290 }
291
292 fn is_vertical(&self) -> bool {
294 self.start.x == self.end.x
295 }
296
297 pub(crate) fn is_aabb_parallel(&self, other: &Self) -> bool {
300 (self.is_horizontal()
301 && other.is_horizontal()
302 && self.start.x == other.start.x
303 && self.end.x == other.end.x)
304 || (self.is_vertical()
305 && other.is_vertical()
306 && self.start.y == other.start.y
307 && self.end.y == other.end.y)
308 }
309
310 pub(crate) fn is_aabb_perpendicular(&self, other: &Self) -> bool {
313 (self.is_horizontal() && other.is_vertical())
314 || (self.is_vertical() && other.is_horizontal())
315 }
316
317 pub(crate) fn is_touching_aabb_perpendicular(&self, other: &Self) -> bool {
319 self.is_touching(other) && self.is_aabb_perpendicular(other)
320 }
321
322 pub(crate) fn absolute_position(&self, cell: Cell) -> Self {
325 Line {
326 start: cell.absolute_position(self.start),
327 end: cell.absolute_position(self.end),
328 is_broken: self.is_broken,
329 }
330 }
331
332 pub fn scale(&self, scale: f32) -> Self {
333 Line {
334 start: self.start.scale(scale),
335 end: self.end.scale(scale),
336 is_broken: self.is_broken,
337 }
338 }
339
340 pub(crate) fn is_broken(&self) -> bool {
341 self.is_broken
342 }
343
344 pub fn localize(&self, cell: Cell) -> Self {
345 Line {
346 start: cell.localize_point(self.start),
347 end: cell.localize_point(self.end),
348 is_broken: self.is_broken,
349 }
350 }
351
352 pub(crate) fn align(&self) -> Self {
353 Line {
354 start: self.start.align(),
355 end: self.end.align(),
356 is_broken: self.is_broken,
357 }
358 }
359
360 pub fn extend(&self, length: f32) -> Self {
363 let d = self.start.distance(&self.end);
364 let cx = self.end.x + (self.end.x - self.start.x) / d * length;
365 let cy = self.end.y + (self.end.y - self.start.y) / d * length;
366 Line::new_noswap(self.start, Point::new(cx, cy), self.is_broken)
367 }
368
369 pub fn extend_start(&self, length: f32) -> Self {
372 let mut tmp_line = self.clone();
373 tmp_line.swap();
374 let mut new_line = tmp_line.extend(length);
375 new_line.swap();
376 new_line
377 }
378}
379
380impl Bounds for Line {
381 fn bounds(&self) -> (Point, Point) {
382 let aabb = Segment::new(*self.start, *self.end).local_aabb();
383 (Point::from(*aabb.mins), Point::from(*aabb.maxs))
384 }
385}
386
387impl fmt::Display for Line {
388 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
389 write!(f, "L {} {} {}", self.start, self.end, self.is_broken)
390 }
391}
392
393impl<MSG> From<Line> for Node<MSG> {
394 fn from(line: Line) -> Node<MSG> {
395 svg::line(
396 [
397 x1(line.start.x),
398 y1(line.start.y),
399 x2(line.end.x),
400 y2(line.end.y),
401 classes_flag([
402 ("broken", line.is_broken),
403 ("solid", !line.is_broken),
404 ]),
405 ],
406 [],
407 )
408 }
409}
410
411impl From<Line> for Segment {
412 fn from(line: Line) -> Segment {
413 Segment::new(*line.start, *line.end)
414 }
415}
416
417impl Eq for Line {}
418
419impl Ord for Line {
421 fn cmp(&self, other: &Self) -> Ordering {
422 self.start
423 .cmp(&other.start)
424 .then(self.end.cmp(&other.end))
425 .then(self.is_broken.cmp(&other.is_broken))
426 }
427}
428
429impl PartialOrd for Line {
430 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
431 Some(self.cmp(other))
432 }
433}
434
435impl PartialEq for Line {
436 fn eq(&self, other: &Self) -> bool {
437 self.cmp(other) == Ordering::Equal
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 use crate::buffer::{
445 fragment_buffer::fragment::polygon::PolygonTag, CellGrid,
446 };
447
448 #[test]
449 fn test_extend_line() {
450 let line1 = Line::new_noswap(
451 Point::new(0.0, 0.0),
452 Point::new(10.0, 0.0),
453 false,
454 );
455 let extended = line1.extend(1.0);
456 assert_eq!(
457 extended,
458 Line::new(Point::new(0.0, 0.0), Point::new(11.0, 0.0), false)
459 );
460 let extended2 = line1.extend(2.0);
461 assert_eq!(
462 extended2,
463 Line::new(Point::new(0.0, 0.0), Point::new(12.0, 0.0), false)
464 );
465 }
466
467 #[test]
468 fn test_extend_line_start() {
469 let line1 = Line::new_noswap(
470 Point::new(0.0, 0.0),
471 Point::new(10.0, 0.0),
472 false,
473 );
474 let extended = line1.extend_start(1.0);
475 assert_eq!(
476 extended,
477 Line::new(Point::new(-1.0, 0.0), Point::new(10.0, 0.0), false)
478 );
479 let extended2 = line1.extend_start(2.0);
480 assert_eq!(
481 extended2,
482 Line::new(Point::new(-2.0, 0.0), Point::new(10.0, 0.0), false)
483 );
484 }
485
486 #[test]
487 fn test_extend_line_vertical() {
488 let line1 = Line::new_noswap(
489 Point::new(0.0, 0.0),
490 Point::new(0.0, 10.0),
491 false,
492 );
493 let extended = line1.extend(1.0);
494 assert_eq!(
495 extended,
496 Line::new(Point::new(0.0, 0.0), Point::new(0.0, 11.0), false)
497 );
498 let extended2 = line1.extend(2.0);
499 assert_eq!(
500 extended2,
501 Line::new(Point::new(0.0, 0.0), Point::new(0.0, 12.0), false)
502 );
503 }
504
505 #[test]
506 fn line_merge() {
507 let line1 =
508 Line::new(Point::new(4.0, 0.0), Point::new(2.0, 4.0), false);
509 let line2 =
510 Line::new(Point::new(2.0, 4.0), Point::new(1.0, 6.0), false);
511 assert!(line1.is_touching(&line2));
512 assert!(line2.is_touching(&line1));
513 assert!(util::is_collinear(&line1.start, &line1.end, &line2.start));
514 assert!(util::is_collinear(&line2.start, &line2.end, &line1.end));
515 assert!(line1.can_merge(&line2));
516 }
517
518 #[test]
519 fn test_angle() {
520 let m = CellGrid::m();
521 let k = CellGrid::k();
522 let c = CellGrid::c();
523 let o = CellGrid::o();
524 let e = CellGrid::e();
525 let a = CellGrid::a();
526 let y = CellGrid::y();
527 let u = CellGrid::u();
528
529 assert_eq!(0.0, 0.0f32.atan());
530
531 let line = Line::new(c, m, false);
532 assert_eq!(line.line_angle(), 270.0);
533
534 let line2 = Line::new(m, o, false);
535 assert_eq!(line2.line_angle(), 0.0);
536
537 let line3 = Line::new(a, y, false);
538 assert_eq!(line3.line_angle(), 296.565);
539
540 let line4 = Line::new(k, o, false);
541 assert_eq!(line4.line_angle(), 0.0);
542
543 let line6 = Line::new(u, e, false);
544 assert_eq!(line6.line_angle(), 243.435);
545
546 let line5 = Line::new(e, u, false);
547 assert_eq!(line5.line_angle(), 243.435);
548 }
549
550 #[test]
551 fn test_bounds() {
552 let d = CellGrid::d();
553 let e = CellGrid::e();
554 let line = Line::new(e, d, false);
555 assert_eq!(line.bounds(), (d, e));
556 }
557
558 #[test]
559 fn test_merge() {
560 let a = CellGrid::a();
561 let b = CellGrid::b();
562 let c = CellGrid::c();
563 let d = CellGrid::d();
564 assert!(Line::new(a, b, false).can_merge(&Line::new(b, c, false)));
565 assert!(Line::new(b, c, false).can_merge(&Line::new(b, c, false)));
566 assert!(Line::new(b, c, false).can_merge(&Line::new(c, b, false)));
567 assert!(Line::new(b, c, false).can_merge(&Line::new(c, d, false)));
568 }
569
570 #[test]
571 fn test_merge_kmo() {
572 let k = CellGrid::k();
573 let m = CellGrid::m();
574 let o = CellGrid::o();
575 assert!(Line::new(k, m, false).can_merge(&Line::new(m, o, false)));
576 assert_eq!(
577 Some(Line::new(k, o, false)),
578 Line::new(k, m, false).merge(&Line::new(m, o, false))
579 );
580 }
581
582 #[test]
583 fn test_merge_cmw() {
584 let c = CellGrid::c();
585 let m = CellGrid::m();
586 let w = CellGrid::w();
587 assert!(Line::new(c, m, false).can_merge(&Line::new(m, w, false)));
588 assert_eq!(
589 Some(Line::new(c, w, false)),
590 Line::new(c, m, false).merge(&Line::new(m, w, false))
591 );
592 }
593}