1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::ops::{Add, Sub};
5
6use use_coordinate::GeometryError;
7use use_vector::Vector2;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Point2 {
12 x: f64,
13 y: f64,
14}
15
16impl Point2 {
17 #[must_use]
19 pub const fn new(x: f64, y: f64) -> Self {
20 Self { x, y }
21 }
22
23 #[must_use]
25 pub const fn x(&self) -> f64 {
26 self.x
27 }
28
29 #[must_use]
31 pub const fn y(&self) -> f64 {
32 self.y
33 }
34
35 pub const fn try_new(x: f64, y: f64) -> Result<Self, GeometryError> {
42 if !x.is_finite() {
43 return Err(GeometryError::non_finite_component("Point2", "x", x));
44 }
45
46 if !y.is_finite() {
47 return Err(GeometryError::non_finite_component("Point2", "y", y));
48 }
49
50 Ok(Self::new(x, y))
51 }
52
53 pub const fn validate(self) -> Result<Self, GeometryError> {
60 Self::try_new(self.x, self.y)
61 }
62
63 #[must_use]
65 pub const fn is_finite(self) -> bool {
66 self.x.is_finite() && self.y.is_finite()
67 }
68
69 #[must_use]
71 pub const fn origin() -> Self {
72 Self::new(0.0, 0.0)
73 }
74
75 #[must_use]
77 pub fn distance_to(self, other: Self) -> f64 {
78 self.distance_squared_to(other).sqrt()
79 }
80
81 #[must_use]
83 pub fn distance_squared_to(self, other: Self) -> f64 {
84 let delta_x = other.x - self.x;
85 let delta_y = other.y - self.y;
86
87 delta_x.mul_add(delta_x, delta_y * delta_y)
88 }
89
90 #[must_use]
92 pub const fn midpoint(self, other: Self) -> Self {
93 Self::new(self.x.midpoint(other.x), self.y.midpoint(other.y))
94 }
95
96 #[must_use]
98 pub const fn lerp(self, other: Self, t: f64) -> Self {
99 Self::new(
100 self.x + ((other.x - self.x) * t),
101 self.y + ((other.y - self.y) * t),
102 )
103 }
104
105 #[must_use]
107 pub const fn translate(self, vector: Vector2) -> Self {
108 Self::new(self.x + vector.x(), self.y + vector.y())
109 }
110}
111
112impl Add<Vector2> for Point2 {
113 type Output = Self;
114
115 fn add(self, rhs: Vector2) -> Self::Output {
116 Self::new(self.x + rhs.x(), self.y + rhs.y())
117 }
118}
119
120impl Sub<Vector2> for Point2 {
121 type Output = Self;
122
123 fn sub(self, rhs: Vector2) -> Self::Output {
124 Self::new(self.x - rhs.x(), self.y - rhs.y())
125 }
126}
127
128impl Sub<Self> for Point2 {
129 type Output = Vector2;
130
131 fn sub(self, rhs: Self) -> Self::Output {
132 Vector2::new(self.x - rhs.x, self.y - rhs.y)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::Point2;
139 use use_coordinate::GeometryError;
140 use use_vector::Vector2;
141
142 fn approx_eq(left: f64, right: f64) -> bool {
143 (left - right).abs() < 1.0e-10
144 }
145
146 #[test]
147 fn constructs_points() {
148 assert_eq!(
149 Point2::new(1.0, 2.0),
150 Point2::try_new(1.0, 2.0).expect("valid point")
151 );
152 }
153
154 #[test]
155 fn rejects_non_finite_coordinates() {
156 assert!(matches!(
157 Point2::try_new(f64::NAN, 2.0),
158 Err(GeometryError::NonFiniteComponent {
159 type_name: "Point2",
160 component: "x",
161 value,
162 }) if value.is_nan()
163 ));
164 assert_eq!(
165 Point2::try_new(1.0, f64::INFINITY),
166 Err(GeometryError::NonFiniteComponent {
167 type_name: "Point2",
168 component: "y",
169 value: f64::INFINITY,
170 })
171 );
172 }
173
174 #[test]
175 fn computes_distance_midpoint_and_lerp() {
176 let left = Point2::new(0.0, 0.0);
177 let right = Point2::new(4.0, 2.0);
178
179 assert!(approx_eq(left.distance_to(Point2::new(3.0, 4.0)), 5.0));
180 assert!(approx_eq(
181 left.distance_squared_to(Point2::new(3.0, 4.0)),
182 25.0
183 ));
184 assert_eq!(left.midpoint(right), Point2::new(2.0, 1.0));
185 assert_eq!(left.lerp(right, 0.25), Point2::new(1.0, 0.5));
186 }
187
188 #[test]
189 fn translates_points_and_builds_differences() {
190 let point = Point2::new(1.5, -2.0);
191 let offset = Vector2::new(2.0, 3.5);
192
193 assert_eq!(point.translate(offset), Point2::new(3.5, 1.5));
194 assert_eq!(point + offset, Point2::new(3.5, 1.5));
195 assert_eq!(point + offset - offset, point);
196 assert_eq!(
197 Point2::new(4.0, 6.0) - Point2::new(1.0, 2.0),
198 Vector2::new(3.0, 4.0)
199 );
200 }
201
202 #[test]
203 fn exposes_accessors_and_origin() {
204 let point = Point2::new(1.5, -2.0);
205
206 assert!(approx_eq(point.x(), 1.5));
207 assert!(approx_eq(point.y(), -2.0));
208 assert!(point.is_finite());
209 assert!(!Point2::new(f64::NAN, 0.0).is_finite());
210 assert_eq!(Point2::origin(), Point2::new(0.0, 0.0));
211 }
212}