1use core::ops::{Add, Sub};
2
3use crate::{aabb::Aabb2, error::GeometryError, vector::Vector2};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct Point2 {
8 x: f64,
10 y: f64,
12}
13
14impl Point2 {
15 #[must_use]
17 pub const fn new(x: f64, y: f64) -> Self {
18 Self { x, y }
19 }
20
21 #[must_use]
23 pub const fn x(&self) -> f64 {
24 self.x
25 }
26
27 #[must_use]
29 pub const fn y(&self) -> f64 {
30 self.y
31 }
32
33 pub const fn try_new(x: f64, y: f64) -> Result<Self, GeometryError> {
55 if !x.is_finite() {
56 return Err(GeometryError::non_finite_component("Point2", "x", x));
57 }
58
59 if !y.is_finite() {
60 return Err(GeometryError::non_finite_component("Point2", "y", y));
61 }
62
63 Ok(Self::new(x, y))
64 }
65
66 pub const fn validate(self) -> Result<Self, GeometryError> {
83 Self::try_new(self.x, self.y)
84 }
85
86 #[must_use]
88 pub const fn is_finite(self) -> bool {
89 self.x.is_finite() && self.y.is_finite()
90 }
91
92 #[must_use]
94 pub const fn origin() -> Self {
95 Self::new(0.0, 0.0)
96 }
97
98 #[must_use]
111 pub fn distance_to(self, other: Self) -> f64 {
112 self.distance_squared_to(other).sqrt()
113 }
114
115 #[must_use]
128 pub fn distance_squared_to(self, other: Self) -> f64 {
129 let delta_x = other.x - self.x;
130 let delta_y = other.y - self.y;
131
132 delta_x.mul_add(delta_x, delta_y * delta_y)
133 }
134
135 #[must_use]
148 pub const fn midpoint(self, other: Self) -> Self {
149 Self::new(self.x.midpoint(other.x), self.y.midpoint(other.y))
150 }
151
152 #[must_use]
165 pub const fn lerp(self, other: Self, t: f64) -> Self {
166 Self::new(
167 self.x + ((other.x - self.x) * t),
168 self.y + ((other.y - self.y) * t),
169 )
170 }
171
172 #[must_use]
185 pub const fn translate(self, vector: Vector2) -> Self {
186 Self::new(self.x + vector.x(), self.y + vector.y())
187 }
188
189 #[must_use]
191 pub const fn aabb(self) -> Aabb2 {
192 Aabb2::from_points(self, self)
193 }
194}
195
196impl Add<Vector2> for Point2 {
197 type Output = Self;
198
199 fn add(self, rhs: Vector2) -> Self::Output {
200 Self::new(self.x + rhs.x(), self.y + rhs.y())
201 }
202}
203
204impl Sub<Vector2> for Point2 {
205 type Output = Self;
206
207 fn sub(self, rhs: Vector2) -> Self::Output {
208 Self::new(self.x - rhs.x(), self.y - rhs.y())
209 }
210}
211
212impl Sub<Self> for Point2 {
213 type Output = Vector2;
214
215 fn sub(self, rhs: Self) -> Self::Output {
216 Vector2::new(self.x - rhs.x, self.y - rhs.y)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::Point2;
223 use crate::{Aabb2, distance::midpoint_2d, error::GeometryError, vector::Vector2};
224
225 fn approx_eq(left: f64, right: f64) -> bool {
226 (left - right).abs() < 1.0e-10
227 }
228
229 #[test]
230 fn constructs_points() {
231 assert_eq!(
232 Point2::new(1.0, 2.0),
233 Point2::try_new(1.0, 2.0).expect("valid point")
234 );
235 }
236
237 #[test]
238 fn constructs_points_with_try_new() {
239 assert_eq!(Point2::try_new(1.0, 2.0), Ok(Point2::new(1.0, 2.0)));
240 }
241
242 #[test]
243 fn rejects_non_finite_x_coordinates() {
244 assert!(matches!(
245 Point2::try_new(f64::NAN, 2.0),
246 Err(GeometryError::NonFiniteComponent {
247 type_name: "Point2",
248 component: "x",
249 value,
250 }) if value.is_nan()
251 ));
252 }
253
254 #[test]
255 fn rejects_non_finite_y_coordinates() {
256 assert_eq!(
257 Point2::try_new(1.0, f64::INFINITY),
258 Err(GeometryError::NonFiniteComponent {
259 type_name: "Point2",
260 component: "y",
261 value: f64::INFINITY,
262 })
263 );
264 }
265
266 #[test]
267 fn validates_existing_points() {
268 assert_eq!(Point2::new(1.0, 2.0).validate(), Ok(Point2::new(1.0, 2.0)));
269 }
270
271 #[test]
272 fn returns_origin() {
273 assert_eq!(Point2::origin(), Point2::new(0.0, 0.0));
274 }
275
276 #[test]
277 fn computes_distance_to_other_point() {
278 let left = Point2::new(0.0, 0.0);
279 let right = Point2::new(3.0, 4.0);
280
281 assert!(approx_eq(left.distance_to(right), 5.0));
282 assert!(approx_eq(left.distance_squared_to(right), 25.0));
283 }
284
285 #[test]
286 fn computes_midpoints() {
287 let left = Point2::new(0.0, 0.0);
288 let right = Point2::new(4.0, 2.0);
289
290 assert_eq!(left.midpoint(right), Point2::new(2.0, 1.0));
291 assert_eq!(midpoint_2d(left, right), Point2::new(2.0, 1.0));
292 assert_eq!(left.lerp(right, 0.25), Point2::new(1.0, 0.5));
293 }
294
295 #[test]
296 fn translates_by_vector() {
297 let point = Point2::new(1.5, -2.0);
298 let offset = Vector2::new(2.0, 3.5);
299
300 assert_eq!(point.translate(offset), Point2::new(3.5, 1.5));
301 assert_eq!(point + offset, Point2::new(3.5, 1.5));
302 assert_eq!(point + offset - offset, point);
303 }
304
305 #[test]
306 fn exposes_accessors_and_finite_checks() {
307 let point = Point2::new(1.5, -2.0);
308
309 assert!(approx_eq(point.x(), 1.5));
310 assert!(approx_eq(point.y(), -2.0));
311 assert!(point.is_finite());
312 assert!(!Point2::new(f64::NAN, 0.0).is_finite());
313 }
314
315 #[test]
316 fn converts_points_to_vectors_and_bounds() {
317 let start = Point2::new(1.0, 2.0);
318 let end = Point2::new(4.0, 6.0);
319
320 assert_eq!(end - start, Vector2::new(3.0, 4.0));
321 assert_eq!(start.aabb(), Aabb2::from_points(start, start));
322 }
323}