Skip to main content

bymsdfgen_core/math/
vector2.rs

1//! A 2-dimensional Euclidean vector backed by `f64`.
2//!
3//! Port of `core/Vector2.hpp`. Unlike the C++ original — where `Point2` is a bare
4//! `typedef` for `Vector2` and every coordinate space collapses into the same type —
5//! the *semantic* distinction between coordinate spaces is layered on top via the
6//! zero-cost newtypes in [`crate::math::typed`]. `Vector2` itself remains the plain
7//! workhorse used inside the hot geometry/distance loops.
8
9use std::ops::{Add, Div, Mul, Neg, Sub};
10
11/// A 2D vector / point in a single, untyped coordinate space.
12#[derive(Debug, Clone, Copy, PartialEq, Default)]
13pub struct Vector2 {
14    pub x: f64,
15    pub y: f64,
16}
17
18/// Semantic alias: a `Vector2` used as a position rather than a direction.
19pub type Point2 = Vector2;
20
21impl Vector2 {
22    pub const ZERO: Vector2 = Vector2 { x: 0.0, y: 0.0 };
23
24    #[inline]
25    pub const fn new(x: f64, y: f64) -> Self {
26        Vector2 { x, y }
27    }
28
29    /// Both components set to the same value (mirrors `Vector2(double val)`).
30    #[inline]
31    pub const fn splat(v: f64) -> Self {
32        Vector2 { x: v, y: v }
33    }
34
35    #[inline]
36    pub fn squared_length(self) -> f64 {
37        self.x * self.x + self.y * self.y
38    }
39
40    #[inline]
41    pub fn length(self) -> f64 {
42        self.squared_length().sqrt()
43    }
44
45    /// Returns the unit-length vector in the same direction.
46    ///
47    /// For a zero vector, returns `(0, 0)` when `allow_zero` is true, otherwise
48    /// `(0, 1)` — matching the original's `Vector2(0, !allowZero)` behaviour.
49    #[inline]
50    pub fn normalize(self, allow_zero: bool) -> Vector2 {
51        let len = self.length();
52        if len != 0.0 {
53            Vector2::new(self.x / len, self.y / len)
54        } else {
55            Vector2::new(0.0, if allow_zero { 0.0 } else { 1.0 })
56        }
57    }
58
59    /// A vector of the same length orthogonal to this one.
60    #[inline]
61    pub fn orthogonal(self, polarity: bool) -> Vector2 {
62        if polarity {
63            Vector2::new(-self.y, self.x)
64        } else {
65            Vector2::new(self.y, -self.x)
66        }
67    }
68
69    /// A unit-length vector orthogonal to this one.
70    #[inline]
71    pub fn orthonormal(self, polarity: bool, allow_zero: bool) -> Vector2 {
72        let len = self.length();
73        if len != 0.0 {
74            if polarity {
75                Vector2::new(-self.y / len, self.x / len)
76            } else {
77                Vector2::new(self.y / len, -self.x / len)
78            }
79        } else {
80            let z = if allow_zero { 0.0 } else { 1.0 };
81            if polarity {
82                Vector2::new(0.0, z)
83            } else {
84                Vector2::new(0.0, -z)
85            }
86        }
87    }
88
89    /// True when the vector is non-zero (mirrors `operator bool`).
90    #[inline]
91    pub fn is_nonzero(self) -> bool {
92        self.x != 0.0 || self.y != 0.0
93    }
94}
95
96/// Dot product.
97#[inline]
98pub fn dot(a: Vector2, b: Vector2) -> f64 {
99    a.x * b.x + a.y * b.y
100}
101
102/// 2D cross product (returns the scalar z-component).
103#[inline]
104pub fn cross(a: Vector2, b: Vector2) -> f64 {
105    a.x * b.y - a.y * b.x
106}
107
108impl Add for Vector2 {
109    type Output = Vector2;
110    #[inline]
111    fn add(self, o: Vector2) -> Vector2 {
112        Vector2::new(self.x + o.x, self.y + o.y)
113    }
114}
115
116impl Sub for Vector2 {
117    type Output = Vector2;
118    #[inline]
119    fn sub(self, o: Vector2) -> Vector2 {
120        Vector2::new(self.x - o.x, self.y - o.y)
121    }
122}
123
124impl Neg for Vector2 {
125    type Output = Vector2;
126    #[inline]
127    fn neg(self) -> Vector2 {
128        Vector2::new(-self.x, -self.y)
129    }
130}
131
132// Component-wise vector * vector and vector / vector.
133impl Mul for Vector2 {
134    type Output = Vector2;
135    #[inline]
136    fn mul(self, o: Vector2) -> Vector2 {
137        Vector2::new(self.x * o.x, self.y * o.y)
138    }
139}
140
141impl Div for Vector2 {
142    type Output = Vector2;
143    #[inline]
144    fn div(self, o: Vector2) -> Vector2 {
145        Vector2::new(self.x / o.x, self.y / o.y)
146    }
147}
148
149// Scalar on the right.
150impl Mul<f64> for Vector2 {
151    type Output = Vector2;
152    #[inline]
153    fn mul(self, s: f64) -> Vector2 {
154        Vector2::new(self.x * s, self.y * s)
155    }
156}
157
158impl Div<f64> for Vector2 {
159    type Output = Vector2;
160    #[inline]
161    fn div(self, s: f64) -> Vector2 {
162        Vector2::new(self.x / s, self.y / s)
163    }
164}
165
166// Scalar on the left (`a * v`).
167impl Mul<Vector2> for f64 {
168    type Output = Vector2;
169    #[inline]
170    fn mul(self, v: Vector2) -> Vector2 {
171        Vector2::new(self * v.x, self * v.y)
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn length_and_normalize() {
181        let v = Vector2::new(3.0, 4.0);
182        assert_eq!(v.length(), 5.0);
183        let n = v.normalize(false);
184        assert!((n.length() - 1.0).abs() < 1e-12);
185    }
186
187    #[test]
188    fn zero_normalize() {
189        assert_eq!(Vector2::ZERO.normalize(true), Vector2::ZERO);
190        assert_eq!(Vector2::ZERO.normalize(false), Vector2::new(0.0, 1.0));
191    }
192
193    #[test]
194    fn dot_and_cross() {
195        let a = Vector2::new(1.0, 2.0);
196        let b = Vector2::new(3.0, 4.0);
197        assert_eq!(dot(a, b), 11.0);
198        assert_eq!(cross(a, b), -2.0);
199    }
200
201    #[test]
202    fn orthogonal_polarity() {
203        let v = Vector2::new(1.0, 0.0);
204        assert_eq!(v.orthogonal(true), Vector2::new(0.0, 1.0));
205        assert_eq!(v.orthogonal(false), Vector2::new(0.0, -1.0));
206    }
207}