Skip to main content

embedded_vector/
lib.rs

1// Copyright (C) 2026 Jorge Andre Castro
2//
3// Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le modifier
4// selon les termes de la Licence Publique Générale GNU telle que publiée par la
5// Free Software Foundation, soit la version 2 de la licence, soit (à votre convention)
6// n'importe quelle version ultérieure.
7
8//! # embedded-vector
9//!
10//! Vecteurs `f32` 2D, 3D et 4D pour systèmes embarqués `no_std`.
11//!
12//! Sans dépendance standard, sans `unsafe`, sans allocation.
13//! Utilise [`embedded_f32_sqrt`] pour le calcul de norme.
14//!
15//! ## Exemple rapide
16//!
17//! ```rust
18//! use embedded_vector::{Vec2, Vec3, Vec4};
19//!
20//! // Arithmétique
21//! let a = Vec3::new(1.0, 2.0, 3.0);
22//! let b = Vec3::new(4.0, 5.0, 6.0);
23//! let c = a + b;                          // Vec3 [5, 7, 9]
24//!
25//! // Produit scalaire et produit vectoriel
26//! let d = a.dot(&b);                      // 32.0
27//! let e = a.cross(&b);                    // Vec3 [-3, 6, -3]
28//!
29//! // Norme et normalisation
30//! let n = a.norm().unwrap();              // √14 ≈ 3.742
31//! let u = a.normalize().unwrap();         // vecteur unitaire
32//!
33//! // Opérateurs
34//! let f = a * 2.0;                        // Vec3 [2, 4, 6]
35//! let g = -b;                             // Vec3 [-4, -5, -6]
36//! ```
37
38#![no_std]
39#![forbid(unsafe_code)]
40#![warn(missing_docs)]
41
42use embedded_f32_sqrt::sqrt;
43
44// ─────────────────────────────────────────────────────────────
45//  Erreurs
46// ─────────────────────────────────────────────────────────────
47
48/// Erreurs retournées par les opérations sur les vecteurs.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum VecError {
51    /// Norme nulle ou trop proche de zéro : normalisation impossible.
52    ZeroNorm,
53    /// Une valeur `NaN` ou infinie a été détectée.
54    NonFiniteValue,
55}
56
57// ─────────────────────────────────────────────────────────────
58//  Vec2
59// ─────────────────────────────────────────────────────────────
60
61/// Vecteur 2D en `f32`.
62#[derive(Debug, Clone, Copy, PartialEq)]
63pub struct Vec2 {
64    /// Composante X.
65    pub x: f32,
66    /// Composante Y.
67    pub y: f32,
68}
69
70impl Vec2 {
71    /// Crée un nouveau vecteur 2D.
72    #[inline]
73    pub const fn new(x: f32, y: f32) -> Self {
74        Self { x, y }
75    }
76
77    /// Vecteur nul `[0, 0]`.
78    pub const ZERO: Self = Self::new(0.0, 0.0);
79
80    /// Vecteur unité X `[1, 0]`.
81    pub const X: Self = Self::new(1.0, 0.0);
82
83    /// Vecteur unité Y `[0, 1]`.
84    pub const Y: Self = Self::new(0.0, 1.0);
85
86    /// Retourne les composantes sous la forme `[x, y]`.
87    #[inline]
88    pub fn as_array(&self) -> [f32; 2] {
89        [self.x, self.y]
90    }
91
92    /// Crée un vecteur depuis un tableau `[x, y]`.
93    #[inline]
94    pub const fn from_array(v: [f32; 2]) -> Self {
95        Self::new(v[0], v[1])
96    }
97
98    /// Produit scalaire : `self · rhs = x₁x₂ + y₁y₂`.
99    #[inline]
100    pub fn dot(&self, rhs: &Self) -> f32 {
101        self.x * rhs.x + self.y * rhs.y
102    }
103
104    /// Norme au carré : `x² + y²`. Infaillible.
105    #[inline]
106    pub fn norm_sq(&self) -> f32 {
107        self.dot(self)
108    }
109
110    /// Norme euclidienne via [`embedded_f32_sqrt`].
111    ///
112    /// Retourne [`VecError::NonFiniteValue`] si un composant est `NaN` ou infini.
113    pub fn norm(&self) -> Result<f32, VecError> {
114        let sq = self.norm_sq();
115        if !sq.is_finite() {
116            return Err(VecError::NonFiniteValue);
117        }
118        sqrt(sq).map_err(|_| VecError::ZeroNorm)
119    }
120
121    /// Vecteur unitaire dans la même direction.
122    ///
123    /// Retourne [`VecError::ZeroNorm`] si la norme est nulle.
124    /// Retourne [`VecError::NonFiniteValue`] si un composant est invalide.
125    pub fn normalize(&self) -> Result<Self, VecError> {
126        let n = self.norm()?;
127        if n < 1e-10 {
128            return Err(VecError::ZeroNorm);
129        }
130        Ok(Self::new(self.x / n, self.y / n))
131    }
132
133    /// Distance entre deux vecteurs.
134    pub fn distance(&self, rhs: &Self) -> Result<f32, VecError> {
135        (*self - *rhs).norm()
136    }
137
138    /// Interpolation linéaire : `self + t * (rhs - self)`, `t ∈ [0, 1]`.
139    #[inline]
140    pub fn lerp(&self, rhs: &Self, t: f32) -> Self {
141        Self::new(
142            self.x + t * (rhs.x - self.x),
143            self.y + t * (rhs.y - self.y),
144        )
145    }
146
147    /// Multiplication terme à terme (produit de Hadamard).
148    #[inline]
149    pub fn hadamard(&self, rhs: &Self) -> Self {
150        Self::new(self.x * rhs.x, self.y * rhs.y)
151    }
152
153    /// Retourne `true` si tous les composants sont finis (non NaN, non infini).
154    #[inline]
155    pub fn is_finite(&self) -> bool {
156        self.x.is_finite() && self.y.is_finite()
157    }
158}
159
160impl core::ops::Add for Vec2 {
161    type Output = Self;
162    #[inline]
163    fn add(self, rhs: Self) -> Self { Self::new(self.x + rhs.x, self.y + rhs.y) }
164}
165impl core::ops::Sub for Vec2 {
166    type Output = Self;
167    #[inline]
168    fn sub(self, rhs: Self) -> Self { Self::new(self.x - rhs.x, self.y - rhs.y) }
169}
170impl core::ops::Mul<f32> for Vec2 {
171    type Output = Self;
172    #[inline]
173    fn mul(self, s: f32) -> Self { Self::new(self.x * s, self.y * s) }
174}
175impl core::ops::Div<f32> for Vec2 {
176    type Output = Self;
177    #[inline]
178    fn div(self, s: f32) -> Self { Self::new(self.x / s, self.y / s) }
179}
180impl core::ops::Neg for Vec2 {
181    type Output = Self;
182    #[inline]
183    fn neg(self) -> Self { Self::new(-self.x, -self.y) }
184}
185impl core::ops::AddAssign for Vec2 {
186    #[inline]
187    fn add_assign(&mut self, rhs: Self) { self.x += rhs.x; self.y += rhs.y; }
188}
189impl core::ops::SubAssign for Vec2 {
190    #[inline]
191    fn sub_assign(&mut self, rhs: Self) { self.x -= rhs.x; self.y -= rhs.y; }
192}
193impl core::ops::MulAssign<f32> for Vec2 {
194    #[inline]
195    fn mul_assign(&mut self, s: f32) { self.x *= s; self.y *= s; }
196}
197
198// ─────────────────────────────────────────────────────────────
199//  Vec3
200// ─────────────────────────────────────────────────────────────
201
202/// Vecteur 3D en `f32`.
203#[derive(Debug, Clone, Copy, PartialEq)]
204pub struct Vec3 {
205    /// Composante X.
206    pub x: f32,
207    /// Composante Y.
208    pub y: f32,
209    /// Composante Z.
210    pub z: f32,
211}
212
213impl Vec3 {
214    /// Crée un nouveau vecteur 3D.
215    #[inline]
216    pub const fn new(x: f32, y: f32, z: f32) -> Self {
217        Self { x, y, z }
218    }
219
220    /// Vecteur nul `[0, 0, 0]`.
221    pub const ZERO: Self = Self::new(0.0, 0.0, 0.0);
222
223    /// Vecteur unité X `[1, 0, 0]`.
224    pub const X: Self = Self::new(1.0, 0.0, 0.0);
225
226    /// Vecteur unité Y `[0, 1, 0]`.
227    pub const Y: Self = Self::new(0.0, 1.0, 0.0);
228
229    /// Vecteur unité Z `[0, 0, 1]`.
230    pub const Z: Self = Self::new(0.0, 0.0, 1.0);
231
232    /// Retourne les composantes sous la forme `[x, y, z]`.
233    #[inline]
234    pub fn as_array(&self) -> [f32; 3] {
235        [self.x, self.y, self.z]
236    }
237
238    /// Crée un vecteur depuis un tableau `[x, y, z]`.
239    #[inline]
240    pub const fn from_array(v: [f32; 3]) -> Self {
241        Self::new(v[0], v[1], v[2])
242    }
243
244    /// Produit scalaire : `self · rhs = x₁x₂ + y₁y₂ + z₁z₂`.
245    #[inline]
246    pub fn dot(&self, rhs: &Self) -> f32 {
247        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
248    }
249
250    /// Produit vectoriel : `self × rhs`.
251    ///
252    /// Le résultat est perpendiculaire aux deux opérandes (règle de la main droite).
253    /// Propriété : `a × b = -(b × a)`.
254    #[inline]
255    pub fn cross(&self, rhs: &Self) -> Self {
256        Self::new(
257            self.y * rhs.z - self.z * rhs.y,
258            self.z * rhs.x - self.x * rhs.z,
259            self.x * rhs.y - self.y * rhs.x,
260        )
261    }
262
263    /// Norme au carré : `x² + y² + z²`. Infaillible.
264    #[inline]
265    pub fn norm_sq(&self) -> f32 {
266        self.dot(self)
267    }
268
269    /// Norme euclidienne via [`embedded_f32_sqrt`].
270    ///
271    /// Retourne [`VecError::NonFiniteValue`] si un composant est `NaN` ou infini.
272    pub fn norm(&self) -> Result<f32, VecError> {
273        let sq = self.norm_sq();
274        if !sq.is_finite() {
275            return Err(VecError::NonFiniteValue);
276        }
277        sqrt(sq).map_err(|_| VecError::ZeroNorm)
278    }
279
280    /// Vecteur unitaire dans la même direction.
281    ///
282    /// Retourne [`VecError::ZeroNorm`] si la norme est nulle.
283    /// Retourne [`VecError::NonFiniteValue`] si un composant est invalide.
284    pub fn normalize(&self) -> Result<Self, VecError> {
285        let n = self.norm()?;
286        if n < 1e-10 {
287            return Err(VecError::ZeroNorm);
288        }
289        Ok(Self::new(self.x / n, self.y / n, self.z / n))
290    }
291
292    /// Distance euclidienne entre deux vecteurs.
293    pub fn distance(&self, rhs: &Self) -> Result<f32, VecError> {
294        (*self - *rhs).norm()
295    }
296
297    /// Interpolation linéaire : `self + t * (rhs - self)`, `t ∈ [0, 1]`.
298    #[inline]
299    pub fn lerp(&self, rhs: &Self, t: f32) -> Self {
300        Self::new(
301            self.x + t * (rhs.x - self.x),
302            self.y + t * (rhs.y - self.y),
303            self.z + t * (rhs.z - self.z),
304        )
305    }
306
307    /// Multiplication terme à terme (produit de Hadamard).
308    #[inline]
309    pub fn hadamard(&self, rhs: &Self) -> Self {
310        Self::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z)
311    }
312
313    /// Projection de `self` sur `rhs` : `(self·rhs / |rhs|²) * rhs`.
314    ///
315    /// Retourne [`VecError::ZeroNorm`] si `rhs` est le vecteur nul.
316    pub fn project_onto(&self, rhs: &Self) -> Result<Self, VecError> {
317        let denom = rhs.norm_sq();
318        if denom < 1e-20 {
319            return Err(VecError::ZeroNorm);
320        }
321        Ok(*rhs * (self.dot(rhs) / denom))
322    }
323
324    /// Composante de `self` perpendiculaire à `rhs` : `self - proj(self, rhs)`.
325    pub fn reject_from(&self, rhs: &Self) -> Result<Self, VecError> {
326        Ok(*self - self.project_onto(rhs)?)
327    }
328
329    /// Retourne `true` si tous les composants sont finis (non NaN, non infini).
330    #[inline]
331    pub fn is_finite(&self) -> bool {
332        self.x.is_finite() && self.y.is_finite() && self.z.is_finite()
333    }
334}
335
336impl core::ops::Add for Vec3 {
337    type Output = Self;
338    #[inline]
339    fn add(self, rhs: Self) -> Self { Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) }
340}
341impl core::ops::Sub for Vec3 {
342    type Output = Self;
343    #[inline]
344    fn sub(self, rhs: Self) -> Self { Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) }
345}
346impl core::ops::Mul<f32> for Vec3 {
347    type Output = Self;
348    #[inline]
349    fn mul(self, s: f32) -> Self { Self::new(self.x * s, self.y * s, self.z * s) }
350}
351impl core::ops::Div<f32> for Vec3 {
352    type Output = Self;
353    #[inline]
354    fn div(self, s: f32) -> Self { Self::new(self.x / s, self.y / s, self.z / s) }
355}
356impl core::ops::Neg for Vec3 {
357    type Output = Self;
358    #[inline]
359    fn neg(self) -> Self { Self::new(-self.x, -self.y, -self.z) }
360}
361impl core::ops::AddAssign for Vec3 {
362    #[inline]
363    fn add_assign(&mut self, rhs: Self) { self.x += rhs.x; self.y += rhs.y; self.z += rhs.z; }
364}
365impl core::ops::SubAssign for Vec3 {
366    #[inline]
367    fn sub_assign(&mut self, rhs: Self) { self.x -= rhs.x; self.y -= rhs.y; self.z -= rhs.z; }
368}
369impl core::ops::MulAssign<f32> for Vec3 {
370    #[inline]
371    fn mul_assign(&mut self, s: f32) { self.x *= s; self.y *= s; self.z *= s; }
372}
373
374// ─────────────────────────────────────────────────────────────
375//  Vec4
376// ─────────────────────────────────────────────────────────────
377
378/// Vecteur 4D en `f32`.
379///
380/// Utile pour les coordonnées homogènes (`w=1` pour un point, `w=0` pour
381/// un vecteur de direction) et les couleurs RGBA.
382#[derive(Debug, Clone, Copy, PartialEq)]
383pub struct Vec4 {
384    /// Composante X.
385    pub x: f32,
386    /// Composante Y.
387    pub y: f32,
388    /// Composante Z.
389    pub z: f32,
390    /// Composante W.
391    pub w: f32,
392}
393
394impl Vec4 {
395    /// Crée un nouveau vecteur 4D.
396    #[inline]
397    pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
398        Self { x, y, z, w }
399    }
400
401    /// Vecteur nul `[0, 0, 0, 0]`.
402    pub const ZERO: Self = Self::new(0.0, 0.0, 0.0, 0.0);
403
404    /// Retourne les composantes sous la forme `[x, y, z, w]`.
405    #[inline]
406    pub fn as_array(&self) -> [f32; 4] {
407        [self.x, self.y, self.z, self.w]
408    }
409
410    /// Crée un vecteur depuis un tableau `[x, y, z, w]`.
411    #[inline]
412    pub const fn from_array(v: [f32; 4]) -> Self {
413        Self::new(v[0], v[1], v[2], v[3])
414    }
415
416    /// Construit un `Vec4` depuis un `Vec3` en ajoutant `w`.
417    ///
418    /// Convention : `w=1.0` pour un point, `w=0.0` pour une direction.
419    #[inline]
420    pub const fn from_vec3(v: Vec3, w: f32) -> Self {
421        Self::new(v.x, v.y, v.z, w)
422    }
423
424    /// Extrait les composantes XYZ comme `Vec3`, en ignorant `w`.
425    #[inline]
426    pub const fn xyz(&self) -> Vec3 {
427        Vec3::new(self.x, self.y, self.z)
428    }
429
430    /// Produit scalaire 4D : `x₁x₂ + y₁y₂ + z₁z₂ + w₁w₂`.
431    #[inline]
432    pub fn dot(&self, rhs: &Self) -> f32 {
433        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z + self.w * rhs.w
434    }
435
436    /// Norme au carré. Infaillible.
437    #[inline]
438    pub fn norm_sq(&self) -> f32 {
439        self.dot(self)
440    }
441
442    /// Norme euclidienne via [`embedded_f32_sqrt`].
443    pub fn norm(&self) -> Result<f32, VecError> {
444        let sq = self.norm_sq();
445        if !sq.is_finite() {
446            return Err(VecError::NonFiniteValue);
447        }
448        sqrt(sq).map_err(|_| VecError::ZeroNorm)
449    }
450
451    /// Vecteur unitaire dans la même direction.
452    pub fn normalize(&self) -> Result<Self, VecError> {
453        let n = self.norm()?;
454        if n < 1e-10 {
455            return Err(VecError::ZeroNorm);
456        }
457        Ok(Self::new(self.x / n, self.y / n, self.z / n, self.w / n))
458    }
459
460    /// Interpolation linéaire.
461    #[inline]
462    pub fn lerp(&self, rhs: &Self, t: f32) -> Self {
463        Self::new(
464            self.x + t * (rhs.x - self.x),
465            self.y + t * (rhs.y - self.y),
466            self.z + t * (rhs.z - self.z),
467            self.w + t * (rhs.w - self.w),
468        )
469    }
470
471    /// Multiplication terme à terme (produit de Hadamard).
472    #[inline]
473    pub fn hadamard(&self, rhs: &Self) -> Self {
474        Self::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z, self.w * rhs.w)
475    }
476
477    /// Retourne `true` si tous les composants sont finis.
478    #[inline]
479    pub fn is_finite(&self) -> bool {
480        self.x.is_finite() && self.y.is_finite()
481            && self.z.is_finite() && self.w.is_finite()
482    }
483}
484
485impl core::ops::Add for Vec4 {
486    type Output = Self;
487    #[inline]
488    fn add(self, rhs: Self) -> Self {
489        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z, self.w + rhs.w)
490    }
491}
492impl core::ops::Sub for Vec4 {
493    type Output = Self;
494    #[inline]
495    fn sub(self, rhs: Self) -> Self {
496        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z, self.w - rhs.w)
497    }
498}
499impl core::ops::Mul<f32> for Vec4 {
500    type Output = Self;
501    #[inline]
502    fn mul(self, s: f32) -> Self {
503        Self::new(self.x * s, self.y * s, self.z * s, self.w * s)
504    }
505}
506impl core::ops::Div<f32> for Vec4 {
507    type Output = Self;
508    #[inline]
509    fn div(self, s: f32) -> Self {
510        Self::new(self.x / s, self.y / s, self.z / s, self.w / s)
511    }
512}
513impl core::ops::Neg for Vec4 {
514    type Output = Self;
515    #[inline]
516    fn neg(self) -> Self { Self::new(-self.x, -self.y, -self.z, -self.w) }
517}
518impl core::ops::AddAssign for Vec4 {
519    #[inline]
520    fn add_assign(&mut self, rhs: Self) {
521        self.x += rhs.x; self.y += rhs.y; self.z += rhs.z; self.w += rhs.w;
522    }
523}
524impl core::ops::SubAssign for Vec4 {
525    #[inline]
526    fn sub_assign(&mut self, rhs: Self) {
527        self.x -= rhs.x; self.y -= rhs.y; self.z -= rhs.z; self.w -= rhs.w;
528    }
529}
530impl core::ops::MulAssign<f32> for Vec4 {
531    #[inline]
532    fn mul_assign(&mut self, s: f32) {
533        self.x *= s; self.y *= s; self.z *= s; self.w *= s;
534    }
535}
536
537// ─────────────────────────────────────────────────────────────
538//  Tests
539// ─────────────────────────────────────────────────────────────
540
541#[cfg(test)]
542mod tests {
543    use super::*;
544
545    //  Vec2
546
547    #[test]
548    fn v2_constants() {
549        assert_eq!(Vec2::ZERO.as_array(), [0.0, 0.0]);
550        assert_eq!(Vec2::X.as_array(), [1.0, 0.0]);
551        assert_eq!(Vec2::Y.as_array(), [0.0, 1.0]);
552    }
553
554    #[test]
555    fn v2_dot() {
556        let a = Vec2::new(1.0, 2.0);
557        let b = Vec2::new(3.0, 4.0);
558        assert!((a.dot(&b) - 11.0).abs() < 1e-6);
559    }
560
561    #[test]
562    fn v2_norm() {
563        // |[3,4]| = 5
564        assert!((Vec2::new(3.0, 4.0).norm().unwrap() - 5.0).abs() < 1e-5);
565    }
566
567    #[test]
568    fn v2_normalize() {
569        let u = Vec2::new(3.0, 0.0).normalize().unwrap();
570        assert!((u.x - 1.0).abs() < 1e-6);
571        assert!(u.y.abs() < 1e-6);
572        assert!((u.norm().unwrap() - 1.0).abs() < 1e-5);
573    }
574
575    #[test]
576    fn v2_normalize_zero() {
577        assert_eq!(Vec2::ZERO.normalize(), Err(VecError::ZeroNorm));
578    }
579
580    #[test]
581    fn v2_distance() {
582        assert!((Vec2::ZERO.distance(&Vec2::new(3.0, 4.0)).unwrap() - 5.0).abs() < 1e-5);
583    }
584
585    #[test]
586    fn v2_lerp() {
587        let m = Vec2::ZERO.lerp(&Vec2::new(2.0, 4.0), 0.5);
588        assert!((m.x - 1.0).abs() < 1e-6);
589        assert!((m.y - 2.0).abs() < 1e-6);
590    }
591
592    #[test]
593    fn v2_hadamard() {
594        let h = Vec2::new(2.0, 3.0).hadamard(&Vec2::new(4.0, 5.0));
595        assert!((h.x - 8.0).abs() < 1e-6);
596        assert!((h.y - 15.0).abs() < 1e-6);
597    }
598
599    #[test]
600    fn v2_ops() {
601        let a = Vec2::new(1.0, 2.0);
602        let b = Vec2::new(3.0, 4.0);
603        assert!((( a + b).x - 4.0).abs() < 1e-6);
604        assert!(((b - a).x - 2.0).abs() < 1e-6);
605        assert!((( a * 3.0).y - 6.0).abs() < 1e-6);
606        assert!((( b / 2.0).x - 1.5).abs() < 1e-6);
607        assert!(((-a).x + 1.0).abs() < 1e-6);
608    }
609
610    #[test]
611    fn v2_assign_ops() {
612        let mut v = Vec2::new(1.0, 1.0);
613        v += Vec2::new(2.0, 3.0);
614        assert!((v.x - 3.0).abs() < 1e-6);
615        v -= Vec2::new(1.0, 0.0);
616        assert!((v.x - 2.0).abs() < 1e-6);
617        v *= 2.0;
618        assert!((v.x - 4.0).abs() < 1e-6);
619    }
620
621    #[test]
622    fn v2_non_finite() {
623        let v = Vec2::new(f32::NAN, 0.0);
624        assert_eq!(v.norm(), Err(VecError::NonFiniteValue));
625        assert!(!v.is_finite());
626    }
627
628    // Vec3
629
630    #[test]
631    fn v3_constants() {
632        assert_eq!(Vec3::ZERO.as_array(), [0.0, 0.0, 0.0]);
633        assert_eq!(Vec3::X.as_array(), [1.0, 0.0, 0.0]);
634        assert_eq!(Vec3::Y.as_array(), [0.0, 1.0, 0.0]);
635        assert_eq!(Vec3::Z.as_array(), [0.0, 0.0, 1.0]);
636    }
637
638    #[test]
639    fn v3_dot() {
640        let a = Vec3::new(1.0, 2.0, 3.0);
641        let b = Vec3::new(4.0, 5.0, 6.0);
642        assert!((a.dot(&b) - 32.0).abs() < 1e-6);
643    }
644
645    #[test]
646    fn v3_cross_basis() {
647        // X × Y = Z,  Y × Z = X,  Z × X = Y
648        let z = Vec3::X.cross(&Vec3::Y);
649        assert!(z.x.abs() < 1e-6);
650        assert!(z.y.abs() < 1e-6);
651        assert!((z.z - 1.0).abs() < 1e-6);
652
653        let x = Vec3::Y.cross(&Vec3::Z);
654        assert!((x.x - 1.0).abs() < 1e-6);
655
656        let y = Vec3::Z.cross(&Vec3::X);
657        assert!((y.y - 1.0).abs() < 1e-6);
658    }
659
660    #[test]
661    fn v3_cross_anticommutative() {
662        let a = Vec3::new(1.0, 2.0, 3.0);
663        let b = Vec3::new(4.0, 5.0, 6.0);
664        let axb = a.cross(&b);
665        let bxa = b.cross(&a);
666        // a × b = -(b × a)
667        assert!((axb.x + bxa.x).abs() < 1e-6);
668        assert!((axb.y + bxa.y).abs() < 1e-6);
669        assert!((axb.z + bxa.z).abs() < 1e-6);
670    }
671
672    #[test]
673    fn v3_cross_perpendicular() {
674        let a = Vec3::new(1.0, 2.0, 3.0);
675        let b = Vec3::new(4.0, 5.0, 6.0);
676        let c = a.cross(&b);
677        assert!(a.dot(&c).abs() < 1e-5);
678        assert!(b.dot(&c).abs() < 1e-5);
679    }
680
681    #[test]
682    fn v3_norm() {
683        // |[1,2,2]| = 3
684        assert!((Vec3::new(1.0, 2.0, 2.0).norm().unwrap() - 3.0).abs() < 1e-5);
685    }
686
687    #[test]
688    fn v3_normalize() {
689        let u = Vec3::new(0.0, 0.0, 5.0).normalize().unwrap();
690        assert!(u.x.abs() < 1e-6);
691        assert!(u.y.abs() < 1e-6);
692        assert!((u.z - 1.0).abs() < 1e-6);
693        assert!((u.norm().unwrap() - 1.0).abs() < 1e-5);
694    }
695
696    #[test]
697    fn v3_normalize_zero() {
698        assert_eq!(Vec3::ZERO.normalize(), Err(VecError::ZeroNorm));
699    }
700
701    #[test]
702    fn v3_distance() {
703        let a = Vec3::new(1.0, 0.0, 0.0);
704        let b = Vec3::new(4.0, 0.0, 0.0);
705        assert!((a.distance(&b).unwrap() - 3.0).abs() < 1e-5);
706    }
707
708    #[test]
709    fn v3_lerp() {
710        let m = Vec3::ZERO.lerp(&Vec3::new(2.0, 4.0, 6.0), 0.5);
711        assert!((m.x - 1.0).abs() < 1e-6);
712        assert!((m.y - 2.0).abs() < 1e-6);
713        assert!((m.z - 3.0).abs() < 1e-6);
714    }
715
716    #[test]
717    fn v3_project_onto() {
718        let p = Vec3::new(3.0, 4.0, 0.0).project_onto(&Vec3::X).unwrap();
719        assert!((p.x - 3.0).abs() < 1e-6);
720        assert!(p.y.abs() < 1e-6);
721    }
722
723    #[test]
724    fn v3_project_onto_zero() {
725        assert_eq!(Vec3::X.project_onto(&Vec3::ZERO), Err(VecError::ZeroNorm));
726    }
727
728    #[test]
729    fn v3_reject_from() {
730        // [3,4,0] rejeté de X → [0,4,0]
731        let r = Vec3::new(3.0, 4.0, 0.0).reject_from(&Vec3::X).unwrap();
732        assert!(r.x.abs() < 1e-6);
733        assert!((r.y - 4.0).abs() < 1e-6);
734        assert!(r.z.abs() < 1e-6);
735    }
736
737    #[test]
738    fn v3_hadamard() {
739        let h = Vec3::new(1.0, 2.0, 3.0).hadamard(&Vec3::new(4.0, 5.0, 6.0));
740        assert!((h.x - 4.0).abs() < 1e-6);
741        assert!((h.y - 10.0).abs() < 1e-6);
742        assert!((h.z - 18.0).abs() < 1e-6);
743    }
744
745    #[test]
746    fn v3_ops() {
747        let a = Vec3::new(1.0, 2.0, 3.0);
748        let b = Vec3::new(4.0, 5.0, 6.0);
749        assert!(((a + b).x - 5.0).abs() < 1e-6);
750        assert!(((b - a).x - 3.0).abs() < 1e-6);
751        assert!(((a * 2.0).z - 6.0).abs() < 1e-6);
752        assert!(((b / 2.0).x - 2.0).abs() < 1e-6);
753        assert!(((-a).x + 1.0).abs() < 1e-6);
754    }
755
756    #[test]
757    fn v3_assign_ops() {
758        let mut v = Vec3::new(1.0, 1.0, 1.0);
759        v += Vec3::new(1.0, 2.0, 3.0);
760        assert!((v.z - 4.0).abs() < 1e-6);
761        v -= Vec3::new(0.0, 0.0, 1.0);
762        assert!((v.z - 3.0).abs() < 1e-6);
763        v *= 2.0;
764        assert!((v.z - 6.0).abs() < 1e-6);
765    }
766
767    #[test]
768    fn v3_non_finite() {
769        let v = Vec3::new(0.0, f32::INFINITY, 0.0);
770        assert_eq!(v.norm(), Err(VecError::NonFiniteValue));
771        assert!(!v.is_finite());
772    }
773
774    #[test]
775    fn v3_from_array_roundtrip() {
776        let arr = [1.0_f32, 2.0, 3.0];
777        assert_eq!(Vec3::from_array(arr).as_array(), arr);
778    }
779
780    // Vec4 
781
782    #[test]
783    fn v4_from_vec3() {
784        let v4 = Vec4::from_vec3(Vec3::new(1.0, 2.0, 3.0), 1.0);
785        assert!((v4.w - 1.0).abs() < 1e-6);
786        let back = v4.xyz();
787        assert!((back.x - 1.0).abs() < 1e-6);
788        assert!((back.z - 3.0).abs() < 1e-6);
789    }
790
791    #[test]
792    fn v4_dot() {
793        let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
794        assert!((a.dot(&Vec4::new(1.0, 0.0, 0.0, 0.0)) - 1.0).abs() < 1e-6);
795    }
796
797    #[test]
798    fn v4_norm() {
799        assert!((Vec4::new(1.0, 0.0, 0.0, 0.0).norm().unwrap() - 1.0).abs() < 1e-5);
800    }
801
802    #[test]
803    fn v4_normalize() {
804        let u = Vec4::new(2.0, 0.0, 0.0, 0.0).normalize().unwrap();
805        assert!((u.x - 1.0).abs() < 1e-6);
806        assert!((u.norm().unwrap() - 1.0).abs() < 1e-5);
807    }
808
809    #[test]
810    fn v4_lerp() {
811        let m = Vec4::ZERO.lerp(&Vec4::new(2.0, 4.0, 6.0, 8.0), 0.5);
812        assert!((m.w - 4.0).abs() < 1e-6);
813    }
814
815    #[test]
816    fn v4_ops() {
817        let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
818        let b = Vec4::new(1.0, 1.0, 1.0, 1.0);
819        assert!(((a + b).w - 5.0).abs() < 1e-6);
820        assert!(((a * 2.0).w - 8.0).abs() < 1e-6);
821        assert!(((-a).x + 1.0).abs() < 1e-6);
822    }
823
824    #[test]
825    fn v4_non_finite() {
826        assert!(!Vec4::new(0.0, 0.0, 0.0, f32::NAN).is_finite());
827    }
828
829    #[test]
830    fn v4_hadamard() {
831        let h = Vec4::new(1.0, 2.0, 3.0, 4.0).hadamard(&Vec4::new(2.0, 2.0, 2.0, 2.0));
832        assert!((h.w - 8.0).abs() < 1e-6);
833    }
834}