Skip to main content

ferroui_base/
point.rs

1//! Defines a point.
2
3use std::fmt;
4use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
5use std::str::FromStr;
6
7use crate::utilities::math_utilities::MathUtilities;
8use crate::utilities::span_helpers::InvariantF64;
9use crate::utilities::{FormatError, SpanStringTokenizer};
10use crate::{Matrix, Vector};
11
12/// Defines a point.
13#[derive(Clone, Copy, Debug, PartialEq, Default)]
14pub struct Point {
15    /// The X position.
16    pub x: f64,
17    /// The Y position.
18    pub y: f64,
19}
20
21impl Point {
22    /// Initializes a new instance of the [`Point`] structure.
23    #[inline]
24    pub const fn new(x: f64, y: f64) -> Self {
25        Self { x, y }
26    }
27
28    /// Computes the Euclidean distance between the two given points.
29    #[inline]
30    pub fn distance(value1: Point, value2: Point) -> f64 {
31        let distance_squared = ((value2.x - value1.x) * (value2.x - value1.x))
32            + ((value2.y - value1.y) * (value2.y - value1.y));
33        distance_squared.sqrt()
34    }
35
36    /// Parses a [`Point`] string (`"x, y"` or `"x y"`).
37    pub fn parse(s: &str) -> Result<Self, FormatError> {
38        SpanStringTokenizer::with_message(s, "Invalid Point.")
39            .scope(|t| Ok(Point::new(t.read_double()?, t.read_double()?)))
40    }
41
42    /// Returns a boolean indicating whether the point is equal to the other given
43    /// point (bitwise equality of the coordinates is *not* required: this is `==`).
44    #[inline]
45    pub fn equals(&self, other: Point) -> bool {
46        self.x == other.x && self.y == other.y
47    }
48
49    /// Returns a boolean indicating whether the point is equal to the other given
50    /// point (numerically, within a relative epsilon).
51    #[inline]
52    pub fn nearly_equals(&self, other: Point) -> bool {
53        MathUtilities::are_close(self.x, other.x) && MathUtilities::are_close(self.y, other.y)
54    }
55
56    /// Transforms the point by a matrix.
57    #[inline]
58    pub fn transform(&self, transform: Matrix) -> Point {
59        transform.transform(*self)
60    }
61
62    /// Returns a new point with the specified X coordinate.
63    #[inline]
64    pub const fn with_x(&self, x: f64) -> Point {
65        Point::new(x, self.y)
66    }
67
68    /// Returns a new point with the specified Y coordinate.
69    #[inline]
70    pub const fn with_y(&self, y: f64) -> Point {
71        Point::new(self.x, y)
72    }
73
74    /// Deconstructs the point into its X and Y coordinates.
75    #[inline]
76    pub const fn deconstruct(&self) -> (f64, f64) {
77        (self.x, self.y)
78    }
79}
80
81/// Converts the [`Point`] to a [`Vector`].
82impl From<Point> for Vector {
83    #[inline]
84    fn from(p: Point) -> Vector {
85        Vector::new(p.x, p.y)
86    }
87}
88
89impl From<Point> for (f64, f64) {
90    #[inline]
91    fn from(p: Point) -> Self {
92        (p.x, p.y)
93    }
94}
95
96impl Neg for Point {
97    type Output = Point;
98    #[inline]
99    fn neg(self) -> Point {
100        Point::new(-self.x, -self.y)
101    }
102}
103
104impl Add for Point {
105    type Output = Point;
106    #[inline]
107    fn add(self, b: Point) -> Point {
108        Point::new(self.x + b.x, self.y + b.y)
109    }
110}
111
112impl Add<Vector> for Point {
113    type Output = Point;
114    #[inline]
115    fn add(self, b: Vector) -> Point {
116        Point::new(self.x + b.x, self.y + b.y)
117    }
118}
119
120impl Sub for Point {
121    type Output = Point;
122    #[inline]
123    fn sub(self, b: Point) -> Point {
124        Point::new(self.x - b.x, self.y - b.y)
125    }
126}
127
128impl Sub<Vector> for Point {
129    type Output = Point;
130    #[inline]
131    fn sub(self, b: Vector) -> Point {
132        Point::new(self.x - b.x, self.y - b.y)
133    }
134}
135
136impl Mul<f64> for Point {
137    type Output = Point;
138    #[inline]
139    fn mul(self, k: f64) -> Point {
140        Point::new(self.x * k, self.y * k)
141    }
142}
143
144impl Mul<Point> for f64 {
145    type Output = Point;
146    #[inline]
147    fn mul(self, p: Point) -> Point {
148        Point::new(p.x * self, p.y * self)
149    }
150}
151
152impl Div<f64> for Point {
153    type Output = Point;
154    #[inline]
155    fn div(self, k: f64) -> Point {
156        Point::new(self.x / k, self.y / k)
157    }
158}
159
160/// Applies a matrix to a point.
161impl Mul<Matrix> for Point {
162    type Output = Point;
163    #[inline]
164    fn mul(self, matrix: Matrix) -> Point {
165        matrix.transform(self)
166    }
167}
168
169impl AddAssign for Point {
170    #[inline]
171    fn add_assign(&mut self, b: Point) {
172        *self = *self + b;
173    }
174}
175
176impl AddAssign<Vector> for Point {
177    #[inline]
178    fn add_assign(&mut self, b: Vector) {
179        *self = *self + b;
180    }
181}
182
183impl SubAssign for Point {
184    #[inline]
185    fn sub_assign(&mut self, b: Point) {
186        *self = *self - b;
187    }
188}
189
190impl SubAssign<Vector> for Point {
191    #[inline]
192    fn sub_assign(&mut self, b: Vector) {
193        *self = *self - b;
194    }
195}
196
197impl MulAssign<f64> for Point {
198    #[inline]
199    fn mul_assign(&mut self, k: f64) {
200        *self = *self * k;
201    }
202}
203
204impl DivAssign<f64> for Point {
205    #[inline]
206    fn div_assign(&mut self, k: f64) {
207        *self = *self / k;
208    }
209}
210
211impl MulAssign<Matrix> for Point {
212    #[inline]
213    fn mul_assign(&mut self, matrix: Matrix) {
214        *self = *self * matrix;
215    }
216}
217
218impl FromStr for Point {
219    type Err = FormatError;
220    #[inline]
221    fn from_str(s: &str) -> Result<Self, FormatError> {
222        Point::parse(s)
223    }
224}
225
226impl fmt::Display for Point {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        write!(f, "{}, {}", InvariantF64(self.x), InvariantF64(self.y))
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    // The reference test-suite has no dedicated tests for this type; these cover
235    // the members that the other suites do not exercise.
236    use super::*;
237
238    #[test]
239    fn parse_and_display_round_trip() {
240        assert_eq!(Point::parse("1.5, -2").unwrap(), Point::new(1.5, -2.0));
241        assert_eq!("3 4".parse::<Point>().unwrap(), Point::new(3.0, 4.0));
242        assert_eq!(Point::new(1.5, -2.0).to_string(), "1.5, -2");
243        assert_eq!(Point::parse("1").unwrap_err().message(), "Invalid Point.");
244        assert!(Point::parse("1,2,3").is_err());
245        assert!(Point::parse("1,,2").is_err());
246        assert!(Point::parse("").is_err());
247    }
248
249    #[test]
250    fn operators() {
251        let p = Point::new(1.0, 2.0);
252        assert_eq!(-p, Point::new(-1.0, -2.0));
253        assert_eq!(p + Point::new(1.0, 1.0), Point::new(2.0, 3.0));
254        assert_eq!(p + Vector::new(1.0, 1.0), Point::new(2.0, 3.0));
255        assert_eq!(p - Point::new(1.0, 1.0), Point::new(0.0, 1.0));
256        assert_eq!(p - Vector::new(1.0, 1.0), Point::new(0.0, 1.0));
257        assert_eq!(p * 2.0, Point::new(2.0, 4.0));
258        assert_eq!(2.0 * p, Point::new(2.0, 4.0));
259        assert_eq!(p / 2.0, Point::new(0.5, 1.0));
260        assert_eq!(
261            p * Matrix::create_translation(1.0, 1.0),
262            Point::new(2.0, 3.0)
263        );
264        assert_eq!(Vector::from(p), Vector::new(1.0, 2.0));
265        assert_eq!(
266            Point::distance(Point::new(0.0, 0.0), Point::new(3.0, 4.0)),
267            5.0
268        );
269    }
270
271    #[test]
272    fn equality_is_exact_and_nearly_equals_is_tolerant() {
273        let a = Point::new(0.1 + 0.2, 1.0);
274        let b = Point::new(0.3, 1.0);
275        assert_ne!(a, b);
276        assert!(a.nearly_equals(b));
277        assert_ne!(Point::new(f64::NAN, 0.0), Point::new(f64::NAN, 0.0));
278    }
279}