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    /// use vec3_rs::Vector3;
305    ///
306    /// let vector3 = Vector3::new(1.0, 2.0, 3.0);
307    /// ```
308    pub const fn new(x: T, y: T, z: T) -> Self {
309        Self { x, y, z }
310    }
311
312    /// Computes the dot product between this vector and another vector.
313    #[must_use]
314    #[inline]
315    pub fn dot(&self, target: Self) -> T {
316        let (x, y, z): (T, T, T) = target.into();
317        self.x.clone() * x + self.y.clone() * y + self.z.clone() * z
318    }
319
320    /// Computes the cross product between this vector and another vector.
321    #[must_use]
322    #[inline]
323    pub fn cross(&self, target: Self) -> Self {
324        let (x, y, z): (T, T, T) = target.into();
325        Self {
326            x: self.y.clone() * z.clone() - self.z.clone() * y.clone(),
327            y: self.z.clone() * x.clone() - self.x.clone() * z,
328            z: self.x.clone() * y - self.y.clone() * x,
329        }
330    }
331
332    /// Computes the component-wise maximum of this vector and another vector.
333    #[must_use]
334    #[inline]
335    pub fn max(&self, target: &Self) -> Self {
336        let x = if self.x > target.x {
337            self.x.clone()
338        } else {
339            target.x.clone()
340        };
341        let y = if self.y > target.y {
342            self.y.clone()
343        } else {
344            target.y.clone()
345        };
346        let z = if self.z > target.z {
347            self.z.clone()
348        } else {
349            target.z.clone()
350        };
351        Self { x, y, z }
352    }
353
354    /// Computes the component-wise minimum of this vector and another vector.
355    #[must_use]
356    #[inline]
357    pub fn min(&self, target: &Self) -> Self {
358        let x = if self.x < target.x {
359            self.x.clone()
360        } else {
361            target.x.clone()
362        };
363        let y = if self.y < target.y {
364            self.y.clone()
365        } else {
366            target.y.clone()
367        };
368        let z = if self.z < target.z {
369            self.z.clone()
370        } else {
371            target.z.clone()
372        };
373        Self { x, y, z }
374    }
375
376    /// Retrieves the X component of the vector.
377    pub const fn x(&self) -> &T {
378        &self.x
379    }
380
381    /// Retrieves the Y component of the vector.
382    pub const fn y(&self) -> &T {
383        &self.y
384    }
385
386    /// Retrieves the Z component of the vector.
387    pub const fn z(&self) -> &T {
388        &self.z
389    }
390}
391
392impl<T: Vector3Coordinate> core::fmt::Display for Vector3<T> {
393    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
394        write!(f, "Vector3({}, {}, {})", self.x, self.y, self.z)
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use core::ops::Sub;
402
403    #[test]
404    fn angle() {
405        let angle = core::f64::consts::PI / 2.0;
406        let calc_angle = Vector3::<f64>::x_axis().angle(Vector3::<f64>::y_axis());
407        assert!(calc_angle.sub(angle) <= f64::EPSILON);
408    }
409
410    #[test]
411    fn create() {
412        let my_vec: Vector3<f64> = Vector3::new(1.3, 0.0, -5.35501);
413        assert!((my_vec.x() - 1.3f64).abs() <= f64::EPSILON);
414        assert!((my_vec.y() - 0.0f64).abs() <= f64::EPSILON);
415        assert!((my_vec.z() - -5.35501f64).abs() <= f64::EPSILON);
416    }
417
418    #[test]
419    fn sum() {
420        let vec1 = Vector3::new(1.0, 2.0, 3.0);
421        let vec2 = Vector3::new(5.0, 0.0, -1.0);
422        assert_eq!(vec1 + vec2, Vector3::new(6.0, 2.0, 2.0));
423    }
424
425    #[test]
426    fn normalization() {
427        let mut test_vec: Vector3<f64> = Vector3::new(1.0, 2.3, 100.123);
428        test_vec.normalize();
429        assert_eq!(
430            test_vec,
431            Vector3::new(
432                0.009_984_583_160_766_44,
433                0.022_964_541_269_762_81,
434                0.999_686_419_805_418_3
435            )
436        );
437        assert!((1.0 - test_vec.magnitude()).abs() <= f64::EPSILON);
438    }
439
440    #[test]
441    fn lerp() {
442        let start = Vector3::new(0.0, 0.0, 0.0);
443        let end = Vector3::new(1.0, 2.0, 3.0);
444        let lerp_result = start.lerp(&end, 0.75);
445        assert_eq!(lerp_result, Vector3::new(0.75, 1.5, 2.25));
446    }
447
448    #[test]
449    fn dot_product() {
450        let vec1 = Vector3::new(1.0, 2.0, 3.0);
451        let vec2 = Vector3::new(5.0, 0.0, -1.0);
452        let dot_result = vec1.dot(vec2);
453        assert!((dot_result - 2.0f64).abs() <= f64::EPSILON);
454    }
455
456    #[test]
457    fn cross_product() {
458        let vec1 = Vector3::new(1.0, 0.0, 0.0);
459        let vec2 = Vector3::new(0.0, 1.0, 0.0);
460        let cross_result = vec1.cross(vec2);
461        assert_eq!(cross_result, Vector3::new(0.0, 0.0, 1.0));
462    }
463
464    #[test]
465    fn max_components() {
466        let vec1 = Vector3::new(1.0, 5.0, 3.0);
467        let vec2 = Vector3::new(3.0, 2.0, 4.0);
468        let max_result = vec1.max(&vec2);
469        assert_eq!(max_result, Vector3::new(3.0, 5.0, 4.0));
470    }
471
472    #[test]
473    fn min_components() {
474        let vec1 = Vector3::new(1.0, 5.0, 3.0);
475        let vec2 = Vector3::new(3.0, 2.0, 4.0);
476        let min_result = vec1.min(&vec2);
477        assert_eq!(min_result, Vector3::new(1.0, 2.0, 3.0));
478    }
479
480    #[test]
481    fn fuzzy_equality() {
482        let vec1 = Vector3::new(1.0, 2.0, 3.0);
483        let vec2 = Vector3::new(1.01, 1.99, 3.01);
484        let epsilon = 0.02;
485        let fuzzy_equal_result = vec1.fuzzy_equal(&vec2, epsilon);
486        assert!(fuzzy_equal_result);
487    }
488
489    #[test]
490    fn distance() {
491        let v1 = Vector3::new(1.0, 2.0, 3.0);
492        let v2 = Vector3::new(4.0, 6.0, 8.0);
493        assert!((v1.distance(v2) - (50.0f64).sqrt()).abs() <= f64::EPSILON);
494    }
495
496    #[test]
497    fn project() {
498        let v = Vector3::new(1.0, 2.0, 3.0);
499        let on_normal = Vector3::new(1.0, 0.0, 0.0);
500        let expected = Vector3::new(1.0, 0.0, 0.0);
501        assert_eq!(v.project(on_normal), expected);
502    }
503
504    #[test]
505    fn reflect() {
506        let v = Vector3::new(1.0, -1.0, 0.0);
507        let normal = Vector3::new(0.0, 1.0, 0.0);
508        let expected = Vector3::new(1.0, 1.0, 0.0);
509        assert_eq!(v.reflect(normal), expected);
510    }
511
512    #[test]
513    fn inverse() {
514        let v = Vector3::new(2.0, 4.0, 8.0);
515        let expected = Vector3::new(0.5, 0.25, 0.125);
516        assert_eq!(v.inverse(), expected);
517    }
518
519    #[test]
520    fn abs() {
521        let v = Vector3::new(-1.0, -2.0, 3.0);
522        let expected = Vector3::new(1.0, 2.0, 3.0);
523        assert_eq!(v.abs(), expected);
524    }
525
526    #[test]
527    fn ceil() {
528        let v = Vector3::new(1.1, 2.9, 3.0);
529        let expected = Vector3::new(2.0, 3.0, 3.0);
530        assert_eq!(v.ceil(), expected);
531    }
532
533    #[test]
534    fn floor() {
535        let v = Vector3::new(1.1, 2.9, 3.0);
536        let expected = Vector3::new(1.0, 2.0, 3.0);
537        assert_eq!(v.floor(), expected);
538    }
539
540    #[test]
541    fn round() {
542        let v = Vector3::new(1.1, 2.9, 3.5);
543        let expected = Vector3::new(1.0, 3.0, 4.0);
544        assert_eq!(v.round(), expected);
545    }
546
547    #[test]
548    fn clamp() {
549        let v = Vector3::new(0.0, 5.0, 10.0);
550        let min = 1.0;
551        let max = 9.0;
552        let expected = Vector3::new(1.0, 5.0, 9.0);
553        assert_eq!(v.clamp(min, max), expected);
554    }
555
556    #[test]
557    fn rotated() {
558        let v = Vector3::new(1.0, 0.0, 0.0);
559        let axis = Vector3::new(0.0, 0.0, 1.0);
560        let angle = core::f64::consts::FRAC_PI_2;
561        let rotated = v.rotated(axis, angle);
562        let expected = Vector3::new(0.0, 1.0, 0.0);
563        assert!(rotated.fuzzy_equal(&expected, 1e-15));
564    }
565
566    #[test]
567    fn from_spherical() {
568        let radius = 1.0;
569        let polar = core::f64::consts::FRAC_PI_2;
570        let azimuth = 0.0;
571        let v = Vector3::from_spherical(radius, polar, azimuth);
572        let expected = Vector3::new(1.0, 0.0, 0.0);
573        assert!(v.fuzzy_equal(&expected, 1e-15));
574    }
575
576    #[test]
577    fn nan_dont_panic() {
578        let mut vec1: Vector3<f64> = Vector3::default();
579        vec1 /= f64::NAN;
580    }
581
582    #[test]
583    fn readme_example() {
584        let mut v1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
585        let mut v2: Vector3<f64> = Vector3::new(3.0, 1.0, 2.0);
586
587        // Basic operations
588        let sum = v1 + v2;
589        let difference = v1 - v2;
590        let dot_product = v1.dot(v2);
591        let cross_product = v1.cross(v2);
592
593        // Other methods
594        let lerp_result = v1.lerp(&v2, 0.5);
595        let angle = v1.angle(v2);
596        let fuzzy_equal = v1.fuzzy_equal(&v2, 0.001);
597
598        println!("Sum: {sum}");
599        println!("Difference: {difference}");
600        println!("Dot product: {dot_product}");
601        println!("Cross product: {cross_product}");
602        println!("Lerp 50%: {lerp_result}");
603        println!("Angle: {angle}");
604        print!("Are they close enough?: {fuzzy_equal}");
605
606        v1.normalize();
607        v2.normalize();
608
609        println!("v1 normalized: {v1}");
610        println!("v2 normalized: {v2}");
611    }
612    #[test]
613    fn conversion_box() {
614        let correct = Vector3::new(1, 2, 3);
615        let x = String::from("Vector3(1,2,3)").into_boxed_str();
616        assert_eq!(x.parse::<Vector3<i32>>().unwrap(), correct);
617    }
618}