vec3_rs/
lib.rs

1#![deny(unsafe_code, warnings, clippy::all)]
2
3pub mod consts;
4mod convert;
5mod float_lerp;
6mod ops;
7
8use float_lerp::Lerp;
9use rand::{thread_rng, Rng};
10
11pub trait Vector3Coordinate:
12    num::Num
13    + num::ToPrimitive
14    + PartialOrd
15    + std::fmt::Display
16    + std::fmt::Debug
17    + std::ops::AddAssign
18    + std::ops::SubAssign
19    + std::ops::MulAssign
20    + std::ops::DivAssign
21    + Clone
22    + Copy
23{
24}
25impl Vector3Coordinate for f64 {}
26impl Vector3Coordinate for f32 {}
27impl Vector3Coordinate for i8 {}
28impl Vector3Coordinate for i16 {}
29impl Vector3Coordinate for i32 {}
30impl Vector3Coordinate for i64 {}
31impl Vector3Coordinate for i128 {}
32impl Vector3Coordinate for u8 {}
33impl Vector3Coordinate for u16 {}
34impl Vector3Coordinate for u32 {}
35impl Vector3Coordinate for u64 {}
36impl Vector3Coordinate for u128 {}
37
38/// Represents a vector in 3D space.
39#[derive(Debug, PartialOrd, PartialEq, Default, Clone, Copy)]
40pub struct Vector3<T: Vector3Coordinate> {
41    x: T,
42    y: T,
43    z: T,
44}
45
46impl<T: Vector3Coordinate + num::Float> Vector3<T>
47where
48    rand::distributions::Standard: rand::prelude::Distribution<T>,
49{
50    /// Generates a random Vector3 with components in the range [0.0, 1.0).
51    pub fn random() -> Self {
52        Vector3 {
53            x: thread_rng().gen(),
54            y: thread_rng().gen(),
55            z: thread_rng().gen(),
56        }
57    }
58
59    /// Checks if this vector is approximately equal to another vector within a given epsilon.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use vec3_rs::Vector3;
65    ///
66    /// let v1 = Vector3::new(0.1, 0.2, 0.3);
67    /// let v2 = Vector3::new(0.101, 0.199, 0.299);
68    ///
69    /// let epsilon = 0.01;
70    /// let is_approx_equal = v1.fuzzy_equal(&v2, epsilon);
71    /// println!("Are v1 and v2 approximately equal? {}", is_approx_equal);
72    /// ```
73    pub fn fuzzy_equal(&self, target: &Self, epsilon: f64) -> bool {
74        (self.x - target.x)
75            .abs()
76            .to_f64()
77            .expect("f64 should handle all values")
78            <= epsilon
79            && (self.y - target.y)
80                .abs()
81                .to_f64()
82                .expect("f64 should handle all values")
83                <= epsilon
84            && (self.z - target.z)
85                .abs()
86                .to_f64()
87                .expect("f64 should handle all values")
88                <= epsilon
89    }
90
91    /// Linearly interpolates between this vector and another vector by a given ratio.
92    pub fn lerp(&self, target: &Self, alpha: T) -> Self {
93        Vector3 {
94            x: self.x.lerp(target.x, alpha),
95            y: self.y.lerp(target.y, alpha),
96            z: self.z.lerp(target.z, alpha),
97        }
98    }
99}
100
101impl Vector3<f64> {
102    /// Scales the vector such that its magnitude becomes 1.
103    pub fn normalize(&mut self) {
104        *self /= self.magnitude();
105    }
106}
107
108impl Vector3<f32> {
109    /// Scales the vector such that its magnitude becomes 1.
110    pub fn normalize(&mut self) {
111        *self /= self.magnitude() as f32;
112    }
113}
114
115impl<T: Vector3Coordinate> Vector3<T> {
116    /// Creates a new Vector3 with the specified coordinates.
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use vec3_rs::Vector3;
122    ///
123    /// let vector3 = Vector3::new(1.0, 2.0, 3.0);
124    /// ```
125    pub fn new(x: T, y: T, z: T) -> Self {
126        Vector3 { x, y, z }
127    }
128
129    /// Computes the magnitude (length) of the vector.
130    pub fn magnitude(&self) -> f64 {
131        let mag2 = self.x * self.x + self.y * self.y + self.z * self.z;
132        mag2.to_f64().expect("f64 should handle all values").sqrt()
133    }
134
135    /// Computes the dot product between this vector and another vector.
136    pub fn dot(&self, target: &Self) -> T {
137        self.x * target.x + self.y * target.y + self.z * target.z
138    }
139
140    /// Computes the cross product between this vector and another vector.
141    pub fn cross(&self, target: &Self) -> Self {
142        Vector3 {
143            x: self.y * target.z - self.z * target.y,
144            y: self.z * target.x - self.x * target.z,
145            z: self.x * target.y - self.y * target.x,
146        }
147    }
148
149    /// Computes the component-wise maximum of this vector and another vector.
150    pub fn max(&self, target: &Self) -> Self {
151        let x = if self.x > target.x { self.x } else { target.x };
152        let y = if self.y > target.y { self.y } else { target.y };
153        let z = if self.z > target.z { self.z } else { target.z };
154        Vector3 { x, y, z }
155    }
156
157    /// Computes the component-wise minimum of this vector and another vector.
158    pub fn min(&self, target: &Self) -> Self {
159        let x = if self.x < target.x { self.x } else { target.x };
160        let y = if self.y < target.y { self.y } else { target.y };
161        let z = if self.z < target.z { self.z } else { target.z };
162        Vector3 { x, y, z }
163    }
164
165    /// Computes the angle in radians between this vector and another vector.
166    pub fn angle(&self, target: &Self) -> f64 {
167        let dot_product = self
168            .dot(target)
169            .to_f64()
170            .expect("f64 should handle all values");
171        let magnitude_product = self.magnitude() * target.magnitude();
172        (dot_product / magnitude_product).acos()
173    }
174
175    /// Computes the angle in degrees between this vector and another vector.
176    pub fn angle_deg(&self, target: &Self) -> f64 {
177        self.angle(target) * (180.0 / std::f64::consts::PI)
178    }
179
180    /// Retrieves the X component of the vector.
181    pub const fn get_x(&self) -> T {
182        self.x
183    }
184
185    /// Retrieves the Y component of the vector.
186    pub const fn get_y(&self) -> T {
187        self.y
188    }
189
190    /// Retrieves the Z component of the vector.
191    pub const fn get_z(&self) -> T {
192        self.z
193    }
194}
195
196impl<T: Vector3Coordinate> std::fmt::Display for Vector3<T> {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        write!(f, "Vector3({}, {}, {})", self.x, self.y, self.z)
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn angle() {
208        let angle = std::f64::consts::PI / 2.0;
209        let calc_angle = consts::X_AXIS.angle(&consts::Y_AXIS);
210        assert_eq!(calc_angle, angle);
211    }
212
213    #[test]
214    fn create() {
215        let my_vec = Vector3::new(1.3, 0.0, -5.35501);
216        assert_eq!(my_vec.get_x(), 1.3);
217        assert_eq!(my_vec.get_y(), 0.0);
218        assert_eq!(my_vec.get_z(), -5.35501);
219    }
220
221    #[test]
222    fn sum() {
223        let vec1 = Vector3::new(1.0, 2.0, 3.0);
224        let vec2 = Vector3::new(5.0, 0.0, -1.0);
225        assert_eq!(vec1 + vec2, Vector3::new(6.0, 2.0, 2.0));
226    }
227
228    #[test]
229    fn normalization() {
230        let mut test_vec: Vector3<f64> = Vector3::new(1.0, 2.3, 100.123);
231        test_vec.normalize();
232        assert_eq!(
233            test_vec,
234            Vector3::new(0.00998458316076644, 0.02296454126976281, 0.9996864198054183)
235        );
236        assert!((1.0 - test_vec.magnitude()).abs() < 0.00000001);
237    }
238
239    #[test]
240    fn lerp() {
241        let start = Vector3::new(0.0, 0.0, 0.0);
242        let end = Vector3::new(1.0, 2.0, 3.0);
243        let lerp_result = start.lerp(&end, 0.75);
244        assert_eq!(lerp_result, Vector3::new(0.75, 1.5, 2.25));
245    }
246
247    #[test]
248    fn dot_product() {
249        let vec1 = Vector3::new(1.0, 2.0, 3.0);
250        let vec2 = Vector3::new(5.0, 0.0, -1.0);
251        let dot_result = vec1.dot(&vec2);
252        assert_eq!(dot_result, 2.0);
253    }
254
255    #[test]
256    fn cross_product() {
257        let vec1 = Vector3::new(1.0, 0.0, 0.0);
258        let vec2 = Vector3::new(0.0, 1.0, 0.0);
259        let cross_result = vec1.cross(&vec2);
260        assert_eq!(cross_result, Vector3::new(0.0, 0.0, 1.0));
261    }
262
263    #[test]
264    fn max_components() {
265        let vec1 = Vector3::new(1.0, 5.0, 3.0);
266        let vec2 = Vector3::new(3.0, 2.0, 4.0);
267        let max_result = vec1.max(&vec2);
268        assert_eq!(max_result, Vector3::new(3.0, 5.0, 4.0));
269    }
270
271    #[test]
272    fn min_components() {
273        let vec1 = Vector3::new(1.0, 5.0, 3.0);
274        let vec2 = Vector3::new(3.0, 2.0, 4.0);
275        let min_result = vec1.min(&vec2);
276        assert_eq!(min_result, Vector3::new(1.0, 2.0, 3.0));
277    }
278
279    #[test]
280    fn fuzzy_equality() {
281        let vec1 = Vector3::new(1.0, 2.0, 3.0);
282        let vec2 = Vector3::new(1.01, 1.99, 3.01);
283        let epsilon = 0.02;
284        let fuzzy_equal_result = vec1.fuzzy_equal(&vec2, epsilon);
285        assert!(fuzzy_equal_result);
286    }
287    #[test]
288    fn nan_dont_panic() {
289        let mut vec1: Vector3<f64> = Vector3::default();
290        vec1 /= std::f64::NAN;
291    }
292    #[test]
293    fn readme_example() {
294        let mut v1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
295        let mut v2: Vector3<f64> = Vector3::new(3.0, 1.0, 2.0);
296
297        // Basic operations
298        let sum = v1 + v2;
299        let difference = v1 - v2;
300        let dot_product = v1.dot(&v2);
301        let cross_product = v1.cross(&v2);
302
303        // Other methods
304        let lerp_result = v1.lerp(&v2, 0.5);
305        let angle = v1.angle(&v2);
306        let fuzzy_equal = v1.fuzzy_equal(&v2, 0.001);
307
308        println!("Sum: {sum}");
309        println!("Difference: {difference}");
310        println!("Dot product: {dot_product}");
311        println!("Cross product: {cross_product}");
312        println!("Lerp 50%: {lerp_result}");
313        println!("Angle: {angle}");
314        print!("Are they close enough?: {fuzzy_equal}");
315
316        v1.normalize();
317        v2.normalize();
318
319        println!("v1 normalized: {v1}");
320        println!("v2 normalized: {v2}");
321    }
322}