index/objects/geometry/
line.rs1use wasm_bindgen::prelude::*;
2
3use crate::{objects::vector_object::VectorObjectBuilder, utils::point2d::Point2D};
4
5use super::tipable::Tipable;
6
7#[wasm_bindgen]
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct Line {
12 pub start: Point2D,
14 pub end: Point2D,
16}
17
18#[wasm_bindgen]
19impl Line {
20 #[wasm_bindgen(constructor, return_description = "A line connecting the start and end points.")]
22 pub fn new(start: Point2D, end: Point2D) -> Line {
23 Line { start, end }
24 }
25
26 #[wasm_bindgen(getter, return_description = "A vector object builder with the line's points.")]
28 pub fn vector_object_builder(
29 &self,
30 ) -> VectorObjectBuilder {
31 VectorObjectBuilder::default()
32 .move_point(&self.start)
33 .line_to(&self.end)
34 }
35
36 #[wasm_bindgen(getter, return_description = "The length of the line.")]
38 pub fn length(&self) -> f32 {
39 Point2D::distance(&self.start, &self.end)
40 }
41
42 #[wasm_bindgen(return_description = "The midpoint of the line.")]
44 pub fn midpoint(&self) -> Point2D {
45 (self.start + self.end) * 0.5
46 }
47
48 #[wasm_bindgen(return_description = "The slope of the line.")]
50 pub fn slope(&self) -> f32 {
51 (self.end.y - self.start.y) / (self.end.x - self.start.x)
52 }
53
54 #[wasm_bindgen(return_description = "The y-intercept of the line.")]
56 pub fn y_intercept(&self) -> f32 {
57 self.start.y - self.slope() * self.start.x
58 }
59
60 #[wasm_bindgen(return_description = "The normal slope of the line.")]
62 pub fn normal_slope(&self) -> f32 {
63 -1.0 / self.slope()
64 }
65
66 #[wasm_bindgen(return_description = "The perpendicular line of the line at the given point.")]
68 pub fn perpendicular_line(
69 &self,
70 #[wasm_bindgen(param_description = "The point to create the perpendicular line at.")]
71 point: Point2D
72 ) -> Line {
73 let slope = self.normal_slope();
74 let y_intercept = point.y - slope * point.x;
75 Line::new(point, Point2D::new(0.0, y_intercept))
76 }
77
78 #[wasm_bindgen(return_description = "The intersection point of the two lines if it exists.")]
80 pub fn intersection(
81 &self,
82 #[wasm_bindgen(param_description = "The other line to intersect with.")]
83 other: &Line
84 ) -> Option<Point2D> {
85 let x = (other.y_intercept() - self.y_intercept()) / (self.slope() - other.slope());
86 let y = self.slope() * x + self.y_intercept();
87 if x.is_nan() || y.is_nan() {
88 None
89 } else {
90 Some(Point2D::new(x, y))
91 }
92 }
93
94 #[wasm_bindgen(return_description = "Whether the line contains the given point.")]
96 pub fn contains(
97 &self,
98 #[wasm_bindgen(param_description = "The point to check if it is contained.")]
99 point: Point2D
100 ) -> bool {
101 let x_min = self.start.x.min(self.end.x);
102 let x_max = self.start.x.max(self.end.x);
103 let y_min = self.start.y.min(self.end.y);
104 let y_max = self.start.y.max(self.end.y);
105 point.x >= x_min && point.x <= x_max && point.y >= y_min && point.y <= y_max
106 }
107
108 #[wasm_bindgen(return_description = "The distance from the line to the given point.")]
110 pub fn distance_to_point(
111 &self,
112 #[wasm_bindgen(param_description = "The point to calculate the distance to.")]
113 point: Point2D
114 ) -> f32 {
115 let a = self.start.y - self.end.y;
116 let b = self.end.x - self.start.x;
117 let c = self.start.x * self.end.y - self.end.x * self.start.y;
118 (a * point.x + b * point.y + c).abs() / (a * a + b * b).sqrt()
119 }
120
121 #[wasm_bindgen(return_description = "A VectorObjectBuilder with the line's points and a tip at the start.")]
123 pub fn with_tip_at_the_start(
124 &self,
125 #[wasm_bindgen(param_description = "The shape of the tip. The shape must be pointing to the right and centered to (0, 0), this function will rotate and move it to the correct angle.")]
126 tip_shape: VectorObjectBuilder,
127 ) -> VectorObjectBuilder {
128 let mut builder = self.vector_object_builder();
129 builder = builder.add_child(self.tip_at_start(tip_shape));
130 builder
131 }
132
133 #[wasm_bindgen(return_description = "A VectorObjectBuilder with the line's points and a tip at the end.")]
135 pub fn with_tip_at_the_end(
136 &self,
137 #[wasm_bindgen(param_description = "The shape of the tip. The shape must be pointing to the right and centered to (0, 0), this function will rotate and move it to the correct angle.")]
138 tip_shape: VectorObjectBuilder,
139 ) -> VectorObjectBuilder {
140 let mut builder = self.vector_object_builder();
141 builder = builder.add_child(self.tip_at_end(tip_shape));
142 builder
143 }
144
145 #[wasm_bindgen(return_description = "A VectorObjectBuilder with the line's points and tips at both ends.")]
147 pub fn with_tips_at_both_ends(
148 &self,
149 #[wasm_bindgen(param_description = "The shape of the tip. The shape must be pointing to the right, this function will rotate and move it to the correct angle.")]
150 tip_shape: VectorObjectBuilder,
151 ) -> VectorObjectBuilder {
152 let mut builder = self.vector_object_builder();
153 builder = builder.add_child(self.tip_at_start(tip_shape.clone()));
154 builder = builder.add_child(self.tip_at_end(tip_shape));
155 builder
156 }
157}
158
159impl Tipable for Line {
160 fn angle_at_end(&self) -> f32 {
161 self.slope().atan()
162 }
163 fn angle_at_start(&self) -> f32 {
164 self.slope().atan() + std::f32::consts::PI
165 }
166 fn end(&self) -> Point2D {
167 self.end
168 }
169 fn start(&self) -> Point2D {
170 self.start
171 }
172}