Skip to main content

vec3_rs/
lib.rs

1//! This crate provides a simple implementation of 3D vectors in Rust. Supports any numeric type trough num-traits.
2
3#![cfg_attr(not(feature = "std"), no_std)]
4
5mod consts;
6mod convert;
7#[cfg(any(feature = "std", feature = "libm"))]
8mod float_lerp;
9mod ops;
10mod ops_scalar;
11
12#[cfg(any(feature = "std", feature = "libm"))]
13use float_lerp::Lerp;
14#[cfg(any(feature = "std", feature = "libm"))]
15use num_traits::clamp;
16#[cfg(feature = "random")]
17use rand::{
18    RngExt,
19    distr::uniform::{SampleRange, SampleUniform},
20    make_rng,
21    rngs::SmallRng,
22};
23
24#[cfg(feature = "random")]
25thread_local! {
26    static RNG: std::cell::RefCell<SmallRng> = std::cell::RefCell::new(make_rng());
27}
28
29/// Trait representing accepted coordonate kind `T` for `Vector3<T>`.
30pub trait Vector3Coordinate:
31    num_traits::Num
32    + num_traits::ToPrimitive
33    + PartialOrd
34    + core::fmt::Display
35    + core::ops::AddAssign
36    + core::ops::SubAssign
37    + core::ops::MulAssign
38    + core::ops::DivAssign
39    + Clone
40{
41}
42
43impl<T> Vector3Coordinate for T where
44    T: num_traits::Num
45        + num_traits::ToPrimitive
46        + PartialOrd
47        + core::fmt::Display
48        + core::ops::AddAssign
49        + core::ops::SubAssign
50        + core::ops::MulAssign
51        + core::ops::DivAssign
52        + Clone
53{
54}
55
56/// Represents a vector in 3D space.
57#[derive(Debug, PartialEq, Eq, Default, Clone, Copy, Hash)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub struct Vector3<T: Vector3Coordinate> {
60    x: T,
61    y: T,
62    z: T,
63}
64
65#[cfg(any(feature = "std", feature = "libm"))]
66impl<T: Vector3Coordinate + num_traits::Float> Vector3<T> {
67    /// Checks if this vector is approximately equal to another vector within a given epsilon.
68    ///
69    /// # Panics
70    ///
71    /// Panics if `epsilon` is negative.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use vec3_rs::Vector3;
77    ///
78    /// let v1 = Vector3::new(0.1, 0.2, 0.3);
79    /// let v2 = Vector3::new(0.101, 0.199, 0.299);
80    ///
81    /// let epsilon = 0.01;
82    /// let is_approx_equal = v1.fuzzy_equal(&v2, epsilon);
83    /// assert!(is_approx_equal)
84    /// ```
85    #[must_use]
86    #[inline]
87    pub fn fuzzy_equal(&self, target: &Self, epsilon: T) -> bool {
88        assert!(epsilon.is_sign_positive());
89        // unrolled for performance
90        (self.x - target.x).abs() <= epsilon
91            && (self.y - target.y).abs() <= epsilon
92            && (self.z - target.z).abs() <= epsilon
93    }
94
95    /// Linearly interpolates between this vector and another vector by a given ratio.
96    #[must_use]
97    #[inline]
98    pub fn lerp(&self, target: &Self, alpha: T) -> Self {
99        Self {
100            x: self.x.lerp(target.x, alpha),
101            y: self.y.lerp(target.y, alpha),
102            z: self.z.lerp(target.z, alpha),
103        }
104    }
105
106    /// Computes the magnitude (length) of the vector.
107    #[must_use]
108    #[inline]
109    pub fn magnitude(&self) -> T {
110        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
111    }
112
113    /// Computes the angle in radians between this vector and another vector.
114    #[must_use]
115    #[inline]
116    pub fn angle(&self, target: Self) -> T {
117        let dot_product = self.dot(target);
118        let magnitude_product = self.magnitude() * target.magnitude();
119        (dot_product / magnitude_product).acos()
120    }
121
122    /// Computes the angle in degrees between this vector and another vector.
123    #[must_use]
124    #[inline]
125    pub fn angle_deg(&self, target: Self) -> T
126    where
127        T: From<f64>,
128    {
129        const COEFF: f64 = 180.0 / core::f64::consts::PI;
130        self.angle(target) * From::from(COEFF)
131    }
132
133    /// Scales the vector such that its magnitude becomes 1.
134    #[inline]
135    pub fn normalize(&mut self) {
136        *self /= self.magnitude();
137    }
138
139    /// Computes the distance between this vector and another vector.
140    #[must_use]
141    #[inline]
142    pub fn distance(&self, target: Self) -> T {
143        (*self - target).magnitude()
144    }
145
146    /// Projects this vector onto another vector.
147    #[must_use]
148    #[inline]
149    pub fn project(&self, on_normal: Self) -> Self {
150        on_normal * (self.dot(on_normal) / on_normal.dot(on_normal))
151    }
152
153    /// Reflects this vector off a surface defined by a normal.
154    #[must_use]
155    #[inline]
156    pub fn reflect(&self, normal: Self) -> Self {
157        let two = T::one() + T::one();
158        *self - (normal * (self.dot(normal) * two))
159    }
160
161    /// Inverts the components of the vector.
162    #[must_use]
163    #[inline]
164    pub fn inverse(&self) -> Self {
165        let one = T::one();
166        Self {
167            x: one / self.x,
168            y: one / self.y,
169            z: one / self.z,
170        }
171    }
172
173    /// Returns a new vector with the absolute value of each component.
174    #[must_use]
175    #[inline]
176    pub fn abs(&self) -> Self {
177        Self {
178            x: self.x.abs(),
179            y: self.y.abs(),
180            z: self.z.abs(),
181        }
182    }
183
184    /// Returns a new vector with the ceiling of each component.
185    #[must_use]
186    #[inline]
187    pub fn ceil(&self) -> Self {
188        Self {
189            x: self.x.ceil(),
190            y: self.y.ceil(),
191            z: self.z.ceil(),
192        }
193    }
194
195    /// Returns a new vector with the floor of each component.
196    #[must_use]
197    #[inline]
198    pub fn floor(&self) -> Self {
199        Self {
200            x: self.x.floor(),
201            y: self.y.floor(),
202            z: self.z.floor(),
203        }
204    }
205
206    /// Returns a new vector with the rounded value of each component.
207    #[must_use]
208    #[inline]
209    pub fn round(&self) -> Self {
210        Self {
211            x: self.x.round(),
212            y: self.y.round(),
213            z: self.z.round(),
214        }
215    }
216
217    /// Returns a new vector with each component clamped to a given range.
218    #[must_use]
219    #[inline]
220    pub fn clamp(&self, min: T, max: T) -> Self {
221        Self {
222            x: clamp(self.x, min, max),
223            y: clamp(self.y, min, max),
224            z: clamp(self.z, min, max),
225        }
226    }
227
228    /// Rotates the vector around an axis by a given angle in radians.
229    #[must_use]
230    #[inline]
231    pub fn rotated(&self, axis: Self, angle: T) -> Self {
232        let (sin, cos) = angle.sin_cos();
233        let axis_normalized = axis / axis.magnitude();
234
235        let term1 = *self * cos;
236        let term2 = axis_normalized.cross(*self) * sin;
237        let term3_scalar = axis_normalized.dot(*self) * (T::one() - cos);
238        let term3 = axis_normalized * term3_scalar;
239
240        term1 + term2 + term3
241    }
242
243    /// Creates a new `Vector3` from spherical coordinates.
244    ///
245    /// # Arguments
246    ///
247    /// * `radius` - The distance from the origin.
248    /// * `polar` - The polar angle (the angle from the z-axis, in radians).
249    /// * `azimuth` - The azimuth angle (the angle from the x-axis in the xy-plane, in radians).
250    #[must_use]
251    #[inline]
252    pub fn from_spherical(radius: T, polar: T, azimuth: T) -> Self {
253        let (sin_polar, cos_polar) = polar.sin_cos();
254        let (sin_azimuth, cos_azimuth) = azimuth.sin_cos();
255        Self {
256            x: radius * sin_polar * cos_azimuth,
257            y: radius * sin_polar * sin_azimuth,
258            z: radius * cos_polar,
259        }
260    }
261
262    /// Generates a random Vector3 with components in the range [0.0, 1.0).
263    #[cfg(feature = "random")]
264    #[must_use]
265    #[inline]
266    pub fn random() -> Self
267    where
268        rand::distr::StandardUniform: rand::prelude::Distribution<T>,
269    {
270        RNG.with_borrow_mut(|thread| Self {
271            x: thread.random(),
272            y: thread.random(),
273            z: thread.random(),
274        })
275    }
276
277    /// Generates a random Vector3 with components in the given ranges.
278    #[cfg(feature = "random")]
279    #[must_use]
280    #[inline]
281    pub fn random_range(
282        range_x: impl SampleRange<T>,
283        range_y: impl SampleRange<T>,
284        range_z: impl SampleRange<T>,
285    ) -> Self
286    where
287        rand::distr::StandardUniform: rand::prelude::Distribution<T>,
288        T: SampleUniform,
289    {
290        RNG.with_borrow_mut(|thread| Self {
291            x: thread.random_range(range_x),
292            y: thread.random_range(range_y),
293            z: thread.random_range(range_z),
294        })
295    }
296}
297
298impl<T: Vector3Coordinate> Vector3<T> {
299    /// Creates a new Vector3 with the specified coordinates.
300    ///
301    /// # Examples
302    ///
303    /// ```
304    /// type Vector3 = vec3_rs::Vector3<f64>;
305    ///
306    /// let vector3 = Vector3::new(1.0, 2.0, 3.0);
307    /// let also_ok = Vector3::new(1, 2, 3);
308    /// assert_eq!(vector3, also_ok);
309    ///
310    /// // can also be created from arrays and tuples
311    /// // but no automatic number conversion
312    /// let from_array = Vector3::from([1.0, 2.0, 3.0]);
313    /// let from_tuple = Vector3::from((1.0, 2.0, 3.0));
314    /// assert_eq!(vector3, from_array);
315    /// assert_eq!(vector3, from_tuple);
316    ///
317    /// // conversion is fallible with unknown sized data types
318    /// let slice: &[f64] = [1.0, 2.0, 3.0].as_ref();
319    /// let from_slice = Vector3::try_from(slice).unwrap();
320    /// let from_vec = Vector3::try_from(vec![1.0, 2.0, 3.0]).unwrap();
321    /// assert_eq!(vector3, from_slice);
322    /// assert_eq!(vector3, from_vec);
323    ///
324    /// ```
325    pub fn new<U: Into<T>>(x: U, y: U, z: U) -> Self {
326        let (x, y, z) = (x.into(), y.into(), z.into());
327        Self { x, y, z }
328    }
329
330    /// Computes the dot product between this vector and another vector.
331    #[must_use]
332    #[inline]
333    pub fn dot(&self, target: Self) -> T {
334        let (x, y, z): (T, T, T) = target.into();
335        self.x.clone() * x + self.y.clone() * y + self.z.clone() * z
336    }
337
338    /// Computes the cross product between this vector and another vector.
339    #[must_use]
340    #[inline]
341    pub fn cross(&self, target: Self) -> Self {
342        let (x, y, z): (T, T, T) = target.into();
343        Self {
344            x: self.y.clone() * z.clone() - self.z.clone() * y.clone(),
345            y: self.z.clone() * x.clone() - self.x.clone() * z,
346            z: self.x.clone() * y - self.y.clone() * x,
347        }
348    }
349
350    /// Computes the component-wise maximum of this vector and another vector.
351    #[must_use]
352    #[inline]
353    pub fn max(&self, target: &Self) -> Self {
354        let x = if self.x > target.x {
355            self.x.clone()
356        } else {
357            target.x.clone()
358        };
359        let y = if self.y > target.y {
360            self.y.clone()
361        } else {
362            target.y.clone()
363        };
364        let z = if self.z > target.z {
365            self.z.clone()
366        } else {
367            target.z.clone()
368        };
369        Self { x, y, z }
370    }
371
372    /// Computes the component-wise minimum of this vector and another vector.
373    #[must_use]
374    #[inline]
375    pub fn min(&self, target: &Self) -> Self {
376        let x = if self.x < target.x {
377            self.x.clone()
378        } else {
379            target.x.clone()
380        };
381        let y = if self.y < target.y {
382            self.y.clone()
383        } else {
384            target.y.clone()
385        };
386        let z = if self.z < target.z {
387            self.z.clone()
388        } else {
389            target.z.clone()
390        };
391        Self { x, y, z }
392    }
393
394    /// Retrieves the X component of the vector.
395    pub const fn x(&self) -> &T {
396        &self.x
397    }
398
399    /// Retrieves the Y component of the vector.
400    pub const fn y(&self) -> &T {
401        &self.y
402    }
403
404    /// Retrieves the Z component of the vector.
405    pub const fn z(&self) -> &T {
406        &self.z
407    }
408}
409
410impl<T: Vector3Coordinate> core::fmt::Display for Vector3<T> {
411    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
412        write!(f, "Vector3({}, {}, {})", self.x, self.y, self.z)
413    }
414}
415
416#[cfg(test)]
417mod tests {
418    use super::*;
419    use core::ops::Sub;
420
421    #[test]
422    fn angle() {
423        let angle = core::f64::consts::PI / 2.0;
424        let calc_angle = Vector3::<f64>::x_axis().angle(Vector3::<f64>::y_axis());
425        assert!(calc_angle.sub(angle) <= f64::EPSILON);
426    }
427
428    #[test]
429    fn create() {
430        let my_vec: Vector3<f64> = Vector3::new(1.3, 0.0, -5.35501);
431        assert!((my_vec.x() - 1.3f64).abs() <= f64::EPSILON);
432        assert!((my_vec.y() - 0.0f64).abs() <= f64::EPSILON);
433        assert!((my_vec.z() - -5.35501f64).abs() <= f64::EPSILON);
434    }
435
436    #[test]
437    fn sum() {
438        let vec1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
439        let vec2 = Vector3::new(5.0, 0.0, -1.0);
440        assert_eq!(vec1 + vec2, Vector3::new(6.0, 2.0, 2.0));
441    }
442
443    #[test]
444    fn normalization() {
445        let mut test_vec: Vector3<f64> = Vector3::new(1.0, 2.3, 100.123);
446        test_vec.normalize();
447        assert_eq!(
448            test_vec,
449            Vector3::new(
450                0.009_984_583_160_766_44,
451                0.022_964_541_269_762_81,
452                0.999_686_419_805_418_3
453            )
454        );
455        assert!((1.0 - test_vec.magnitude()).abs() <= f64::EPSILON);
456    }
457
458    #[test]
459    fn lerp() {
460        let start = Vector3::new(0.0, 0.0, 0.0);
461        let end = Vector3::new(1.0, 2.0, 3.0);
462        let lerp_result = start.lerp(&end, 0.75);
463        assert_eq!(lerp_result, Vector3::new(0.75, 1.5, 2.25));
464    }
465
466    #[test]
467    fn dot_product() {
468        let vec1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
469        let vec2 = Vector3::new(5.0, 0.0, -1.0);
470        let dot_result = vec1.dot(vec2);
471        assert!((dot_result - 2.0f64).abs() <= f64::EPSILON);
472    }
473
474    #[test]
475    fn cross_product() {
476        let vec1: Vector3<f64> = Vector3::new(1.0, 0.0, 0.0);
477        let vec2 = Vector3::new(0.0, 1.0, 0.0);
478        let cross_result = vec1.cross(vec2);
479        assert_eq!(cross_result, Vector3::new(0.0, 0.0, 1.0));
480    }
481
482    #[test]
483    fn max_components() {
484        let vec1: Vector3<f64> = Vector3::new(1.0, 5.0, 3.0);
485        let vec2 = Vector3::new(3.0, 2.0, 4.0);
486        let max_result = vec1.max(&vec2);
487        assert_eq!(max_result, Vector3::new(3.0, 5.0, 4.0));
488    }
489
490    #[test]
491    fn min_components() {
492        let vec1: Vector3<f64> = Vector3::new(1.0, 5.0, 3.0);
493        let vec2 = Vector3::new(3.0, 2.0, 4.0);
494        let min_result = vec1.min(&vec2);
495        assert_eq!(min_result, Vector3::new(1.0, 2.0, 3.0));
496    }
497
498    #[test]
499    fn fuzzy_equality() {
500        let vec1 = Vector3::new(1.0, 2.0, 3.0);
501        let vec2 = Vector3::new(1.01, 1.99, 3.01);
502        let epsilon = 0.02;
503        let fuzzy_equal_result = vec1.fuzzy_equal(&vec2, epsilon);
504        assert!(fuzzy_equal_result);
505    }
506
507    #[test]
508    fn distance() {
509        let v1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
510        let v2 = Vector3::new(4.0, 6.0, 8.0);
511        assert!((v1.distance(v2) - (50.0f64).sqrt()).abs() <= f64::EPSILON);
512    }
513
514    #[test]
515    fn project() {
516        let v: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
517        let on_normal = Vector3::new(1.0, 0.0, 0.0);
518        let expected = Vector3::new(1.0, 0.0, 0.0);
519        assert_eq!(v.project(on_normal), expected);
520    }
521
522    #[test]
523    fn reflect() {
524        let v: Vector3<f64> = Vector3::new(1.0, -1.0, 0.0);
525        let normal = Vector3::new(0.0, 1.0, 0.0);
526        let expected = Vector3::new(1.0, 1.0, 0.0);
527        assert_eq!(v.reflect(normal), expected);
528    }
529
530    #[test]
531    fn inverse() {
532        let v: Vector3<f64> = Vector3::new(2.0, 4.0, 8.0);
533        let expected = Vector3::new(0.5, 0.25, 0.125);
534        assert_eq!(v.inverse(), expected);
535    }
536
537    #[test]
538    fn abs() {
539        let v: Vector3<f64> = Vector3::new(-1.0, -2.0, 3.0);
540        let expected = Vector3::new(1.0, 2.0, 3.0);
541        assert_eq!(v.abs(), expected);
542    }
543
544    #[test]
545    fn ceil() {
546        let v: Vector3<f64> = Vector3::new(1.1, 2.9, 3.0);
547        let expected = Vector3::new(2.0, 3.0, 3.0);
548        assert_eq!(v.ceil(), expected);
549    }
550
551    #[test]
552    fn floor() {
553        let v: Vector3<f64> = Vector3::new(1.1, 2.9, 3.0);
554        let expected = Vector3::new(1.0, 2.0, 3.0);
555        assert_eq!(v.floor(), expected);
556    }
557
558    #[test]
559    fn round() {
560        let v: Vector3<f64> = Vector3::new(1.1, 2.9, 3.5);
561        let expected = Vector3::new(1.0, 3.0, 4.0);
562        assert_eq!(v.round(), expected);
563    }
564
565    #[test]
566    fn clamp() {
567        let v = Vector3::new(0.0, 5.0, 10.0);
568        let min = 1.0;
569        let max = 9.0;
570        let expected = Vector3::new(1.0, 5.0, 9.0);
571        assert_eq!(v.clamp(min, max), expected);
572    }
573
574    #[test]
575    fn rotated() {
576        let v = Vector3::new(1.0, 0.0, 0.0);
577        let axis = Vector3::new(0.0, 0.0, 1.0);
578        let angle = core::f64::consts::FRAC_PI_2;
579        let rotated = v.rotated(axis, angle);
580        let expected = Vector3::new(0.0, 1.0, 0.0);
581        assert!(rotated.fuzzy_equal(&expected, 1e-15));
582    }
583
584    #[test]
585    fn from_spherical() {
586        let radius = 1.0;
587        let polar = core::f64::consts::FRAC_PI_2;
588        let azimuth = 0.0;
589        let v = Vector3::from_spherical(radius, polar, azimuth);
590        let expected = Vector3::new(1.0, 0.0, 0.0);
591        assert!(v.fuzzy_equal(&expected, 1e-15));
592    }
593
594    #[test]
595    fn nan_dont_panic() {
596        let mut vec1: Vector3<f64> = Vector3::default();
597        vec1 /= f64::NAN;
598    }
599
600    #[test]
601    fn readme_example() {
602        let mut v1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
603        let mut v2: Vector3<f64> = Vector3::new(3.0, 1.0, 2.0);
604
605        // Basic operations
606        let sum = v1 + v2;
607        let difference = v1 - v2;
608        let dot_product = v1.dot(v2);
609        let cross_product = v1.cross(v2);
610
611        // Other methods
612        let lerp_result = v1.lerp(&v2, 0.5);
613        let angle = v1.angle(v2);
614        let fuzzy_equal = v1.fuzzy_equal(&v2, 0.001);
615
616        println!("Sum: {sum}");
617        println!("Difference: {difference}");
618        println!("Dot product: {dot_product}");
619        println!("Cross product: {cross_product}");
620        println!("Lerp 50%: {lerp_result}");
621        println!("Angle: {angle}");
622        print!("Are they close enough?: {fuzzy_equal}");
623
624        v1.normalize();
625        v2.normalize();
626
627        println!("v1 normalized: {v1}");
628        println!("v2 normalized: {v2}");
629    }
630    #[test]
631    fn conversion_box() {
632        let correct = Vector3::new(1, 2, 3);
633        let x = String::from("Vector3(1,2,3)").into_boxed_str();
634        assert_eq!(x.parse::<Vector3<i32>>().unwrap(), correct);
635    }
636    #[test]
637    fn from_slice() {
638        let correct = Vector3::new(1, 2, 3);
639        let arr = [1, 2, 3].as_ref();
640        let x = Vector3::try_from(arr).unwrap();
641        assert_eq!(correct, x);
642    }
643
644    #[test]
645    fn from_box_slice() {
646        let correct = Vector3::new(1, 2, 3);
647        let arr: Box<[i32]> = Box::from([1, 2, 3].as_ref());
648        let x = Vector3::try_from(arr).unwrap();
649        assert_eq!(correct, x);
650    }
651}