1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#![warn(missing_docs)]

//! # tahga::stroke::line
//!
//! The stroke::line module defines a line with collision detection

use nannou::color::Alpha;
use nannou::prelude::*;

/// A Line can detect whether it collides with another stroke::line, taking line weight into account.
#[derive(Clone, Debug)]
pub struct Line {
    /// The line start point
    pub start: Vec2,
    /// The line end point
    pub end: Vec2,
    /// The line weight
    pub weight: f32,
    /// The line color
    pub color: Alpha<Hsl, f32>,
}

impl Line {
    /// Create a new instance of a [`Line`]
    ///
    /// # Arguments
    ///
    /// `start` - The starting point of the line
    ///
    /// `end` - The ending point of the line
    ///
    /// `weight` - The line weight
    ///
    /// `color` - The line color
    ///
    pub fn new(start: Vec2, end: Vec2, weight: f32, color: Alpha<Hsl, f32>) -> Self {
        Self { start, end, weight, color }
    }

    /// Draw the line
    ///
    /// # Arguments
    ///
    /// `draw` - The draw context
    ///
    pub fn draw(&self, draw: &Draw) {
        draw.line()
            .start(self.start)
            .end(self.end)
            .weight(self.weight)
            .color(self.color);
    }


    /// Check whether a line collides with another line
    ///
    /// # Arguments
    ///
    /// `other` - The other line
    ///
    pub fn collides_with_line(&self, other: &Line) -> bool {

        // See if lines intersect
        if collision_line_line(self.start.x, self.start.y, self.end.x, self.end.y, other.start.x, other.start.y, other.end.x, other.end.y) {
            return true;
        }

        // See if endpoint of line is too close to line
        let radius = self.weight + 2.0;
        let length = other.start.distance(other.end);

        let dot_product = (((self.end.x - other.start.x) * (other.end.x - other.start.x)) + ((self.end.y - other.start.y) * (other.end.y - other.start.y))) / (length * length);
        let closest_point = vec2(other.start.x + (dot_product * (other.end.x - other.start.x)), other.start.y + (dot_product * (other.end.y - other.start.y)));
        if !collision_line_point(other, closest_point) {
            return false;
        }

        let distance_to_closest_point = self.end.distance(closest_point);
        if distance_to_closest_point <= radius {
            return true;
        }

        false
    }
}

fn collision_line_line(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, x4: f32, y4: f32) -> bool {
    let a = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
    let b = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));

    a >= 0. && a <= 1. && b >= 0. && b <= 1.
}

fn collision_line_point(line: &Line, p: Vec2) -> bool {
    let d1 = p.distance(line.start);
    let d2 = p.distance(line.end);
    let line_length = line.start.distance(line.end);
    let buffer = 0.1;

    if d1 + d2 >= line_length - buffer && d1 + d2 <= line_length + buffer {
        return true;
    }

    false
}