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    ///
97    /// # Examples
98    ///
99    /// ```
100    /// type Vector3 = vec3_rs::Vector3<f64>;
101    ///
102    /// let origin = Vector3::zero();
103    /// let x_axis = Vector3::x_axis();
104    /// assert_eq!(origin.lerp(&x_axis, 0.5), Vector3::new(0.5, 0, 0))
105    ///
106    /// ```
107    #[must_use]
108    #[inline]
109    pub fn lerp(&self, target: &Self, alpha: T) -> Self {
110        Self {
111            x: self.x.lerp(target.x, alpha),
112            y: self.y.lerp(target.y, alpha),
113            z: self.z.lerp(target.z, alpha),
114        }
115    }
116
117    /// Computes the magnitude (length) of the vector.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// type Vector3 = vec3_rs::Vector3<f64>;
123    ///
124    /// let mut vector3 = Vector3::new(12354,7324,-7765).normalized();
125    /// assert_eq!(vector3.magnitude(), 1.0);
126    /// ```
127    #[must_use]
128    #[inline]
129    pub fn magnitude(&self) -> T {
130        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
131    }
132
133    /// Computes the angle in **radians** (0..2pi) between this vector and another vector.
134    #[must_use]
135    #[inline]
136    pub fn angle(&self, target: &Self) -> T {
137        let dot_product = self.dot(target);
138        let magnitude_product = self.magnitude() * target.magnitude();
139        (dot_product / magnitude_product).acos()
140    }
141
142    /// Computes the angle in **degrees** (0.0..360.0) between this vector and another vector.
143    #[must_use]
144    #[inline]
145    pub fn angle_deg(&self, target: &Self) -> T
146    where
147        T: From<f64>,
148    {
149        const COEFF: f64 = 180.0 / core::f64::consts::PI;
150        self.angle(target) * From::from(COEFF)
151    }
152
153    /// Scales the vector such that its magnitude becomes 1.
154    #[inline]
155    pub fn normalize(&mut self) {
156        *self /= self.magnitude();
157    }
158
159    /// Copies the vector and scales it such that its magnitude becomes 1.
160    #[must_use]
161    #[inline]
162    pub fn normalized(&self) -> Self {
163        *self / self.magnitude()
164    }
165
166    /// Computes the distance between this vector and another vector.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// type Vector3 = vec3_rs::Vector3<f64>;
172    ///
173    /// let left = -Vector3::x_axis();
174    /// let right = Vector3::x_axis();
175    /// assert_eq!(left.distance(&right), 2.0);
176    #[must_use]
177    #[inline]
178    pub fn distance(&self, target: &Self) -> T {
179        (*self - *target).magnitude()
180    }
181
182    /// Projects this vector onto another vector.
183    #[must_use]
184    #[inline]
185    pub fn project(&self, on_normal: &Self) -> Self {
186        *on_normal * (self.dot(on_normal) / on_normal.dot(on_normal))
187    }
188
189    /// Reflects this vector off a surface defined by a normal.
190    #[must_use]
191    #[inline]
192    pub fn reflect(&self, normal: &Self) -> Self {
193        let two = T::one() + T::one();
194        *self - (*normal * (self.dot(normal) * two))
195    }
196
197    /// Inverts the components of the vector.
198    #[must_use]
199    #[inline]
200    pub fn inverse(&self) -> Self {
201        let one = T::one();
202        Self {
203            x: one / self.x,
204            y: one / self.y,
205            z: one / self.z,
206        }
207    }
208
209    /// Returns a new vector with the absolute value of each component.
210    #[must_use]
211    #[inline]
212    pub fn abs(&self) -> Self {
213        Self {
214            x: self.x.abs(),
215            y: self.y.abs(),
216            z: self.z.abs(),
217        }
218    }
219
220    /// Returns a new vector with the ceiling of each component.
221    #[must_use]
222    #[inline]
223    pub fn ceil(&self) -> Self {
224        Self {
225            x: self.x.ceil(),
226            y: self.y.ceil(),
227            z: self.z.ceil(),
228        }
229    }
230
231    /// Returns a new vector with the floor of each component.
232    #[must_use]
233    #[inline]
234    pub fn floor(&self) -> Self {
235        Self {
236            x: self.x.floor(),
237            y: self.y.floor(),
238            z: self.z.floor(),
239        }
240    }
241
242    /// Returns a new vector with the rounded value of each component.
243    #[must_use]
244    #[inline]
245    pub fn round(&self) -> Self {
246        Self {
247            x: self.x.round(),
248            y: self.y.round(),
249            z: self.z.round(),
250        }
251    }
252
253    /// Returns a new vector with each component clamped to a given range.
254    #[must_use]
255    #[inline]
256    pub fn clamp(&self, min: T, max: T) -> Self {
257        Self {
258            x: clamp(self.x, min, max),
259            y: clamp(self.y, min, max),
260            z: clamp(self.z, min, max),
261        }
262    }
263
264    /// Rotates the vector around an axis by a given angle in radians.
265    #[must_use]
266    #[inline]
267    pub fn rotated(&self, axis: &Self, angle: T) -> Self {
268        let (sin, cos) = angle.sin_cos();
269        let axis_normalized = axis.normalized();
270
271        let term1 = *self * cos;
272        let term2 = axis_normalized.cross(self) * sin;
273        let term3_scalar = axis_normalized.dot(self) * (T::one() - cos);
274        let term3 = axis_normalized * term3_scalar;
275
276        term1 + term2 + term3
277    }
278
279    /// Creates a new `Vector3` from spherical coordinates.
280    ///
281    /// # Arguments
282    ///
283    /// * `radius` - The distance from the origin.
284    /// * `polar` - The polar angle (the angle from the z-axis, in radians).
285    /// * `azimuth` - The azimuth angle (the angle from the x-axis in the xy-plane, in radians).
286    #[must_use]
287    #[inline]
288    pub fn from_spherical(radius: T, polar: T, azimuth: T) -> Self {
289        let (sin_polar, cos_polar) = polar.sin_cos();
290        let (sin_azimuth, cos_azimuth) = azimuth.sin_cos();
291        Self {
292            x: radius * sin_polar * cos_azimuth,
293            y: radius * sin_polar * sin_azimuth,
294            z: radius * cos_polar,
295        }
296    }
297
298    /// Generates a random Vector3 with components in the range [0.0, 1.0).
299    #[cfg(feature = "random")]
300    #[must_use]
301    #[inline]
302    pub fn random() -> Self
303    where
304        rand::distr::StandardUniform: rand::prelude::Distribution<T>,
305    {
306        RNG.with_borrow_mut(|thread| Self {
307            x: thread.random(),
308            y: thread.random(),
309            z: thread.random(),
310        })
311    }
312
313    /// Generates a random Vector3 with components in the given ranges.
314    #[cfg(feature = "random")]
315    #[must_use]
316    #[inline]
317    pub fn random_range(
318        range_x: impl SampleRange<T>,
319        range_y: impl SampleRange<T>,
320        range_z: impl SampleRange<T>,
321    ) -> Self
322    where
323        rand::distr::StandardUniform: rand::prelude::Distribution<T>,
324        T: SampleUniform,
325    {
326        RNG.with_borrow_mut(|thread| Self {
327            x: thread.random_range(range_x),
328            y: thread.random_range(range_y),
329            z: thread.random_range(range_z),
330        })
331    }
332}
333
334impl<T: Vector3Coordinate> Vector3<T> {
335    /// Creates a new Vector3 with the specified coordinates.
336    ///
337    /// # Examples
338    ///
339    /// ```
340    /// type Vector3 = vec3_rs::Vector3<f64>;
341    ///
342    /// let vector3 = Vector3::new(1.0, 2.0, 3.0);
343    /// let also_ok = Vector3::new(1, 2, 3);
344    /// assert_eq!(vector3, also_ok);
345    ///
346    /// // can also be created from arrays and tuples
347    /// // but no automatic number conversion
348    /// let from_array = Vector3::from([1.0, 2.0, 3.0]);
349    /// let from_tuple = Vector3::from((1.0, 2.0, 3.0));
350    /// assert_eq!(vector3, from_array);
351    /// assert_eq!(vector3, from_tuple);
352    ///
353    /// // conversion is fallible with unknown sized data types
354    /// let slice: &[f64] = [1.0, 2.0, 3.0].as_ref();
355    /// let from_slice = Vector3::try_from(slice).unwrap();
356    /// let from_vec = Vector3::try_from(vec![1.0, 2.0, 3.0]).unwrap();
357    /// assert_eq!(vector3, from_slice);
358    /// assert_eq!(vector3, from_vec);
359    ///
360    /// ```
361    pub fn new<U: Into<T>, V: Into<T>, W: Into<T>>(x: U, y: V, z: W) -> Self {
362        let (x, y, z) = (x.into(), y.into(), z.into());
363        Self { x, y, z }
364    }
365
366    /// Computes the dot product between this vector and another vector.
367    /// <https://en.wikipedia.org/wiki/Dot_product>
368    #[must_use]
369    #[inline]
370    pub fn dot(&self, target: &Self) -> T {
371        let (x, y, z): (T, T, T) = target.clone().into();
372        self.x.clone() * x + self.y.clone() * y + self.z.clone() * z
373    }
374
375    /// Computes the cross product between this vector and another vector.
376    /// <https://en.wikipedia.org/wiki/Cross_product>
377    #[must_use]
378    #[inline]
379    pub fn cross(&self, target: &Self) -> Self {
380        let (x, y, z): (T, T, T) = target.clone().into();
381        Self {
382            x: self.y.clone() * z.clone() - self.z.clone() * y.clone(),
383            y: self.z.clone() * x.clone() - self.x.clone() * z,
384            z: self.x.clone() * y - self.y.clone() * x,
385        }
386    }
387
388    /// Computes the component-wise maximum of this vector and another vector.
389    #[must_use]
390    #[inline]
391    pub fn max(&self, target: &Self) -> Self {
392        let x = if self.x > target.x {
393            self.x.clone()
394        } else {
395            target.x.clone()
396        };
397        let y = if self.y > target.y {
398            self.y.clone()
399        } else {
400            target.y.clone()
401        };
402        let z = if self.z > target.z {
403            self.z.clone()
404        } else {
405            target.z.clone()
406        };
407        Self { x, y, z }
408    }
409
410    /// Computes the component-wise minimum of this vector and another vector.
411    #[must_use]
412    #[inline]
413    pub fn min(&self, target: &Self) -> Self {
414        let x = if self.x < target.x {
415            self.x.clone()
416        } else {
417            target.x.clone()
418        };
419        let y = if self.y < target.y {
420            self.y.clone()
421        } else {
422            target.y.clone()
423        };
424        let z = if self.z < target.z {
425            self.z.clone()
426        } else {
427            target.z.clone()
428        };
429        Self { x, y, z }
430    }
431
432    /// Retrieves the X component of the vector.
433    pub const fn x(&self) -> &T {
434        &self.x
435    }
436
437    /// Retrieves the Y component of the vector.
438    pub const fn y(&self) -> &T {
439        &self.y
440    }
441
442    /// Retrieves the Z component of the vector.
443    pub const fn z(&self) -> &T {
444        &self.z
445    }
446}
447
448impl<T: Vector3Coordinate> core::fmt::Display for Vector3<T> {
449    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
450        write!(f, "Vector3({}, {}, {})", self.x, self.y, self.z)
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457    use core::ops::Sub;
458
459    #[test]
460    fn angle() {
461        let angle = core::f64::consts::PI / 2.0;
462        let calc_angle = Vector3::<f64>::x_axis().angle(&Vector3::<f64>::y_axis());
463        assert!(calc_angle.sub(angle) <= f64::EPSILON);
464    }
465
466    #[test]
467    fn create() {
468        let my_vec: Vector3<f64> = Vector3::new(1.3, 0.0, -5.35501);
469        assert!((my_vec.x() - 1.3f64).abs() <= f64::EPSILON);
470        assert!((my_vec.y() - 0.0f64).abs() <= f64::EPSILON);
471        assert!((my_vec.z() - -5.35501f64).abs() <= f64::EPSILON);
472    }
473
474    #[test]
475    fn sum() {
476        let vec1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
477        let vec2 = Vector3::new(5.0, 0.0, -1.0);
478        assert_eq!(vec1 + vec2, Vector3::new(6.0, 2.0, 2.0));
479    }
480
481    #[test]
482    fn normalization() {
483        let mut test_vec: Vector3<f64> = Vector3::new(1.0, 2.3, 100.123);
484        test_vec.normalize();
485        assert_eq!(
486            test_vec,
487            Vector3::new(
488                0.009_984_583_160_766_44,
489                0.022_964_541_269_762_81,
490                0.999_686_419_805_418_3
491            )
492        );
493        assert!((1.0 - test_vec.magnitude()).abs() <= f64::EPSILON);
494    }
495
496    #[test]
497    fn lerp() {
498        let start = Vector3::new(0.0, 0.0, 0.0);
499        let end = Vector3::new(1.0, 2.0, 3.0);
500        let lerp_result = start.lerp(&end, 0.75);
501        assert_eq!(lerp_result, Vector3::new(0.75, 1.5, 2.25));
502    }
503
504    #[test]
505    fn dot_product() {
506        let vec1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
507        let vec2 = Vector3::new(5.0, 0.0, -1.0);
508        let dot_result = vec1.dot(&vec2);
509        assert!((dot_result - 2.0f64).abs() <= f64::EPSILON);
510    }
511
512    #[test]
513    fn cross_product() {
514        let vec1: Vector3<f64> = Vector3::new(1.0, 0.0, 0.0);
515        let vec2 = Vector3::new(0.0, 1.0, 0.0);
516        let cross_result = vec1.cross(&vec2);
517        assert_eq!(cross_result, Vector3::new(0.0, 0.0, 1.0));
518    }
519
520    #[test]
521    fn max_components() {
522        let vec1: Vector3<f64> = Vector3::new(1.0, 5.0, 3.0);
523        let vec2 = Vector3::new(3.0, 2.0, 4.0);
524        let max_result = vec1.max(&vec2);
525        assert_eq!(max_result, Vector3::new(3.0, 5.0, 4.0));
526    }
527
528    #[test]
529    fn min_components() {
530        let vec1: Vector3<f64> = Vector3::new(1.0, 5.0, 3.0);
531        let vec2 = Vector3::new(3.0, 2.0, 4.0);
532        let min_result = vec1.min(&vec2);
533        assert_eq!(min_result, Vector3::new(1.0, 2.0, 3.0));
534    }
535
536    #[test]
537    fn fuzzy_equality() {
538        let vec1 = Vector3::new(1.0, 2.0, 3.0);
539        let vec2 = Vector3::new(1.01, 1.99, 3.01);
540        let epsilon = 0.02;
541        let fuzzy_equal_result = vec1.fuzzy_equal(&vec2, epsilon);
542        assert!(fuzzy_equal_result);
543    }
544
545    #[test]
546    fn distance() {
547        let v1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
548        let v2 = Vector3::new(4.0, 6.0, 8.0);
549        assert!((v1.distance(&v2) - (50.0f64).sqrt()).abs() <= f64::EPSILON);
550    }
551
552    #[test]
553    fn project() {
554        let v: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
555        let on_normal = Vector3::new(1.0, 0.0, 0.0);
556        let expected = Vector3::new(1.0, 0.0, 0.0);
557        assert_eq!(v.project(&on_normal), expected);
558    }
559
560    #[test]
561    fn reflect() {
562        let v: Vector3<f64> = Vector3::new(1.0, -1.0, 0.0);
563        let normal = Vector3::new(0.0, 1.0, 0.0);
564        let expected = Vector3::new(1.0, 1.0, 0.0);
565        assert_eq!(v.reflect(&normal), expected);
566    }
567
568    #[test]
569    fn inverse() {
570        let v: Vector3<f64> = Vector3::new(2.0, 4.0, 8.0);
571        let expected = Vector3::new(0.5, 0.25, 0.125);
572        assert_eq!(v.inverse(), expected);
573    }
574
575    #[test]
576    fn abs() {
577        let v: Vector3<f64> = Vector3::new(-1.0, -2.0, 3.0);
578        let expected = Vector3::new(1.0, 2.0, 3.0);
579        assert_eq!(v.abs(), expected);
580    }
581
582    #[test]
583    fn ceil() {
584        let v: Vector3<f64> = Vector3::new(1.1, 2.9, 3.0);
585        let expected = Vector3::new(2.0, 3.0, 3.0);
586        assert_eq!(v.ceil(), expected);
587    }
588
589    #[test]
590    fn floor() {
591        let v: Vector3<f64> = Vector3::new(1.1, 2.9, 3.0);
592        let expected = Vector3::new(1.0, 2.0, 3.0);
593        assert_eq!(v.floor(), expected);
594    }
595
596    #[test]
597    fn round() {
598        let v: Vector3<f64> = Vector3::new(1.1, 2.9, 3.5);
599        let expected = Vector3::new(1.0, 3.0, 4.0);
600        assert_eq!(v.round(), expected);
601    }
602
603    #[test]
604    fn clamp() {
605        let v = Vector3::new(0.0, 5.0, 10.0);
606        let min = 1.0;
607        let max = 9.0;
608        let expected = Vector3::new(1.0, 5.0, 9.0);
609        assert_eq!(v.clamp(min, max), expected);
610    }
611
612    #[test]
613    fn rotated() {
614        let v = Vector3::new(1.0, 0.0, 0.0);
615        let axis = Vector3::new(0.0, 0.0, 1.0);
616        let angle = core::f64::consts::FRAC_PI_2;
617        let rotated = v.rotated(&axis, angle);
618        let expected = Vector3::new(0.0, 1.0, 0.0);
619        assert!(rotated.fuzzy_equal(&expected, 1e-15));
620    }
621
622    #[test]
623    fn from_spherical() {
624        let radius = 1.0;
625        let polar = core::f64::consts::FRAC_PI_2;
626        let azimuth = 0.0;
627        let v = Vector3::from_spherical(radius, polar, azimuth);
628        let expected = Vector3::new(1.0, 0.0, 0.0);
629        assert!(v.fuzzy_equal(&expected, 1e-15));
630    }
631
632    #[test]
633    fn nan_dont_panic() {
634        let mut vec1: Vector3<f64> = Vector3::default();
635        vec1 /= f64::NAN;
636    }
637
638    #[test]
639    fn readme_example() {
640        type Vector3 = super::Vector3<f64>;
641
642        let v1 = Vector3::new(1, 2.5, 3);
643        let v2 = Vector3::from([9.0, 1.0, 4.0]);
644
645        let v3 = (v1 + v2) - (v1 - v2);
646        let v3 = v3.cross(&v2);
647        let v3 = v3 * v3.dot(&v1);
648        let v3 = v3 * 10.0 / 3.2;
649        let v3 = v3.normalized();
650        let v3 = v3.lerp(&Vector3::zero(), 0.25);
651        let v3 = v3.floor();
652
653        println!("{v3}");
654        println!("{}", v3.angle(&Vector3::z_axis()));
655        println!("{}", v3.fuzzy_equal(&Vector3::z_axis(), 2.0));
656    }
657    #[test]
658    fn conversion_box() {
659        let correct = Vector3::new(1, 2, 3);
660        let x = String::from("Vector3(1,2,3)").into_boxed_str();
661        assert_eq!(x.parse::<Vector3<i32>>().unwrap(), correct);
662    }
663    #[test]
664    fn from_slice() {
665        let correct = Vector3::new(1, 2, 3);
666        let arr = [1, 2, 3].as_ref();
667        let x = Vector3::try_from(arr).unwrap();
668        assert_eq!(correct, x);
669    }
670
671    #[test]
672    fn from_box_slice() {
673        let correct = Vector3::new(1, 2, 3);
674        let arr: Box<[i32]> = Box::from([1, 2, 3].as_ref());
675        let x = Vector3::try_from(arr).unwrap();
676        assert_eq!(correct, x);
677    }
678}