Skip to main content

ferroui_base/
vector3d.rs

1//! 3D vector with double-precision components.
2
3use std::fmt;
4use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
5use std::str::FromStr;
6
7use crate::utilities::math_utilities;
8use crate::utilities::span_helpers::InvariantF64;
9use crate::utilities::{FormatError, SpanStringTokenizer};
10
11/// 3D vector with double-precision components.
12#[derive(Clone, Copy, Debug, PartialEq, Default)]
13pub struct Vector3D {
14    pub x: f64,
15    pub y: f64,
16    pub z: f64,
17}
18
19impl Vector3D {
20    #[inline]
21    pub const fn new(x: f64, y: f64, z: f64) -> Self {
22        Self { x, y, z }
23    }
24
25    /// Parses a [`Vector3D`] string (`"x, y, z"`).
26    pub fn parse(s: &str) -> Result<Self, FormatError> {
27        SpanStringTokenizer::with_message(s, "Invalid Vector.").scope(|t| {
28            Ok(Vector3D::new(
29                t.read_double()?,
30                t.read_double()?,
31                t.read_double()?,
32            ))
33        })
34    }
35
36    /// Calculates the dot product of two vectors.
37    #[inline]
38    pub fn dot(vector1: Vector3D, vector2: Vector3D) -> f64 {
39        (vector1.x * vector2.x) + (vector1.y * vector2.y) + (vector1.z * vector2.z)
40    }
41
42    /// Adds the second to the first vector.
43    #[inline]
44    #[allow(clippy::should_implement_trait)] // the operator trait is implemented too
45    pub fn add(left: Vector3D, right: Vector3D) -> Vector3D {
46        Vector3D::new(left.x + right.x, left.y + right.y, left.z + right.z)
47    }
48
49    /// Subtracts the second from the first vector.
50    #[inline]
51    pub fn substract(left: Vector3D, right: Vector3D) -> Vector3D {
52        Vector3D::new(left.x - right.x, left.y - right.y, left.z - right.z)
53    }
54
55    /// Multiplies the first vector by the second, component-wise.
56    #[inline]
57    pub fn multiply(left: Vector3D, right: Vector3D) -> Vector3D {
58        Vector3D::new(left.x * right.x, left.y * right.y, left.z * right.z)
59    }
60
61    /// Multiplies the vector by the given scalar.
62    #[inline]
63    pub fn multiply_scalar(left: Vector3D, right: f64) -> Vector3D {
64        Vector3D::new(left.x * right, left.y * right, left.z * right)
65    }
66
67    /// Divides the first vector by the second, component-wise.
68    #[inline]
69    pub fn divide(left: Vector3D, right: Vector3D) -> Vector3D {
70        Vector3D::new(left.x / right.x, left.y / right.y, left.z / right.z)
71    }
72
73    /// Divides the vector by the given scalar.
74    #[inline]
75    pub fn divide_scalar(left: Vector3D, right: f64) -> Vector3D {
76        Vector3D::new(left.x / right, left.y / right, left.z / right)
77    }
78
79    /// Returns a vector whose elements are the absolute values of each of the
80    /// specified vector's elements.
81    #[inline]
82    pub fn abs(&self) -> Vector3D {
83        Vector3D::new(self.x.abs(), self.y.abs(), self.z.abs())
84    }
85
86    /// Restricts a vector between a minimum and a maximum value.
87    #[inline]
88    pub fn clamp(value: Vector3D, min: Vector3D, max: Vector3D) -> Vector3D {
89        Vector3D::min(Vector3D::max(value, min), max)
90    }
91
92    /// Returns a vector whose elements are the maximum of each of the pairs of
93    /// elements in two specified vectors.
94    #[inline]
95    pub fn max(left: Vector3D, right: Vector3D) -> Vector3D {
96        Vector3D::new(
97            math_utilities::max(left.x, right.x),
98            math_utilities::max(left.y, right.y),
99            math_utilities::max(left.z, right.z),
100        )
101    }
102
103    /// Returns a vector whose elements are the minimum of each of the pairs of
104    /// elements in two specified vectors.
105    #[inline]
106    pub fn min(left: Vector3D, right: Vector3D) -> Vector3D {
107        Vector3D::new(
108            math_utilities::min(left.x, right.x),
109            math_utilities::min(left.y, right.y),
110            math_utilities::min(left.z, right.z),
111        )
112    }
113
114    /// Length of the vector.
115    #[inline]
116    pub fn length(&self) -> f64 {
117        Vector3D::dot(*self, *self).sqrt()
118    }
119
120    /// Returns a normalized version of this vector.
121    #[inline]
122    pub fn normalize(value: Vector3D) -> Vector3D {
123        Vector3D::divide_scalar(value, value.length())
124    }
125
126    /// Computes the squared Euclidean distance between the two given points.
127    #[inline]
128    pub fn distance_squared(value1: Vector3D, value2: Vector3D) -> f64 {
129        let difference = Vector3D::substract(value1, value2);
130        Vector3D::dot(difference, difference)
131    }
132
133    /// Computes the Euclidean distance between the two given points.
134    #[inline]
135    pub fn distance(value1: Vector3D, value2: Vector3D) -> f64 {
136        Vector3D::distance_squared(value1, value2).sqrt()
137    }
138
139    /// Deconstructs the vector into its components.
140    #[inline]
141    pub const fn deconstruct(&self) -> (f64, f64, f64) {
142        (self.x, self.y, self.z)
143    }
144}
145
146impl From<Vector3D> for (f64, f64, f64) {
147    #[inline]
148    fn from(v: Vector3D) -> Self {
149        (v.x, v.y, v.z)
150    }
151}
152
153impl Add for Vector3D {
154    type Output = Vector3D;
155    #[inline]
156    fn add(self, right: Vector3D) -> Vector3D {
157        Vector3D::add(self, right)
158    }
159}
160
161impl Sub for Vector3D {
162    type Output = Vector3D;
163    #[inline]
164    fn sub(self, right: Vector3D) -> Vector3D {
165        Vector3D::substract(self, right)
166    }
167}
168
169impl Neg for Vector3D {
170    type Output = Vector3D;
171    #[inline]
172    fn neg(self) -> Vector3D {
173        Vector3D::new(-self.x, -self.y, -self.z)
174    }
175}
176
177impl Mul<f64> for Vector3D {
178    type Output = Vector3D;
179    #[inline]
180    fn mul(self, right: f64) -> Vector3D {
181        Vector3D::multiply_scalar(self, right)
182    }
183}
184
185impl AddAssign for Vector3D {
186    #[inline]
187    fn add_assign(&mut self, right: Vector3D) {
188        *self = *self + right;
189    }
190}
191
192impl SubAssign for Vector3D {
193    #[inline]
194    fn sub_assign(&mut self, right: Vector3D) {
195        *self = *self - right;
196    }
197}
198
199impl MulAssign<f64> for Vector3D {
200    #[inline]
201    fn mul_assign(&mut self, right: f64) {
202        *self = *self * right;
203    }
204}
205
206impl FromStr for Vector3D {
207    type Err = FormatError;
208    #[inline]
209    fn from_str(s: &str) -> Result<Self, FormatError> {
210        Vector3D::parse(s)
211    }
212}
213
214impl fmt::Display for Vector3D {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        write!(
217            f,
218            "Vector3D {{ X = {}, Y = {}, Z = {} }}",
219            InvariantF64(self.x),
220            InvariantF64(self.y),
221            InvariantF64(self.z)
222        )
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    // The reference test-suite has no tests for this type.
229    use super::*;
230
231    #[test]
232    fn parse_and_display() {
233        assert_eq!(
234            Vector3D::parse("1, 2.5 -3").unwrap(),
235            Vector3D::new(1.0, 2.5, -3.0)
236        );
237        assert_eq!(
238            Vector3D::parse("1,2").unwrap_err().message(),
239            "Invalid Vector."
240        );
241        assert_eq!(
242            Vector3D::new(1.0, 2.5, -3.0).to_string(),
243            "Vector3D { X = 1, Y = 2.5, Z = -3 }"
244        );
245    }
246
247    #[test]
248    fn arithmetic() {
249        let a = Vector3D::new(1.0, 2.0, 3.0);
250        let b = Vector3D::new(4.0, 5.0, 6.0);
251        assert_eq!(a + b, Vector3D::new(5.0, 7.0, 9.0));
252        assert_eq!(b - a, Vector3D::new(3.0, 3.0, 3.0));
253        assert_eq!(-a, Vector3D::new(-1.0, -2.0, -3.0));
254        assert_eq!(a * 2.0, Vector3D::new(2.0, 4.0, 6.0));
255        assert_eq!(Vector3D::dot(a, b), 32.0);
256        assert_eq!(Vector3D::multiply(a, b), Vector3D::new(4.0, 10.0, 18.0));
257        assert_eq!(Vector3D::divide(b, a), Vector3D::new(4.0, 2.5, 2.0));
258        assert_eq!(Vector3D::new(2.0, 3.0, 6.0).length(), 7.0);
259        assert_eq!(
260            Vector3D::normalize(Vector3D::new(0.0, 0.0, 5.0)),
261            Vector3D::new(0.0, 0.0, 1.0)
262        );
263        assert_eq!(Vector3D::distance(a, Vector3D::new(1.0, 2.0, 5.0)), 2.0);
264        assert_eq!(
265            Vector3D::clamp(
266                Vector3D::new(-1.0, 0.5, 9.0),
267                Vector3D::default(),
268                Vector3D::new(1.0, 1.0, 1.0)
269            ),
270            Vector3D::new(0.0, 0.5, 1.0)
271        );
272        assert_eq!(Vector3D::new(-1.0, 2.0, -3.0).abs(), a);
273    }
274}