1use crate::{aabb::Aabb2, error::GeometryError, point::Point2, vector::Vector2};
2
3#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct Segment2 {
6 start: Point2,
8 end: Point2,
10}
11
12impl Segment2 {
13 #[must_use]
15 pub const fn new(start: Point2, end: Point2) -> Self {
16 Self { start, end }
17 }
18
19 pub fn try_new(start: Point2, end: Point2) -> Result<Self, GeometryError> {
36 Ok(Self::new(start.validate()?, end.validate()?))
37 }
38
39 #[must_use]
41 pub const fn start(self) -> Point2 {
42 self.start
43 }
44
45 #[must_use]
47 pub const fn end(self) -> Point2 {
48 self.end
49 }
50
51 #[must_use]
53 pub fn length(self) -> f64 {
54 self.start.distance_to(self.end)
55 }
56
57 #[must_use]
59 pub fn length_squared(self) -> f64 {
60 self.start.distance_squared_to(self.end)
61 }
62
63 #[must_use]
65 pub const fn midpoint(self) -> Point2 {
66 self.start.midpoint(self.end)
67 }
68
69 #[must_use]
71 pub const fn vector(self) -> Vector2 {
72 Vector2::from_points(self.start, self.end)
73 }
74
75 #[must_use]
86 pub const fn point_at(self, t: f64) -> Point2 {
87 self.start.lerp(self.end, t)
88 }
89
90 #[must_use]
103 pub const fn reversed(self) -> Self {
104 Self::new(self.end, self.start)
105 }
106
107 #[must_use]
109 pub fn is_degenerate(self) -> bool {
110 self.length_squared() == 0.0
111 }
112
113 pub fn is_degenerate_with_tolerance(self, tolerance: f64) -> Result<bool, GeometryError> {
132 let tolerance = GeometryError::validate_tolerance(tolerance)?;
133
134 Ok(self.length_squared() <= tolerance * tolerance)
135 }
136
137 #[must_use]
139 pub const fn aabb(self) -> Aabb2 {
140 Aabb2::from_points(self.start, self.end)
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::Segment2;
147 use crate::{error::GeometryError, point::Point2, vector::Vector2};
148
149 fn approx_eq(left: f64, right: f64) -> bool {
150 (left - right).abs() < 1.0e-10
151 }
152
153 #[test]
154 fn constructs_segments() {
155 let start = Point2::new(0.0, 0.0);
156 let end = Point2::new(1.0, 1.0);
157
158 assert_eq!(Segment2::new(start, end).start(), start);
159 assert_eq!(Segment2::new(start, end).end(), end);
160 }
161
162 #[test]
163 fn constructs_segments_with_try_new() {
164 let start = Point2::new(0.0, 0.0);
165 let end = Point2::new(1.0, 1.0);
166
167 assert_eq!(Segment2::try_new(start, end), Ok(Segment2::new(start, end)));
168 }
169
170 #[test]
171 fn rejects_non_finite_segment_points() {
172 assert_eq!(
173 Segment2::try_new(Point2::new(0.0, 0.0), Point2::new(1.0, f64::INFINITY)),
174 Err(GeometryError::NonFiniteComponent {
175 type_name: "Point2",
176 component: "y",
177 value: f64::INFINITY,
178 })
179 );
180 }
181
182 #[test]
183 fn computes_length() {
184 let segment = Segment2::new(Point2::new(0.0, 0.0), Point2::new(3.0, 4.0));
185
186 assert!(approx_eq(segment.length(), 5.0));
187 assert!(approx_eq(segment.length_squared(), 25.0));
188 }
189
190 #[test]
191 fn computes_midpoint() {
192 let segment = Segment2::new(Point2::new(0.0, 0.0), Point2::new(4.0, 2.0));
193
194 assert_eq!(segment.midpoint(), Point2::new(2.0, 1.0));
195 assert_eq!(segment.point_at(0.25), Point2::new(1.0, 0.5));
196 }
197
198 #[test]
199 fn computes_vector() {
200 let segment = Segment2::new(Point2::new(1.0, 2.0), Point2::new(4.0, 6.0));
201
202 assert_eq!(segment.vector(), Vector2::new(3.0, 4.0));
203 assert_eq!(segment.start(), Point2::new(1.0, 2.0));
204 assert_eq!(segment.end(), Point2::new(4.0, 6.0));
205 assert_eq!(
206 segment.reversed(),
207 Segment2::new(Point2::new(4.0, 6.0), Point2::new(1.0, 2.0))
208 );
209 }
210
211 #[test]
212 fn detects_degenerate_segments() {
213 let segment = Segment2::new(Point2::new(2.0, 2.0), Point2::new(2.0, 2.0));
214
215 assert!(segment.is_degenerate());
216 assert_eq!(segment.is_degenerate_with_tolerance(0.0), Ok(true));
217 assert_eq!(
218 segment.is_degenerate_with_tolerance(-1.0),
219 Err(GeometryError::NegativeTolerance(-1.0))
220 );
221 }
222
223 #[test]
224 fn computes_segment_bounds() {
225 let segment = Segment2::new(Point2::new(4.0, 1.0), Point2::new(1.0, 3.0));
226
227 assert_eq!(segment.aabb().min(), Point2::new(1.0, 1.0));
228 assert_eq!(segment.aabb().max(), Point2::new(4.0, 3.0));
229 }
230}