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