rs_math3d/
primitives.rs

1// Copyright 2020-Present (c) Raja Lehtihet & Wael El Oraiby
2//
3// Redistribution and use in source and binary forms, with or without
4// modification, are permitted provided that the following conditions are met:
5//
6// 1. Redistributions of source code must retain the above copyright notice,
7// this list of conditions and the following disclaimer.
8//
9// 2. Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12//
13// 3. Neither the name of the copyright holder nor the names of its contributors
14// may be used to endorse or promote products derived from this software without
15// specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27// POSSIBILITY OF SUCH DAMAGE.
28
29//! Geometric primitives for 3D graphics and collision detection.
30//!
31//! This module provides common geometric shapes and primitives used in
32//! 3D graphics, physics simulations, and collision detection systems.
33//!
34//! # Examples
35//!
36//! ```
37//! use rs_math3d::primitives::{Ray, Plane, Tri3};
38//! use rs_math3d::vector::Vector3;
39//! 
40//! // Create a ray from origin pointing along +X axis
41//! let ray = Ray::new(
42//!     Vector3::new(0.0, 0.0, 0.0),
43//!     Vector3::new(1.0, 0.0, 0.0)
44//! );
45//! 
46//! // Create a plane at z=5 facing down
47//! let plane = Plane::new(
48//!     Vector3::new(0.0, 0.0, 1.0),
49//!     -5.0
50//! );
51//! ```
52
53use crate::matrix::*;
54use crate::scalar::*;
55use crate::vector::*;
56use num_traits::{Zero, One};
57
58/// A 2D dimension with width and height.
59#[repr(C)]
60#[derive(Debug, Clone, Copy, Default)]
61pub struct Dimension<T: Scalar> {
62    pub width: T,
63    pub height: T,
64}
65
66impl<T: Scalar> Dimension<T> {
67    /// Creates a new dimension with the specified width and height.
68    pub fn new(width: T, height: T) -> Self {
69        Self { width, height }
70    }
71}
72
73/// A 2D axis-aligned rectangle.
74///
75/// Defined by its top-left corner position (x, y) and dimensions (width, height).
76#[repr(C)]
77#[derive(Debug, Clone, Copy, Default)]
78pub struct Rect<T: Scalar> {
79    pub x: T,
80    pub y: T,
81    pub width: T,
82    pub height: T,
83}
84
85impl<T: Scalar> Rect<T> {
86    /// Creates a new rectangle from position and dimensions.
87    pub fn new(x: T, y: T, width: T, height: T) -> Self {
88        Self {
89            x,
90            y,
91            width,
92            height,
93        }
94    }
95    /// Creates a rectangle from two corner points.
96    ///
97    /// The points don't need to be min/max - they will be sorted.
98    pub fn from(min_vec: &Vector2<T>, max_vec: &Vector2<T>) -> Self {
99        let min_x = T::min(min_vec.x, max_vec.x);
100        let min_y = T::min(min_vec.y, max_vec.y);
101        let max_x = T::max(min_vec.x, max_vec.x);
102        let max_y = T::max(min_vec.y, max_vec.y);
103        Self {
104            x: min_x,
105            y: min_y,
106            width: max_x - min_x,
107            height: max_y - min_y,
108        }
109    }
110
111    /// Returns the minimum corner (top-left) of the rectangle.
112    pub fn min(&self) -> Vector2<T> {
113        Vector2::new(self.x, self.y)
114    }
115    /// Returns the maximum corner (bottom-right) of the rectangle.
116    pub fn max(&self) -> Vector2<T> {
117        Vector2::new(self.x + self.width, self.y + self.height)
118    }
119    /// Computes the intersection of two rectangles.
120    ///
121    /// Returns `None` if the rectangles don't overlap.
122    pub fn intersect(&self, other: &Self) -> Option<Self> {
123        let smx = self.max();
124        let smn = self.min();
125        let omx = other.max();
126        let omn = other.min();
127
128        if smx.x < omn.x || smx.y < omn.y || smn.x > omx.x || smn.y > omx.y {
129            return None;
130        }
131
132        let min_vec = Vector2::max(&self.min(), &other.min());
133        let max_vec = Vector2::min(&self.max(), &other.max());
134        Some(Self::from(&min_vec, &max_vec))
135    }
136
137    /// Checks if a point is inside the rectangle.
138    pub fn contains(&self, p: &Vector2<T>) -> bool {
139        p.x >= self.x && p.y >= self.y && p.x <= self.x + self.width && p.y <= self.y + self.height
140    }
141}
142
143/// A 3D axis-aligned bounding box (AABB).
144///
145/// Defined by its minimum and maximum corners.
146#[repr(C)]
147#[derive(Debug, Clone, Copy, Default)]
148pub struct Box3<T: Scalar> {
149    pub min: Vector3<T>,
150    pub max: Vector3<T>,
151}
152
153impl<T: Scalar> Box3<T> {
154    /// Creates a new box from two corner points.
155    ///
156    /// The points are automatically sorted to find min/max.
157    pub fn new(v0: &Vector3<T>, v1: &Vector3<T>) -> Self {
158        Self {
159            min: Vector3::min(v0, v1),
160            max: Vector3::max(v0, v1),
161        }
162    }
163    /// Returns the center point of the box.
164    pub fn center(&self) -> Vector3<T> {
165        (self.max + self.min) * T::half()
166    }
167    /// Returns the half-extents of the box from center to max.
168    pub fn extent(&self) -> Vector3<T> {
169        self.max - self.center()
170    }
171    /// Checks if two boxes overlap.
172    pub fn overlap(&self, other: &Self) -> bool {
173        if self.max.x < other.min.x {
174            return false;
175        }
176        if self.max.y < other.min.y {
177            return false;
178        }
179        if self.max.z < other.min.z {
180            return false;
181        }
182
183        if self.min.x > other.max.x {
184            return false;
185        }
186        if self.min.x > other.max.x {
187            return false;
188        }
189        if self.min.x > other.max.x {
190            return false;
191        }
192        return true;
193    }
194
195    /// Expands the box to include a point.
196    pub fn add(&self, p: &Vector3<T>) -> Self {
197        Self {
198            min: Vector3::min(&p, &self.min),
199            max: Vector3::max(&p, &self.max),
200        }
201    }
202
203    /// Subdivides the box into 8 octants.
204    ///
205    /// Returns an array of 8 boxes representing the octree subdivision.
206    pub fn subdivide(&self) -> [Self; 8] {
207        let cube_table: [Vector3<i32>; 8] = [
208            Vector3::new(0, 1, 0),
209            Vector3::new(1, 1, 0),
210            Vector3::new(1, 1, 1),
211            Vector3::new(0, 1, 1),
212            Vector3::new(0, 0, 0),
213            Vector3::new(1, 0, 0),
214            Vector3::new(1, 0, 1),
215            Vector3::new(0, 0, 1),
216        ];
217
218        let ps: [Vector3<T>; 2] = [self.min, self.max];
219        let mut vs = [Vector3::zero(); 8];
220        for i in 0..8 {
221            vs[i] = Vector3::new(
222                ps[cube_table[i].x as usize].x,
223                ps[cube_table[i].y as usize].y,
224                ps[cube_table[i].z as usize].z,
225            );
226        }
227
228        let c = self.center();
229        let mut out = [Box3 {
230            min: Vector3::zero(),
231            max: Vector3::zero(),
232        }; 8];
233        for i in 0..8 {
234            out[i] = Self::new(&Vector3::min(&c, &vs[i]), &Vector3::max(&c, &vs[i]));
235        }
236        out
237    }
238}
239
240/// An infinite line in 2D or 3D space.
241///
242/// Defined by a point on the line and a direction vector.
243#[repr(C)]
244#[derive(Debug, Clone, Copy, Default)]
245pub struct Line<T: Scalar, V: Vector<T>> {
246    pub p: V, // point on the line
247    pub d: V, // direction of the line
248    t: core::marker::PhantomData<T>,
249}
250
251impl<T: Scalar, V: Vector<T>> Line<T, V> {
252    /// Creates a new line from a point and direction.
253    pub fn new(p: &V, d: &V) -> Self {
254        Self {
255            p: *p,
256            d: *d,
257            t: core::marker::PhantomData,
258        }
259    }
260    /// Creates a line from two points.
261    ///
262    /// The line passes through both points, with direction from s to e.
263    pub fn from_start_end(s: &V, e: &V) -> Self {
264        Self {
265            p: *s,
266            d: *e - *s,
267            t: core::marker::PhantomData,
268        }
269    }
270    /// Finds the closest point on the line to a given point.
271    ///
272    /// Returns:
273    /// - `t`: Parameter value where the closest point occurs
274    /// - Point: The closest point on the line
275    pub fn closest_point_on_line(&self, p: &V) -> (T, V) {
276        let p_dir = *p - self.p;
277
278        let d_sp = V::dot(&self.d, &p_dir);
279        let d_ss = V::dot(&self.d, &self.d);
280
281        let t = d_sp / d_ss;
282
283        (t, self.p + self.d * t)
284    }
285}
286
287impl<T: FloatScalar, V: FloatVector<T>> Line<T, V> {
288    /// Returns a line with normalized direction vector.
289    pub fn normalize(&self) -> Self {
290        Self {
291            p: self.p,
292            d: FloatVector::normalize(&self.d),
293            t: core::marker::PhantomData,
294        }
295    }
296}
297
298/// Finds the shortest segment connecting two 3D lines.
299///
300/// Returns `None` if the lines are parallel (within epsilon tolerance).
301pub fn shortest_segment3d_between_lines3d<T: FloatScalar>(
302    line0: &Line<T, Vector3<T>>,
303    line1: &Line<T, Vector3<T>>,
304    epsilon: T,
305) -> Option<Segment<T, Vector3<T>>> {
306    let s0 = line0.p;
307    let s1 = line1.p;
308
309    let d1 = line1.d;
310    let d0 = line0.d;
311
312    let normal = Vector3::normalize(&Vector3::cross(&d1, &d0));
313    let n0 = Vector3::normalize(&Vector3::cross(&normal, &d0));
314    let n1 = Vector3::normalize(&Vector3::cross(&normal, &d1));
315
316    let plane0 = Plane::new(&n0, &s0);
317    let plane1 = Plane::new(&n1, &s1);
318
319    let p1 = plane0.intersect_line(line1, epsilon);
320    let p0 = plane1.intersect_line(line0, epsilon);
321
322    match (p0, p1) {
323        (Some((_, s)), Some((_, e))) => Some(Segment::new(&s, &e)),
324        _ => None,
325    }
326}
327
328/// A line segment with defined start and end points.
329#[repr(C)]
330#[derive(Clone, Copy, Default)]
331pub struct Segment<T: Scalar, V: Vector<T>> {
332    pub s: V, // start
333    pub e: V, // end
334    t: core::marker::PhantomData<T>,
335}
336
337impl<T: Scalar, V: Vector<T>> Segment<T, V> {
338    /// Creates a new segment from start to end point.
339    pub fn new(s: &V, e: &V) -> Self {
340        Self {
341            s: *s,
342            e: *e,
343            t: core::marker::PhantomData,
344        }
345    }
346    /// Finds the closest point on the segment to a given point.
347    ///
348    /// Returns:
349    /// - `t`: Parameter value \[0,1\] where 0=start, 1=end
350    /// - Point: The closest point on the segment
351    pub fn closest_point_on_segment(&self, p: &V) -> (T, V) {
352        let dir = self.e - self.s;
353        let p_dir = *p - self.s;
354
355        let d_sp = V::dot(&dir, &p_dir);
356        let d_ss = V::dot(&dir, &dir);
357
358        if d_sp < <T as Zero>::zero() {
359            return (<T as Zero>::zero(), self.s);
360        } else if d_sp > d_ss {
361            return (<T as One>::one(), self.e);
362        }
363
364        let t = d_sp / d_ss;
365
366        (t, self.s + dir * t)
367    }
368}
369
370impl<T: FloatScalar, V: FloatVector<T>> Segment<T, V> {
371    /// Computes the distance from a point to the segment.
372    pub fn distance(&self, p: &V) -> T {
373        let (_, p_on_seg) = self.closest_point_on_segment(p);
374        V::length(&(p_on_seg - *p))
375    }
376}
377
378/// A ray with an origin and direction.
379///
380/// Commonly used for ray casting and intersection tests.
381#[repr(C)]
382#[derive(Clone, Copy, Default)]
383pub struct Ray<T: Scalar, V: Vector<T>> {
384    pub start: V,
385    pub direction: V,
386    t: core::marker::PhantomData<T>,
387}
388
389impl<T: FloatScalar, V: FloatVector<T>> Ray<T, V> {
390    /// Creates a new ray with normalized direction.
391    pub fn new(start: &V, direction: &V) -> Self {
392        Self {
393            start: *start,
394            direction: V::normalize(direction),
395            t: core::marker::PhantomData,
396        }
397    }
398}
399
400impl<T: Scalar> Ray<T, Vector3<T>> {
401    /// Computes ray-plane intersection.
402    ///
403    /// Returns the intersection point, or `None` if the ray doesn't hit the plane
404    /// (parallel or pointing away).
405    pub fn intersect_plane(&self, p: &Plane<T>) -> Option<Vector3<T>> {
406        let n = p.normal();
407        let t: T = -(p.d + Vector3::dot(&n, &self.start)) / Vector3::dot(&n, &self.direction);
408        if t < <T as Zero>::zero() {
409            None
410        } else {
411            Some(self.direction * t + self.start)
412        }
413    }
414}
415
416////////////////////////////////////////////////////////////////////////////////
417/// Sphere3
418////////////////////////////////////////////////////////////////////////////////
419#[repr(C)]
420#[derive(Clone, Copy, Default)]
421pub struct Sphere3<T: FloatScalar> {
422    pub center: Vector3<T>,
423    pub radius: T,
424}
425
426impl<T: FloatScalar> Sphere3<T> {
427    pub fn new(center: Vector3<T>, radius: T) -> Self {
428        Self {
429            center: center,
430            radius: radius,
431        }
432    }
433}
434
435////////////////////////////////////////////////////////////////////////////////
436/// Triangle
437////////////////////////////////////////////////////////////////////////////////
438#[repr(C)]
439#[derive(Clone, Copy, Default)]
440pub struct Tri3<T: FloatScalar> {
441    vertices: [Vector3<T>; 3],
442}
443
444impl<T: FloatScalar> Tri3<T> {
445    pub fn new(vertices: [Vector3<T>; 3]) -> Self {
446        Self { vertices: vertices }
447    }
448    pub fn vertices(&self) -> &[Vector3<T>; 3] {
449        &self.vertices
450    }
451
452    pub fn barycentric_coordinates(&self, pt: &Vector3<T>) -> Vector3<T> {
453        let v0 = self.vertices[0];
454        let v1 = self.vertices[1];
455        let v2 = self.vertices[2];
456        let vv1 = v1 - v0;
457        let vv2 = v2 - v0;
458        let vvc = Vector3::cross(&vv1, &vv2);
459        let m = Matrix3::new(
460            vv1.x, vv1.y, vv1.z, vv2.x, vv2.y, vv2.z, vvc.x, vvc.y, vvc.z,
461        );
462        let lambda = m.inverse() * (*pt - v0);
463        Vector3::new(<T as One>::one() - lambda.x - lambda.y, lambda.x, lambda.y)
464    }
465}
466
467////////////////////////////////////////////////////////////////////////////////
468/// Plane
469////////////////////////////////////////////////////////////////////////////////
470#[repr(C)]
471#[derive(Clone, Copy, Default)]
472pub struct Plane<T: Scalar> {
473    a: T,
474    b: T,
475    c: T,
476    d: T,
477}
478
479impl<T: Scalar> Plane<T> {
480    pub fn normal(&self) -> Vector3<T> {
481        Vector3::new(self.a, self.b, self.c)
482    }
483    pub fn constant(&self) -> T {
484        self.d
485    }
486
487    pub fn intersect_ray(&self, r: &Ray<T, Vector3<T>>) -> Option<Vector3<T>> {
488        r.intersect_plane(self)
489    }
490
491    /// Computes line-plane intersection.
492    ///
493    /// Returns the parameter t and intersection point, or `None` if parallel.
494    pub fn intersect_line(
495        &self,
496        line: &Line<T, Vector3<T>>,
497        epsilon: T,
498    ) -> Option<(T, Vector3<T>)> {
499        let s = line.p;
500        let dir = line.d;
501        let n = self.normal();
502
503        let denom = Vector3::dot(&n, &dir);
504        if denom.tabs() < epsilon {
505            None
506        } else {
507            let t = -(self.constant() + Vector3::dot(&n, &s)) / denom;
508            Some((t, dir * t + s))
509        }
510    }
511}
512
513impl<T: FloatScalar> Plane<T> {
514    pub fn new(n: &Vector3<T>, p: &Vector3<T>) -> Self {
515        let norm = Vector3::normalize(n);
516        let d = Vector3::dot(&norm, p);
517        Self {
518            a: norm.x,
519            b: norm.y,
520            c: norm.z,
521            d: -d,
522        }
523    }
524
525    pub fn from_tri(v0: &Vector3<T>, v1: &Vector3<T>, v2: &Vector3<T>) -> Self {
526        let n = tri_normal(v0, v1, v2);
527        Self::new(&n, v0)
528    }
529    pub fn from_quad(v0: &Vector3<T>, v1: &Vector3<T>, v2: &Vector3<T>, v3: &Vector3<T>) -> Self {
530        let n = quad_normal(v0, v1, v2, v3);
531        let c = (*v0 + *v1 + *v2 + *v3) * T::quarter();
532        Self::new(&n, &c)
533    }
534}
535
536////////////////////////////////////////////////////////////////////////////////
537/// Parametric Plane
538////////////////////////////////////////////////////////////////////////////////
539#[repr(C)]
540#[derive(Clone, Copy, Default)]
541pub struct ParametricPlane<T: Scalar> {
542    pub center: Vector3<T>,
543    pub x_axis: Vector3<T>,
544    pub y_axis: Vector3<T>,
545}
546
547impl<T: FloatScalar> ParametricPlane<T> {
548    pub fn new(center: &Vector3<T>, x_axis: &Vector3<T>, y_axis: &Vector3<T>) -> Self {
549        Self {
550            center: *center,
551            x_axis: *x_axis,
552            y_axis: *y_axis,
553        }
554    }
555
556    pub fn plane(&self) -> Plane<T> {
557        Plane::new(&self.normal(), &self.center)
558    }
559
560    /// Computes the normal vector (cross product of axes).
561    pub fn normal(&self) -> Vector3<T> {
562        Vector3::cross(&self.x_axis, &self.y_axis).normalize()
563    }
564
565    pub fn intersect_ray(&self, r: &Ray<T, Vector3<T>>) -> Option<Vector3<T>> {
566        r.intersect_plane(&self.plane())
567    }
568
569    pub fn intersect_line(
570        &self,
571        line: &Line<T, Vector3<T>>,
572        epsilon: T,
573    ) -> Option<(T, Vector3<T>)> {
574        self.plane().intersect_line(line, epsilon)
575    }
576
577    pub fn project(&self, v: &Vector3<T>) -> Vector2<T> {
578        let p = *v - self.center;
579        let x_coord = dot(&p, &self.x_axis) / dot(&self.x_axis, &self.x_axis);
580        let y_coord = dot(&p, &self.y_axis) / dot(&self.y_axis, &self.y_axis);
581        Vector2::new(x_coord, y_coord)
582    }
583}
584
585////////////////////////////////////////////////////////////////////////////////
586/// Tri
587////////////////////////////////////////////////////////////////////////////////
588pub fn tri_normal<T: FloatScalar>(v0: &Vector3<T>, v1: &Vector3<T>, v2: &Vector3<T>) -> Vector3<T> {
589    let v10 = *v1 - *v0;
590    let v20 = *v2 - *v0;
591    Vector3::normalize(&Vector3::cross(&v10, &v20))
592}
593
594/// Computes the normal vector of a quadrilateral.
595///
596/// Uses the cross product of the two diagonals for a more stable result.
597pub fn quad_normal<T: FloatScalar>(
598    v0: &Vector3<T>,
599    v1: &Vector3<T>,
600    v2: &Vector3<T>,
601    v3: &Vector3<T>,
602) -> Vector3<T> {
603    let v20 = *v2 - *v0;
604    let v31 = *v3 - *v1;
605    Vector3::normalize(&Vector3::cross(&v20, &v31))
606}
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611    #[test]
612    pub fn test_barycentric() {
613        let v0 = Vector3::new(0.0, 0.0, 0.0);
614        let v1 = Vector3::new(0.0, 1.0, 0.0);
615        let v2 = Vector3::new(0.0, 0.0, 1.0);
616
617        let tri = Tri3::new([v0, v1, v2]);
618        let pp0 = tri.barycentric_coordinates(&v0);
619        assert!(f32::abs(pp0.x - 1.0) < 0.01);
620        assert!(f32::abs(pp0.y) < 0.01);
621        assert!(f32::abs(pp0.z) < 0.01);
622
623        let pp1 = tri.barycentric_coordinates(&v1);
624        assert!(f32::abs(pp1.x) < 0.01);
625        assert!(f32::abs(pp1.y - 1.0) < 0.01);
626        assert!(f32::abs(pp1.z) < 0.01);
627
628        let pp2 = tri.barycentric_coordinates(&v2);
629        assert!(f32::abs(pp2.x) < 0.01);
630        assert!(f32::abs(pp2.y) < 0.01);
631        assert!(f32::abs(pp2.z - 1.0) < 0.01);
632    }
633
634    #[test]
635    pub fn test_barycentric_translated() {
636        // Test with a triangle not at the origin
637        let v0 = Vector3::new(1.0, 2.0, 3.0);
638        let v1 = Vector3::new(4.0, 2.0, 3.0);
639        let v2 = Vector3::new(1.0, 5.0, 3.0);
640
641        let tri = Tri3::new([v0, v1, v2]);
642        
643        // Test vertices
644        let pp0 = tri.barycentric_coordinates(&v0);
645        assert!(f32::abs(pp0.x - 1.0) < 0.001);
646        assert!(f32::abs(pp0.y) < 0.001);
647        assert!(f32::abs(pp0.z) < 0.001);
648
649        let pp1 = tri.barycentric_coordinates(&v1);
650        assert!(f32::abs(pp1.x) < 0.001);
651        assert!(f32::abs(pp1.y - 1.0) < 0.001);
652        assert!(f32::abs(pp1.z) < 0.001);
653
654        let pp2 = tri.barycentric_coordinates(&v2);
655        assert!(f32::abs(pp2.x) < 0.001);
656        assert!(f32::abs(pp2.y) < 0.001);
657        assert!(f32::abs(pp2.z - 1.0) < 0.001);
658
659        // Test center point
660        let center = (v0 + v1 + v2) / 3.0;
661        let pp_center = tri.barycentric_coordinates(&center);
662        assert!(f32::abs(pp_center.x - 1.0/3.0) < 0.001);
663        assert!(f32::abs(pp_center.y - 1.0/3.0) < 0.001);
664        assert!(f32::abs(pp_center.z - 1.0/3.0) < 0.001);
665        
666        // Verify barycentric coordinates sum to 1
667        assert!(f32::abs((pp_center.x + pp_center.y + pp_center.z) - 1.0) < 0.001);
668    }
669
670    #[test]
671    pub fn test_barycentric_edge_midpoints() {
672        let v0 = Vector3::new(0.0, 0.0, 0.0);
673        let v1 = Vector3::new(2.0, 0.0, 0.0);
674        let v2 = Vector3::new(0.0, 2.0, 0.0);
675
676        let tri = Tri3::new([v0, v1, v2]);
677        
678        // Midpoint of edge v0-v1
679        let mid01 = (v0 + v1) / 2.0;
680        let pp_mid01 = tri.barycentric_coordinates(&mid01);
681        assert!(f32::abs(pp_mid01.x - 0.5) < 0.001);
682        assert!(f32::abs(pp_mid01.y - 0.5) < 0.001);
683        assert!(f32::abs(pp_mid01.z) < 0.001);
684
685        // Midpoint of edge v0-v2
686        let mid02 = (v0 + v2) / 2.0;
687        let pp_mid02 = tri.barycentric_coordinates(&mid02);
688        assert!(f32::abs(pp_mid02.x - 0.5) < 0.001);
689        assert!(f32::abs(pp_mid02.y) < 0.001);
690        assert!(f32::abs(pp_mid02.z - 0.5) < 0.001);
691
692        // Midpoint of edge v1-v2
693        let mid12 = (v1 + v2) / 2.0;
694        let pp_mid12 = tri.barycentric_coordinates(&mid12);
695        assert!(f32::abs(pp_mid12.x) < 0.001);
696        assert!(f32::abs(pp_mid12.y - 0.5) < 0.001);
697        assert!(f32::abs(pp_mid12.z - 0.5) < 0.001);
698    }
699
700    #[test]
701    pub fn test_barycentric_interpolation() {
702        // Test that we can reconstruct points using barycentric coordinates
703        let v0 = Vector3::new(1.0, 0.0, 0.0);
704        let v1 = Vector3::new(0.0, 1.0, 0.0);
705        let v2 = Vector3::new(0.0, 0.0, 1.0);
706
707        let tri = Tri3::new([v0, v1, v2]);
708        
709        // Test arbitrary point inside triangle
710        let test_point = Vector3::new(0.2, 0.3, 0.5);
711        let bary = tri.barycentric_coordinates(&test_point);
712        
713        // Reconstruct the point using barycentric coordinates
714        let reconstructed = v0 * bary.x + v1 * bary.y + v2 * bary.z;
715        
716        assert!(f32::abs(reconstructed.x - test_point.x) < 0.001);
717        assert!(f32::abs(reconstructed.y - test_point.y) < 0.001);
718        assert!(f32::abs(reconstructed.z - test_point.z) < 0.001);
719    }
720}