1use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
7#[serde(from = "[f64; 2]", into = "[f64; 2]")]
8pub struct Point {
9 pub x: f64,
11 pub y: f64,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
17#[serde(from = "[f64; 2]", into = "[f64; 2]")]
18pub struct Vector {
19 pub x: f64,
21 pub y: f64,
23}
24
25impl From<[f64; 2]> for Point {
26 fn from(v: [f64; 2]) -> Self {
27 Self { x: v[0], y: v[1] }
28 }
29}
30impl From<Point> for [f64; 2] {
31 fn from(p: Point) -> Self {
32 [p.x, p.y]
33 }
34}
35impl From<[f64; 2]> for Vector {
36 fn from(v: [f64; 2]) -> Self {
37 Self { x: v[0], y: v[1] }
38 }
39}
40impl From<Vector> for [f64; 2] {
41 fn from(p: Vector) -> Self {
42 [p.x, p.y]
43 }
44}
45
46impl Point {
47 pub const ORIGIN: Self = Self { x: 0.0, y: 0.0 };
49
50 #[must_use]
52 pub const fn new(x: f64, y: f64) -> Self {
53 Self { x, y }
54 }
55
56 #[must_use]
58 pub fn is_finite(self) -> bool {
59 self.x.is_finite() && self.y.is_finite()
60 }
61
62 #[must_use]
64 pub fn distance(self, other: Self) -> f64 {
65 (other - self).length()
66 }
67
68 #[must_use]
70 pub fn distance_sq(self, other: Self) -> f64 {
71 (other - self).length_sq()
72 }
73
74 #[must_use]
76 pub fn lerp(self, other: Self, t: f64) -> Self {
77 Self::new(self.x + (other.x - self.x) * t, self.y + (other.y - self.y) * t)
78 }
79
80 #[must_use]
82 pub fn midpoint(self, other: Self) -> Self {
83 Self::new((self.x + other.x) * 0.5, (self.y + other.y) * 0.5)
84 }
85
86 #[must_use]
88 pub const fn to_vector(self) -> Vector {
89 Vector::new(self.x, self.y)
90 }
91
92 pub(crate) fn coord(self) -> robust::Coord<f64> {
93 robust::Coord { x: self.x, y: self.y }
94 }
95}
96
97impl Vector {
98 pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
100
101 #[must_use]
103 pub const fn new(x: f64, y: f64) -> Self {
104 Self { x, y }
105 }
106
107 #[must_use]
109 pub fn from_angle(angle: f64) -> Self {
110 let (s, c) = crate::math::sin_cos(angle);
111 Self::new(c, s)
112 }
113
114 #[must_use]
116 pub fn is_finite(self) -> bool {
117 self.x.is_finite() && self.y.is_finite()
118 }
119
120 #[must_use]
122 pub fn length(self) -> f64 {
123 crate::math::hypot(self.x, self.y)
124 }
125
126 #[must_use]
128 pub fn length_sq(self) -> f64 {
129 self.x * self.x + self.y * self.y
130 }
131
132 #[must_use]
134 pub fn dot(self, o: Self) -> f64 {
135 self.x * o.x + self.y * o.y
136 }
137
138 #[must_use]
140 pub fn cross(self, o: Self) -> f64 {
141 self.x * o.y - self.y * o.x
142 }
143
144 #[must_use]
146 pub fn normalize(self) -> Option<Self> {
147 let len = self.length();
148 if len > 0.0 && len.is_finite() { Some(Self::new(self.x / len, self.y / len)) } else { None }
149 }
150
151 #[must_use]
153 pub const fn perp(self) -> Self {
154 Self::new(-self.y, self.x)
155 }
156
157 #[must_use]
159 pub fn angle(self) -> f64 {
160 crate::math::atan2(self.y, self.x)
161 }
162
163 #[must_use]
165 pub fn rotate(self, angle: f64) -> Self {
166 let (s, c) = crate::math::sin_cos(angle);
167 Self::new(self.x * c - self.y * s, self.x * s + self.y * c)
168 }
169
170 #[must_use]
172 pub const fn to_point(self) -> Point {
173 Point::new(self.x, self.y)
174 }
175}
176
177impl Sub for Point {
178 type Output = Vector;
179 fn sub(self, o: Self) -> Vector {
180 Vector::new(self.x - o.x, self.y - o.y)
181 }
182}
183impl Add<Vector> for Point {
184 type Output = Self;
185 fn add(self, v: Vector) -> Self {
186 Self::new(self.x + v.x, self.y + v.y)
187 }
188}
189impl AddAssign<Vector> for Point {
190 fn add_assign(&mut self, v: Vector) {
191 self.x += v.x;
192 self.y += v.y;
193 }
194}
195impl Sub<Vector> for Point {
196 type Output = Self;
197 fn sub(self, v: Vector) -> Self {
198 Self::new(self.x - v.x, self.y - v.y)
199 }
200}
201impl SubAssign<Vector> for Point {
202 fn sub_assign(&mut self, v: Vector) {
203 self.x -= v.x;
204 self.y -= v.y;
205 }
206}
207impl Add for Vector {
208 type Output = Self;
209 fn add(self, o: Self) -> Self {
210 Self::new(self.x + o.x, self.y + o.y)
211 }
212}
213impl AddAssign for Vector {
214 fn add_assign(&mut self, o: Self) {
215 self.x += o.x;
216 self.y += o.y;
217 }
218}
219impl Sub for Vector {
220 type Output = Self;
221 fn sub(self, o: Self) -> Self {
222 Self::new(self.x - o.x, self.y - o.y)
223 }
224}
225impl Neg for Vector {
226 type Output = Self;
227 fn neg(self) -> Self {
228 Self::new(-self.x, -self.y)
229 }
230}
231impl Mul<f64> for Vector {
232 type Output = Self;
233 fn mul(self, s: f64) -> Self {
234 Self::new(self.x * s, self.y * s)
235 }
236}
237impl Mul<Vector> for f64 {
238 type Output = Vector;
239 fn mul(self, v: Vector) -> Vector {
240 Vector::new(self * v.x, self * v.y)
241 }
242}
243impl Div<f64> for Vector {
244 type Output = Self;
245 fn div(self, s: f64) -> Self {
246 Self::new(self.x / s, self.y / s)
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum Orientation {
253 CounterClockwise,
255 Clockwise,
257 Collinear,
259}
260
261#[must_use]
263pub fn orientation(a: Point, b: Point, c: Point) -> Orientation {
264 let d = robust::orient2d(a.coord(), b.coord(), c.coord());
265 if d > 0.0 {
266 Orientation::CounterClockwise
267 } else if d < 0.0 {
268 Orientation::Clockwise
269 } else {
270 Orientation::Collinear
271 }
272}
273
274#[must_use]
276pub fn normalize_angle(a: f64) -> f64 {
277 let tau = core::f64::consts::TAU;
278 let r = a.rem_euclid(tau);
279 if r >= tau { 0.0 } else { r }
280}
281
282#[must_use]
284pub fn normalize_angle_signed(a: f64) -> f64 {
285 let pi = core::f64::consts::PI;
286 let r = normalize_angle(a);
287 if r > pi { r - core::f64::consts::TAU } else { r }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn serde_as_array() {
296 let p = Point::new(1.5, -2.0);
297 let s = serde_json::to_string(&p).unwrap();
298 assert_eq!(s, "[1.5,-2.0]");
299 let back: Point = serde_json::from_str(&s).unwrap();
300 assert_eq!(back, p);
301 }
302
303 #[test]
304 fn orientation_is_exact_for_nearly_collinear_points() {
305 let a = Point::new(0.5, 0.5);
307 let b = Point::new(12.0, 12.0);
308 let c = Point::new(24.0, 24.0);
309 assert_eq!(orientation(a, b, c), Orientation::Collinear);
310 let c2 = Point::new(24.0, 24.000_000_000_000_004);
311 assert_eq!(orientation(a, b, c2), Orientation::CounterClockwise);
312 }
313
314 #[test]
315 fn angle_normalization() {
316 use core::f64::consts::{PI, TAU};
317 assert!((normalize_angle(-PI / 2.0) - 1.5 * PI).abs() < 1e-15);
318 assert!(normalize_angle(TAU) < 1e-15);
319 assert!((normalize_angle_signed(1.5 * PI) + PI / 2.0).abs() < 1e-15);
320 }
321
322 #[test]
323 fn normalize_zero_is_none() {
324 assert!(Vector::ZERO.normalize().is_none());
325 assert!(Vector::new(f64::NAN, 1.0).normalize().is_none());
326 assert!(Vector::new(f64::INFINITY, 1.0).normalize().is_none());
327 }
328}