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