forma_render/math/
point.rs

1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
80// Restrict the Point functions visibility as we do not want to run into the business of delivering
81// a linear algebra package.
82impl 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}