1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_bounds::Aabb2;
5use use_coordinate::GeometryError;
6use use_point::Point2;
7use use_vector::Vector2;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Segment2 {
12 start: Point2,
13 end: Point2,
14}
15
16impl Segment2 {
17 #[must_use]
19 pub const fn new(start: Point2, end: Point2) -> Self {
20 Self { start, end }
21 }
22
23 pub fn try_new(start: Point2, end: Point2) -> Result<Self, GeometryError> {
30 Ok(Self::new(start.validate()?, end.validate()?))
31 }
32
33 #[must_use]
35 pub const fn start(self) -> Point2 {
36 self.start
37 }
38
39 #[must_use]
41 pub const fn end(self) -> Point2 {
42 self.end
43 }
44
45 #[must_use]
47 pub fn length(self) -> f64 {
48 self.start.distance_to(self.end)
49 }
50
51 #[must_use]
53 pub fn length_squared(self) -> f64 {
54 self.start.distance_squared_to(self.end)
55 }
56
57 #[must_use]
59 pub const fn midpoint(self) -> Point2 {
60 self.start.midpoint(self.end)
61 }
62
63 #[must_use]
65 pub fn vector(self) -> Vector2 {
66 self.end - self.start
67 }
68
69 #[must_use]
71 pub const fn point_at(self, t: f64) -> Point2 {
72 self.start.lerp(self.end, t)
73 }
74
75 #[must_use]
77 pub const fn reversed(self) -> Self {
78 Self::new(self.end, self.start)
79 }
80
81 #[must_use]
83 pub fn is_degenerate(self) -> bool {
84 self.length_squared() == 0.0
85 }
86
87 pub fn is_degenerate_with_tolerance(self, tolerance: f64) -> Result<bool, GeometryError> {
96 let tolerance = GeometryError::validate_tolerance(tolerance)?;
97
98 Ok(self.length_squared() <= tolerance * tolerance)
99 }
100
101 #[must_use]
103 pub const fn aabb(self) -> Aabb2 {
104 Aabb2::from_points(self.start, self.end)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::Segment2;
111 use use_coordinate::GeometryError;
112 use use_point::Point2;
113 use use_vector::Vector2;
114
115 fn approx_eq(left: f64, right: f64) -> bool {
116 (left - right).abs() < 1.0e-10
117 }
118
119 #[test]
120 fn constructs_segments() {
121 let start = Point2::new(0.0, 0.0);
122 let end = Point2::new(1.0, 1.0);
123
124 assert_eq!(Segment2::new(start, end).start(), start);
125 assert_eq!(Segment2::new(start, end).end(), end);
126 }
127
128 #[test]
129 fn constructs_segments_with_try_new() {
130 let start = Point2::new(0.0, 0.0);
131 let end = Point2::new(1.0, 1.0);
132
133 assert_eq!(Segment2::try_new(start, end), Ok(Segment2::new(start, end)));
134 }
135
136 #[test]
137 fn rejects_non_finite_segment_points() {
138 assert_eq!(
139 Segment2::try_new(Point2::new(0.0, 0.0), Point2::new(1.0, f64::INFINITY)),
140 Err(GeometryError::NonFiniteComponent {
141 type_name: "Point2",
142 component: "y",
143 value: f64::INFINITY,
144 })
145 );
146 }
147
148 #[test]
149 fn computes_length() {
150 let segment = Segment2::new(Point2::new(0.0, 0.0), Point2::new(3.0, 4.0));
151
152 assert!(approx_eq(segment.length(), 5.0));
153 assert!(approx_eq(segment.length_squared(), 25.0));
154 }
155
156 #[test]
157 fn computes_midpoint() {
158 let segment = Segment2::new(Point2::new(0.0, 0.0), Point2::new(4.0, 2.0));
159
160 assert_eq!(segment.midpoint(), Point2::new(2.0, 1.0));
161 assert_eq!(segment.point_at(0.25), Point2::new(1.0, 0.5));
162 }
163
164 #[test]
165 fn computes_vector() {
166 let segment = Segment2::new(Point2::new(1.0, 2.0), Point2::new(4.0, 6.0));
167
168 assert_eq!(segment.vector(), Vector2::new(3.0, 4.0));
169 assert_eq!(segment.start(), Point2::new(1.0, 2.0));
170 assert_eq!(segment.end(), Point2::new(4.0, 6.0));
171 assert_eq!(
172 segment.reversed(),
173 Segment2::new(Point2::new(4.0, 6.0), Point2::new(1.0, 2.0))
174 );
175 }
176
177 #[test]
178 fn detects_degenerate_segments() {
179 let segment = Segment2::new(Point2::new(2.0, 2.0), Point2::new(2.0, 2.0));
180
181 assert!(segment.is_degenerate());
182 assert_eq!(segment.is_degenerate_with_tolerance(0.0), Ok(true));
183 assert_eq!(
184 segment.is_degenerate_with_tolerance(-1.0),
185 Err(GeometryError::NegativeTolerance(-1.0))
186 );
187 }
188
189 #[test]
190 fn computes_segment_bounds() {
191 let segment = Segment2::new(Point2::new(4.0, 1.0), Point2::new(1.0, 3.0));
192
193 assert_eq!(segment.aabb().min(), Point2::new(1.0, 1.0));
194 assert_eq!(segment.aabb().max(), Point2::new(4.0, 3.0));
195 }
196}