1use std::ops::{Add, Div, Mul, Sub};
6use std::{f32, hash};
7
8use crate::CanonBits;
9
10#[derive(Clone, Copy, Debug)]
11pub struct Point {
12 pub x: f32,
13 pub y: f32,
14}
15
16impl Eq for Point {}
17
18impl PartialEq for Point {
19 fn eq(&self, other: &Self) -> bool {
20 self.x == other.x && self.y == other.y
21 }
22}
23
24impl hash::Hash for Point {
25 fn hash<H: hash::Hasher>(&self, state: &mut H) {
26 self.x.to_canon_bits().hash(state);
27 self.y.to_canon_bits().hash(state);
28 }
29}
30
31impl Point {
32 pub fn new(x: f32, y: f32) -> Self {
33 Self { x, y }
34 }
35
36 pub fn to_array(self) -> [f32; 2] {
37 [self.x, self.y]
38 }
39}
40
41#[allow(clippy::many_single_char_names)]
42fn approx_atan2(y: f32, x: f32) -> f32 {
43 let x_abs = x.abs();
44 let y_abs = y.abs();
45
46 let a = x_abs.min(y_abs) / x_abs.max(y_abs);
47 let s = a * a;
48 let mut r = s.mul_add(-0.046_496_473, 0.159_314_22).mul_add(s, -0.327_622_77).mul_add(s * a, a);
49
50 if y_abs > x_abs {
51 r = f32::consts::FRAC_PI_2 - r;
52 }
53
54 if x < 0.0 {
55 r = f32::consts::PI - r;
56 }
57
58 if y < 0.0 {
59 r = -r;
60 }
61
62 r
63}
64
65impl Point {
67 pub(crate) fn len(self) -> f32 {
68 (self.x * self.x + self.y * self.y).sqrt()
69 }
70
71 pub(crate) fn angle(self) -> Option<f32> {
72 (self.len() >= f32::EPSILON).then(|| approx_atan2(self.y, self.x))
73 }
74}
75
76impl Add<Point> for Point {
77 type Output = Self;
78
79 #[inline]
80 fn add(self, other: Self) -> Self {
81 Self { x: self.x + other.x, y: self.y + other.y }
82 }
83}
84
85impl Sub<Point> for Point {
86 type Output = Self;
87
88 #[inline]
89 fn sub(self, other: Self) -> Self {
90 Self { x: self.x - other.x, y: self.y - other.y }
91 }
92}
93
94impl Mul<f32> for Point {
95 type Output = Self;
96
97 #[inline]
98 fn mul(self, other: f32) -> Self {
99 Self { x: self.x * other, y: self.y * other }
100 }
101}
102
103impl Div<f32> for Point {
104 type Output = Self;
105
106 #[inline]
107 fn div(self, other: f32) -> Self {
108 Self { x: self.x / other, y: self.y / other }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI};
116
117 #[test]
118 fn add() {
119 assert_eq!(
120 Point { x: 32.0, y: 126.0 },
121 Point { x: 13.0, y: 27.0 } + Point { x: 19.0, y: 99.0 }
122 );
123 }
124
125 #[test]
126 fn sub() {
127 assert_eq!(
128 Point { x: -6.0, y: -72.0 },
129 Point { x: 13.0, y: 27.0 } - Point { x: 19.0, y: 99.0 }
130 );
131 }
132
133 #[test]
134 fn mul() {
135 assert_eq!(Point { x: 3.0, y: 21.0 }, Point { x: 1.0, y: 7.0 } * 3.0);
136 }
137
138 #[test]
139 fn div() {
140 assert_eq!(Point { x: 1.0, y: 7.0 }, Point { x: 3.0, y: 21.0 } / 3.0);
141 }
142
143 #[test]
144 fn angle() {
145 assert_eq!(Some(0.0), Point { x: 1.0, y: 0.0 }.angle());
146 assert_eq!(Some(0.0), Point { x: 1e10, y: 0.0 }.angle());
147 assert_eq!(Some(PI), Point { x: -1.0, y: 0.0 }.angle());
148 assert_eq!(Some(FRAC_PI_2), Point { x: 0.0, y: 1.0 }.angle());
149 assert_eq!(Some(-FRAC_PI_2), Point { x: 0.0, y: -1.0 }.angle());
150 assert!((FRAC_PI_4 - Point { x: 1.0, y: 1.0 }.angle().unwrap()).abs() < 1e-3);
151 assert!((-FRAC_PI_4 - Point { x: 1.0, y: -1.0 }.angle().unwrap()).abs() < 1e-3);
152 }
153}