Skip to main content

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//! Discrete storage types such as rectangles and boxes work with integer scalars.
35//! Metric and analytic primitives such as lines, rays, planes, spheres, and
36//! triangles are intended for floating-point use.
37//!
38//! Constructors that normalize directions or normals provide checked `try_`
39//! variants. The unchecked variants assume non-degenerate input and panic when
40//! that precondition is violated.
41//!
42//! # Examples
43//!
44//! ```
45//! use rs_math3d::primitives::{Ray, Plane, Tri3};
46//! use rs_math3d::vector::Vector3;
47//! use rs_math3d::EPS_F32;
48//!
49//! // Create a ray from origin pointing along +X axis
50//! let ray = Ray::new(
51//!     &Vector3::new(0.0f32, 0.0, 0.0),
52//!     &Vector3::new(1.0, 0.0, 0.0),
53//!     EPS_F32,
54//! ).unwrap();
55//!
56//! // Create a plane at z=5 facing down
57//! let plane = Plane::new(
58//!     &Vector3::new(0.0f32, 0.0, -1.0),
59//!     &Vector3::new(0.0, 0.0, 5.0)
60//! );
61//!
62//! let checked = Plane::try_new(
63//!     &Vector3::new(0.0f32, 0.0, -1.0),
64//!     &Vector3::new(0.0, 0.0, 5.0),
65//!     EPS_F32,
66//! ).expect("plane normal should be valid");
67//! assert_eq!(plane.constant(), checked.constant());
68//! ```
69
70use crate::scalar::*;
71use crate::vector::*;
72use num_traits::{One, Zero};
73
74/// A 2D dimension with width and height.
75#[repr(C)]
76#[derive(Debug, Clone, Copy, Default)]
77pub struct Dimension<T: Scalar> {
78    /// Width component.
79    pub width: T,
80    /// Height component.
81    pub height: T,
82}
83
84impl<T: Scalar> Dimension<T> {
85    /// Creates a new dimension with the specified width and height.
86    pub fn new(width: T, height: T) -> Self {
87        Self { width, height }
88    }
89}
90
91/// A 2D axis-aligned rectangle.
92///
93/// Defined by its top-left corner position (x, y) and dimensions (width, height).
94#[repr(C)]
95#[derive(Debug, Clone, Copy, Default)]
96pub struct Rect<T: Scalar> {
97    /// X coordinate of the top-left corner.
98    pub x: T,
99    /// Y coordinate of the top-left corner.
100    pub y: T,
101    /// Rectangle width.
102    pub width: T,
103    /// Rectangle height.
104    pub height: T,
105}
106
107impl<T: Scalar> Rect<T> {
108    /// Creates a new rectangle from position and dimensions.
109    pub fn new(x: T, y: T, width: T, height: T) -> Self {
110        Self {
111            x,
112            y,
113            width,
114            height,
115        }
116    }
117    /// Creates a rectangle from two corner points.
118    ///
119    /// The points don't need to be min/max - they will be sorted.
120    pub fn from(min_vec: &Vector2<T>, max_vec: &Vector2<T>) -> Self {
121        let min_x = T::min(min_vec.x, max_vec.x);
122        let min_y = T::min(min_vec.y, max_vec.y);
123        let max_x = T::max(min_vec.x, max_vec.x);
124        let max_y = T::max(min_vec.y, max_vec.y);
125        Self {
126            x: min_x,
127            y: min_y,
128            width: max_x - min_x,
129            height: max_y - min_y,
130        }
131    }
132
133    /// Returns the minimum corner (top-left) of the rectangle.
134    pub fn min(&self) -> Vector2<T> {
135        Vector2::new(self.x, self.y)
136    }
137    /// Returns the maximum corner (bottom-right) of the rectangle.
138    pub fn max(&self) -> Vector2<T> {
139        Vector2::new(self.x + self.width, self.y + self.height)
140    }
141    /// Computes the intersection of two rectangles.
142    ///
143    /// Returns `None` if the rectangles don't overlap.
144    pub fn intersect(&self, other: &Self) -> Option<Self> {
145        let smx = self.max();
146        let smn = self.min();
147        let omx = other.max();
148        let omn = other.min();
149
150        if smx.x < omn.x || smx.y < omn.y || smn.x > omx.x || smn.y > omx.y {
151            return None;
152        }
153
154        let min_vec = Vector2::max(&self.min(), &other.min());
155        let max_vec = Vector2::min(&self.max(), &other.max());
156        Some(Self::from(&min_vec, &max_vec))
157    }
158
159    /// Checks if a point is inside the rectangle.
160    pub fn contains(&self, p: &Vector2<T>) -> bool {
161        p.x >= self.x && p.y >= self.y && p.x <= self.x + self.width && p.y <= self.y + self.height
162    }
163}
164
165/// A 3D axis-aligned bounding box (AABB).
166///
167/// Defined by its minimum and maximum corners.
168#[repr(C)]
169#[derive(Debug, Clone, Copy, Default)]
170pub struct Box3<T: Scalar> {
171    /// Minimum corner.
172    pub min: Vector3<T>,
173    /// Maximum corner.
174    pub max: Vector3<T>,
175}
176
177impl<T: Scalar> Box3<T> {
178    /// Creates a new box from two corner points.
179    ///
180    /// The points are automatically sorted to find min/max.
181    pub fn new(v0: &Vector3<T>, v1: &Vector3<T>) -> Self {
182        Self {
183            min: Vector3::min(v0, v1),
184            max: Vector3::max(v0, v1),
185        }
186    }
187    /// Checks if two boxes overlap.
188    pub fn overlap(&self, other: &Self) -> bool {
189        if self.max.x < other.min.x {
190            return false;
191        }
192        if self.max.y < other.min.y {
193            return false;
194        }
195        if self.max.z < other.min.z {
196            return false;
197        }
198
199        if self.min.x > other.max.x {
200            return false;
201        }
202        if self.min.y > other.max.y {
203            return false;
204        }
205        if self.min.z > other.max.z {
206            return false;
207        }
208        true
209    }
210
211    /// Expands the box to include a point.
212    pub fn add(&self, p: &Vector3<T>) -> Self {
213        Self {
214            min: Vector3::min(p, &self.min),
215            max: Vector3::max(p, &self.max),
216        }
217    }
218}
219
220impl<T: FloatScalar> Box3<T> {
221    /// Returns the center point of the box.
222    pub fn center(&self) -> Vector3<T> {
223        (self.max + self.min) * T::half()
224    }
225    /// Returns the half-extents of the box from center to max.
226    pub fn extent(&self) -> Vector3<T> {
227        self.max - self.center()
228    }
229
230    /// Subdivides the box into 8 octants.
231    ///
232    /// Returns an array of 8 boxes representing the octree subdivision.
233    pub fn subdivide(&self) -> [Self; 8] {
234        let cube_table: [Vector3<i32>; 8] = [
235            Vector3::new(0, 1, 0),
236            Vector3::new(1, 1, 0),
237            Vector3::new(1, 1, 1),
238            Vector3::new(0, 1, 1),
239            Vector3::new(0, 0, 0),
240            Vector3::new(1, 0, 0),
241            Vector3::new(1, 0, 1),
242            Vector3::new(0, 0, 1),
243        ];
244
245        let ps: [Vector3<T>; 2] = [self.min, self.max];
246        let mut vs = [Vector3::zero(); 8];
247        for i in 0..8 {
248            vs[i] = Vector3::new(
249                ps[cube_table[i].x as usize].x,
250                ps[cube_table[i].y as usize].y,
251                ps[cube_table[i].z as usize].z,
252            );
253        }
254
255        let c = self.center();
256        let mut out = [Box3 {
257            min: Vector3::zero(),
258            max: Vector3::zero(),
259        }; 8];
260        for i in 0..8 {
261            out[i] = Self::new(&Vector3::min(&c, &vs[i]), &Vector3::max(&c, &vs[i]));
262        }
263        out
264    }
265}
266
267/// An infinite line in 2D or 3D space.
268///
269/// Defined by a point on the line and a direction vector.
270#[repr(C)]
271#[derive(Debug, Clone, Copy, Default)]
272pub struct Line<T: Scalar, V: Vector<T>> {
273    /// Point on the line.
274    pub p: V,
275    /// Direction of the line.
276    pub d: V,
277    t: core::marker::PhantomData<T>,
278}
279
280impl<T: FloatScalar, V: FloatVector<T>> Line<T, V> {
281    /// Creates a new line from a point and direction.
282    ///
283    /// Returns `None` if the direction is too small.
284    pub fn new(p: &V, d: &V, epsilon: T) -> Option<Self> {
285        let d_ss = V::dot(d, d);
286        if d_ss <= epsilon * epsilon {
287            return None;
288        }
289        Some(Self {
290            p: *p,
291            d: *d,
292            t: core::marker::PhantomData,
293        })
294    }
295    /// Creates a line from two points.
296    ///
297    /// The line passes through both points, with direction from s to e.
298    ///
299    /// Returns `None` if the points are too close.
300    pub fn from_start_end(s: &V, e: &V, epsilon: T) -> Option<Self> {
301        let dir = *e - *s;
302        Self::new(s, &dir, epsilon)
303    }
304    /// Finds the closest point on the line to a given point.
305    ///
306    /// Returns:
307    /// - `t`: Parameter value where the closest point occurs
308    /// - Point: The closest point on the line
309    ///
310    /// Returns `None` if the line direction is too small.
311    pub fn closest_point_on_line(&self, p: &V, epsilon: T) -> Option<(T, V)> {
312        let p_dir = *p - self.p;
313
314        let d_sp = V::dot(&self.d, &p_dir);
315        let d_ss = V::dot(&self.d, &self.d);
316
317        if d_ss <= epsilon * epsilon {
318            return None;
319        }
320
321        let t = d_sp / d_ss;
322
323        Some((t, self.p + self.d * t))
324    }
325
326    /// Returns a line with normalized direction vector.
327    ///
328    /// Returns `None` if the direction is too small.
329    pub fn normalize(&self, epsilon: T) -> Option<Self> {
330        let d_ss = V::dot(&self.d, &self.d);
331        if d_ss <= epsilon * epsilon {
332            return None;
333        }
334        let inv_len = <T as One>::one() / d_ss.tsqrt();
335        Some(Self {
336            p: self.p,
337            d: self.d * inv_len,
338            t: core::marker::PhantomData,
339        })
340    }
341}
342
343/// Finds the shortest segment connecting two 3D lines.
344///
345/// Returns `None` if the lines are parallel (within epsilon tolerance).
346pub fn shortest_segment3d_between_lines3d<T: FloatScalar>(
347    line0: &Line<T, Vector3<T>>,
348    line1: &Line<T, Vector3<T>>,
349    epsilon: T,
350) -> Option<Segment<T, Vector3<T>>> {
351    let s0 = line0.p;
352    let s1 = line1.p;
353
354    let d1 = line1.d;
355    let d0 = line0.d;
356
357    let eps_sq = epsilon * epsilon;
358    let d0_len_sq = Vector3::dot(&d0, &d0);
359    let d1_len_sq = Vector3::dot(&d1, &d1);
360    if d0_len_sq <= eps_sq || d1_len_sq <= eps_sq {
361        return None;
362    }
363
364    let cross = Vector3::cross(&d1, &d0);
365    let cross_len_sq = Vector3::dot(&cross, &cross);
366    if cross_len_sq <= eps_sq {
367        return None;
368    }
369
370    let normal = Vector3::normalize(&cross);
371    let n0 = Vector3::normalize(&Vector3::cross(&normal, &d0));
372    let n1 = Vector3::normalize(&Vector3::cross(&normal, &d1));
373
374    let plane0 = Plane::try_new(&n0, &s0, epsilon)?;
375    let plane1 = Plane::try_new(&n1, &s1, epsilon)?;
376
377    let p1 = plane0.intersect_line(line1, epsilon);
378    let p0 = plane1.intersect_line(line0, epsilon);
379
380    match (p0, p1) {
381        (Some((_, s)), Some((_, e))) => Some(Segment::new(&s, &e)),
382        _ => None,
383    }
384}
385
386/// A line segment with defined start and end points.
387#[repr(C)]
388#[derive(Clone, Copy, Default)]
389pub struct Segment<T: Scalar, V: Vector<T>> {
390    /// Start point.
391    pub s: V,
392    /// End point.
393    pub e: V,
394    t: core::marker::PhantomData<T>,
395}
396
397impl<T: Scalar, V: Vector<T>> Segment<T, V> {
398    /// Creates a new segment from start to end point.
399    pub fn new(s: &V, e: &V) -> Self {
400        Self {
401            s: *s,
402            e: *e,
403            t: core::marker::PhantomData,
404        }
405    }
406}
407
408impl<T: FloatScalar, V: FloatVector<T>> Segment<T, V> {
409    /// Finds the closest point on the segment to a given point.
410    ///
411    /// Returns:
412    /// - `t`: Parameter value \[0,1\] where 0=start, 1=end
413    /// - Point: The closest point on the segment
414    ///
415    /// Returns `None` if the segment is too small.
416    pub fn closest_point_on_segment(&self, p: &V, epsilon: T) -> Option<(T, V)> {
417        let dir = self.e - self.s;
418        let p_dir = *p - self.s;
419
420        let d_sp = V::dot(&dir, &p_dir);
421        let d_ss = V::dot(&dir, &dir);
422
423        if d_ss <= epsilon * epsilon {
424            return None;
425        }
426
427        if d_sp < <T as Zero>::zero() {
428            return Some((<T as Zero>::zero(), self.s));
429        } else if d_sp > d_ss {
430            return Some((<T as One>::one(), self.e));
431        }
432
433        let t = d_sp / d_ss;
434
435        Some((t, self.s + dir * t))
436    }
437    /// Computes the distance from a point to the segment.
438    ///
439    /// Returns `None` if the segment is too small.
440    pub fn distance(&self, p: &V, epsilon: T) -> Option<T> {
441        self.closest_point_on_segment(p, epsilon)
442            .map(|(_, p_on_seg)| V::length(&(p_on_seg - *p)))
443    }
444}
445
446/// A ray with an origin and direction.
447///
448/// Commonly used for ray casting and intersection tests.
449#[repr(C)]
450#[derive(Clone, Copy, Default)]
451pub struct Ray<T: Scalar, V: Vector<T>> {
452    /// Ray origin.
453    pub start: V,
454    /// Ray direction (typically normalized).
455    pub direction: V,
456    t: core::marker::PhantomData<T>,
457}
458
459impl<T: FloatScalar, V: FloatVector<T>> Ray<T, V> {
460    /// Creates a new ray with normalized direction.
461    ///
462    /// Returns `None` if the direction is too small.
463    pub fn new(start: &V, direction: &V, epsilon: T) -> Option<Self> {
464        let d_ss = V::dot(direction, direction);
465        if d_ss <= epsilon * epsilon {
466            return None;
467        }
468        let inv_len = <T as One>::one() / d_ss.tsqrt();
469        Some(Self {
470            start: *start,
471            direction: *direction * inv_len,
472            t: core::marker::PhantomData,
473        })
474    }
475}
476
477impl<T: FloatScalar> Ray<T, Vector3<T>> {
478    /// Computes ray-plane intersection.
479    ///
480    /// Returns the intersection point, or `None` if the ray doesn't hit the plane
481    /// (parallel or pointing away).
482    ///
483    /// Returns `None` if the ray is parallel to the plane within `epsilon`.
484    pub fn intersect_plane(&self, p: &Plane<T>, epsilon: T) -> Option<Vector3<T>> {
485        let n = p.normal();
486        let denom = Vector3::dot(&n, &self.direction);
487        if denom.tabs() <= epsilon {
488            return None;
489        }
490        let t: T = -(p.d + Vector3::dot(&n, &self.start)) / denom;
491        if t < <T as Zero>::zero() {
492            None
493        } else {
494            Some(self.direction * t + self.start)
495        }
496    }
497}
498
499////////////////////////////////////////////////////////////////////////////////
500/// A sphere defined by center and radius.
501////////////////////////////////////////////////////////////////////////////////
502#[repr(C)]
503#[derive(Clone, Copy, Default)]
504pub struct Sphere3<T: FloatScalar> {
505    /// Sphere center.
506    pub center: Vector3<T>,
507    /// Sphere radius.
508    pub radius: T,
509}
510
511impl<T: FloatScalar> Sphere3<T> {
512    /// Creates a sphere from center and radius.
513    ///
514    /// The radius is canonicalized with `abs(radius)`, so negative inputs are
515    /// treated the same as their positive magnitude.
516    pub fn new(center: Vector3<T>, radius: T) -> Self {
517        Self {
518            center,
519            radius: radius.tabs(),
520        }
521    }
522}
523
524////////////////////////////////////////////////////////////////////////////////
525/// A triangle defined by three vertices.
526////////////////////////////////////////////////////////////////////////////////
527#[repr(C)]
528#[derive(Clone, Copy, Default)]
529pub struct Tri3<T: FloatScalar> {
530    vertices: [Vector3<T>; 3],
531}
532
533impl<T: FloatScalar> Tri3<T> {
534    /// Creates a triangle from three vertices.
535    pub fn new(vertices: [Vector3<T>; 3]) -> Self {
536        Self { vertices }
537    }
538    /// Returns the triangle vertices.
539    pub fn vertices(&self) -> &[Vector3<T>; 3] {
540        &self.vertices
541    }
542
543    /// Computes barycentric coordinates of a point in the triangle plane.
544    ///
545    /// This method assumes the triangle is non-degenerate. When the triangle
546    /// area is zero, the denominator vanishes and the returned coordinates are
547    /// non-finite.
548    pub fn barycentric_coordinates(&self, pt: &Vector3<T>) -> Vector3<T> {
549        let v0 = self.vertices[0];
550        let v1 = self.vertices[1];
551        let v2 = self.vertices[2];
552        let e0 = v1 - v0;
553        let e1 = v2 - v0;
554        let vp = *pt - v0;
555
556        let d00 = Vector3::dot(&e0, &e0);
557        let d01 = Vector3::dot(&e0, &e1);
558        let d11 = Vector3::dot(&e1, &e1);
559        let d20 = Vector3::dot(&vp, &e0);
560        let d21 = Vector3::dot(&vp, &e1);
561        let denom = d00 * d11 - d01 * d01;
562        let v = (d11 * d20 - d01 * d21) / denom;
563        let w = (d00 * d21 - d01 * d20) / denom;
564        let u = <T as One>::one() - v - w;
565        Vector3::new(u, v, w)
566    }
567}
568
569////////////////////////////////////////////////////////////////////////////////
570/// A plane defined by ax + by + cz + d = 0.
571////////////////////////////////////////////////////////////////////////////////
572#[repr(C)]
573#[derive(Clone, Copy, Default)]
574pub struct Plane<T: Scalar> {
575    a: T,
576    b: T,
577    c: T,
578    d: T,
579}
580
581impl<T: Scalar> Plane<T> {
582    /// Returns the plane normal vector.
583    pub fn normal(&self) -> Vector3<T> {
584        Vector3::new(self.a, self.b, self.c)
585    }
586    /// Returns the plane constant term (d).
587    pub fn constant(&self) -> T {
588        self.d
589    }
590}
591
592impl<T: FloatScalar> Plane<T> {
593    /// Creates a plane from a normal and a point on the plane.
594    ///
595    /// # Panics
596    /// Panics if `n` is too small to normalize. Use [`Plane::try_new`] when the
597    /// input may be degenerate.
598    pub fn new(n: &Vector3<T>, p: &Vector3<T>) -> Self {
599        Self::try_new(n, p, T::epsilon()).expect("plane normal must be non-zero")
600    }
601
602    /// Creates a plane from a normal and a point on the plane.
603    ///
604    /// Returns `None` if `n` is too small to normalize.
605    pub fn try_new(n: &Vector3<T>, p: &Vector3<T>, epsilon: T) -> Option<Self> {
606        let norm = n.try_normalize(epsilon)?;
607        let d = Vector3::dot(&norm, p);
608        Some(Self {
609            a: norm.x,
610            b: norm.y,
611            c: norm.z,
612            d: -d,
613        })
614    }
615
616    /// Creates a plane from triangle vertices.
617    ///
618    /// # Panics
619    /// Panics if the triangle is degenerate. Use [`Plane::try_from_tri`] when
620    /// the input may be degenerate.
621    pub fn from_tri(v0: &Vector3<T>, v1: &Vector3<T>, v2: &Vector3<T>) -> Self {
622        Self::try_from_tri(v0, v1, v2, T::epsilon()).expect("triangle must define a plane")
623    }
624
625    /// Creates a plane from triangle vertices.
626    ///
627    /// Returns `None` if the triangle is degenerate.
628    pub fn try_from_tri(
629        v0: &Vector3<T>,
630        v1: &Vector3<T>,
631        v2: &Vector3<T>,
632        epsilon: T,
633    ) -> Option<Self> {
634        let n = try_tri_normal(v0, v1, v2, epsilon)?;
635        Self::try_new(&n, v0, epsilon)
636    }
637    /// Creates a plane from quad vertices.
638    ///
639    /// # Panics
640    /// Panics if the quad diagonals do not define a plane. Use
641    /// [`Plane::try_from_quad`] when the input may be degenerate.
642    ///
643    /// This routine does not validate that the four vertices are coplanar.
644    /// For non-coplanar quads it returns the diagonal-derived representative
645    /// plane described by [`Plane::try_from_quad`].
646    pub fn from_quad(v0: &Vector3<T>, v1: &Vector3<T>, v2: &Vector3<T>, v3: &Vector3<T>) -> Self {
647        Self::try_from_quad(v0, v1, v2, v3, T::epsilon()).expect("quad must define a plane")
648    }
649
650    /// Creates a plane from quad vertices.
651    ///
652    /// Returns `None` if the quad diagonals do not define a plane.
653    ///
654    /// The plane normal is computed from the quad diagonals and anchored at the
655    /// vertex centroid. This is a representative plane for the quad and does
656    /// not validate coplanarity. For non-coplanar input, the returned plane is
657    /// not guaranteed to pass through any of the vertices.
658    pub fn try_from_quad(
659        v0: &Vector3<T>,
660        v1: &Vector3<T>,
661        v2: &Vector3<T>,
662        v3: &Vector3<T>,
663        epsilon: T,
664    ) -> Option<Self> {
665        let n = try_quad_normal(v0, v1, v2, v3, epsilon)?;
666        let c = (*v0 + *v1 + *v2 + *v3) * T::quarter();
667        Self::try_new(&n, &c, epsilon)
668    }
669
670    /// Intersects the plane with a ray.
671    pub fn intersect_ray(&self, r: &Ray<T, Vector3<T>>, epsilon: T) -> Option<Vector3<T>> {
672        r.intersect_plane(self, epsilon)
673    }
674
675    /// Computes line-plane intersection.
676    ///
677    /// Returns the parameter t and intersection point, or `None` if parallel.
678    pub fn intersect_line(
679        &self,
680        line: &Line<T, Vector3<T>>,
681        epsilon: T,
682    ) -> Option<(T, Vector3<T>)> {
683        let s = line.p;
684        let dir = line.d;
685        let n = self.normal();
686
687        let denom = Vector3::dot(&n, &dir);
688        if denom.tabs() < epsilon {
689            None
690        } else {
691            let t = -(self.constant() + Vector3::dot(&n, &s)) / denom;
692            Some((t, dir * t + s))
693        }
694    }
695}
696
697////////////////////////////////////////////////////////////////////////////////
698/// A parametric plane defined by center and axis vectors.
699////////////////////////////////////////////////////////////////////////////////
700#[repr(C)]
701#[derive(Clone, Copy, Default)]
702pub struct ParametricPlane<T: Scalar> {
703    /// Plane center point.
704    pub center: Vector3<T>,
705    /// Plane X axis direction.
706    pub x_axis: Vector3<T>,
707    /// Plane Y axis direction.
708    pub y_axis: Vector3<T>,
709}
710
711impl<T: FloatScalar> ParametricPlane<T> {
712    /// Creates a parametric plane from center and axes.
713    pub fn new(center: &Vector3<T>, x_axis: &Vector3<T>, y_axis: &Vector3<T>) -> Self {
714        Self {
715            center: *center,
716            x_axis: *x_axis,
717            y_axis: *y_axis,
718        }
719    }
720
721    /// Converts the parametric plane to an infinite plane.
722    pub fn plane(&self) -> Plane<T> {
723        self.try_plane(T::epsilon())
724            .expect("parametric plane axes must span a plane")
725    }
726
727    /// Converts the parametric plane to an infinite plane.
728    ///
729    /// Returns `None` if the axes are too small or parallel.
730    pub fn try_plane(&self, epsilon: T) -> Option<Plane<T>> {
731        let normal = self.try_normal(epsilon)?;
732        Plane::try_new(&normal, &self.center, epsilon)
733    }
734
735    /// Computes the normal vector (cross product of axes).
736    pub fn normal(&self) -> Vector3<T> {
737        self.try_normal(T::epsilon())
738            .expect("parametric plane axes must span a plane")
739    }
740
741    /// Computes the normal vector (cross product of axes).
742    ///
743    /// Returns `None` if the axes are too small or parallel.
744    pub fn try_normal(&self, epsilon: T) -> Option<Vector3<T>> {
745        Vector3::cross(&self.x_axis, &self.y_axis).try_normalize(epsilon)
746    }
747
748    /// Intersects the plane with a ray.
749    pub fn intersect_ray(&self, r: &Ray<T, Vector3<T>>, epsilon: T) -> Option<Vector3<T>> {
750        let plane = self.try_plane(epsilon)?;
751        r.intersect_plane(&plane, epsilon)
752    }
753
754    /// Intersects the plane with a line.
755    pub fn intersect_line(
756        &self,
757        line: &Line<T, Vector3<T>>,
758        epsilon: T,
759    ) -> Option<(T, Vector3<T>)> {
760        let plane = self.try_plane(epsilon)?;
761        plane.intersect_line(line, epsilon)
762    }
763
764    /// Projects a 3D point onto the plane's 2D coordinates.
765    ///
766    /// The coordinates are solved against the 2x2 Gram matrix of the plane
767    /// axes, so non-orthogonal axes are handled correctly. The axes must still
768    /// be linearly independent; otherwise the returned coordinates are
769    /// non-finite.
770    pub fn project(&self, v: &Vector3<T>) -> Vector2<T> {
771        let p = *v - self.center;
772        let g00 = Vector3::dot(&self.x_axis, &self.x_axis);
773        let g01 = Vector3::dot(&self.x_axis, &self.y_axis);
774        let g11 = Vector3::dot(&self.y_axis, &self.y_axis);
775        let rhs0 = Vector3::dot(&p, &self.x_axis);
776        let rhs1 = Vector3::dot(&p, &self.y_axis);
777        let det = g00 * g11 - g01 * g01;
778
779        let x_coord = (rhs0 * g11 - rhs1 * g01) / det;
780        let y_coord = (rhs1 * g00 - rhs0 * g01) / det;
781        Vector2::new(x_coord, y_coord)
782    }
783}
784
785////////////////////////////////////////////////////////////////////////////////
786/// Computes the normal vector of a triangle.
787////////////////////////////////////////////////////////////////////////////////
788pub fn tri_normal<T: FloatScalar>(v0: &Vector3<T>, v1: &Vector3<T>, v2: &Vector3<T>) -> Vector3<T> {
789    try_tri_normal(v0, v1, v2, T::epsilon()).expect("triangle must be non-degenerate")
790}
791
792/// Computes the normal vector of a triangle.
793///
794/// Returns `None` if the triangle is degenerate.
795pub fn try_tri_normal<T: FloatScalar>(
796    v0: &Vector3<T>,
797    v1: &Vector3<T>,
798    v2: &Vector3<T>,
799    epsilon: T,
800) -> Option<Vector3<T>> {
801    let v10 = *v1 - *v0;
802    let v20 = *v2 - *v0;
803    Vector3::cross(&v10, &v20).try_normalize(epsilon)
804}
805
806/// Computes the normal vector of a quadrilateral.
807///
808/// Uses the cross product of the two diagonals for a more stable result.
809///
810/// For non-coplanar quads this is a representative diagonal-based normal, not a
811/// proof that the four vertices lie on a single plane.
812pub fn quad_normal<T: FloatScalar>(
813    v0: &Vector3<T>,
814    v1: &Vector3<T>,
815    v2: &Vector3<T>,
816    v3: &Vector3<T>,
817) -> Vector3<T> {
818    try_quad_normal(v0, v1, v2, v3, T::epsilon()).expect("quad must be non-degenerate")
819}
820
821/// Computes the normal vector of a quadrilateral.
822///
823/// Returns `None` if the quad diagonals are too small or parallel.
824pub fn try_quad_normal<T: FloatScalar>(
825    v0: &Vector3<T>,
826    v1: &Vector3<T>,
827    v2: &Vector3<T>,
828    v3: &Vector3<T>,
829    epsilon: T,
830) -> Option<Vector3<T>> {
831    let v20 = *v2 - *v0;
832    let v31 = *v3 - *v1;
833    Vector3::cross(&v20, &v31).try_normalize(epsilon)
834}
835
836#[cfg(test)]
837mod tests {
838    use super::*;
839    #[test]
840    pub fn test_barycentric() {
841        let v0 = Vector3::new(0.0, 0.0, 0.0);
842        let v1 = Vector3::new(0.0, 1.0, 0.0);
843        let v2 = Vector3::new(0.0, 0.0, 1.0);
844
845        let tri = Tri3::new([v0, v1, v2]);
846        let pp0 = tri.barycentric_coordinates(&v0);
847        assert!(f32::abs(pp0.x - 1.0) < 0.01);
848        assert!(f32::abs(pp0.y) < 0.01);
849        assert!(f32::abs(pp0.z) < 0.01);
850
851        let pp1 = tri.barycentric_coordinates(&v1);
852        assert!(f32::abs(pp1.x) < 0.01);
853        assert!(f32::abs(pp1.y - 1.0) < 0.01);
854        assert!(f32::abs(pp1.z) < 0.01);
855
856        let pp2 = tri.barycentric_coordinates(&v2);
857        assert!(f32::abs(pp2.x) < 0.01);
858        assert!(f32::abs(pp2.y) < 0.01);
859        assert!(f32::abs(pp2.z - 1.0) < 0.01);
860    }
861
862    #[test]
863    pub fn test_barycentric_translated() {
864        // Test with a triangle not at the origin
865        let v0 = Vector3::new(1.0, 2.0, 3.0);
866        let v1 = Vector3::new(4.0, 2.0, 3.0);
867        let v2 = Vector3::new(1.0, 5.0, 3.0);
868
869        let tri = Tri3::new([v0, v1, v2]);
870
871        // Test vertices
872        let pp0 = tri.barycentric_coordinates(&v0);
873        assert!(f32::abs(pp0.x - 1.0) < 0.001);
874        assert!(f32::abs(pp0.y) < 0.001);
875        assert!(f32::abs(pp0.z) < 0.001);
876
877        let pp1 = tri.barycentric_coordinates(&v1);
878        assert!(f32::abs(pp1.x) < 0.001);
879        assert!(f32::abs(pp1.y - 1.0) < 0.001);
880        assert!(f32::abs(pp1.z) < 0.001);
881
882        let pp2 = tri.barycentric_coordinates(&v2);
883        assert!(f32::abs(pp2.x) < 0.001);
884        assert!(f32::abs(pp2.y) < 0.001);
885        assert!(f32::abs(pp2.z - 1.0) < 0.001);
886
887        // Test center point
888        let center = (v0 + v1 + v2) / 3.0;
889        let pp_center = tri.barycentric_coordinates(&center);
890        assert!(f32::abs(pp_center.x - 1.0 / 3.0) < 0.001);
891        assert!(f32::abs(pp_center.y - 1.0 / 3.0) < 0.001);
892        assert!(f32::abs(pp_center.z - 1.0 / 3.0) < 0.001);
893
894        // Verify barycentric coordinates sum to 1
895        assert!(f32::abs((pp_center.x + pp_center.y + pp_center.z) - 1.0) < 0.001);
896    }
897
898    #[test]
899    pub fn test_barycentric_edge_midpoints() {
900        let v0 = Vector3::new(0.0, 0.0, 0.0);
901        let v1 = Vector3::new(2.0, 0.0, 0.0);
902        let v2 = Vector3::new(0.0, 2.0, 0.0);
903
904        let tri = Tri3::new([v0, v1, v2]);
905
906        // Midpoint of edge v0-v1
907        let mid01 = (v0 + v1) / 2.0;
908        let pp_mid01 = tri.barycentric_coordinates(&mid01);
909        assert!(f32::abs(pp_mid01.x - 0.5) < 0.001);
910        assert!(f32::abs(pp_mid01.y - 0.5) < 0.001);
911        assert!(f32::abs(pp_mid01.z) < 0.001);
912
913        // Midpoint of edge v0-v2
914        let mid02 = (v0 + v2) / 2.0;
915        let pp_mid02 = tri.barycentric_coordinates(&mid02);
916        assert!(f32::abs(pp_mid02.x - 0.5) < 0.001);
917        assert!(f32::abs(pp_mid02.y) < 0.001);
918        assert!(f32::abs(pp_mid02.z - 0.5) < 0.001);
919
920        // Midpoint of edge v1-v2
921        let mid12 = (v1 + v2) / 2.0;
922        let pp_mid12 = tri.barycentric_coordinates(&mid12);
923        assert!(f32::abs(pp_mid12.x) < 0.001);
924        assert!(f32::abs(pp_mid12.y - 0.5) < 0.001);
925        assert!(f32::abs(pp_mid12.z - 0.5) < 0.001);
926    }
927
928    #[test]
929    pub fn test_barycentric_degenerate_triangle() {
930        let v0 = Vector3::new(0.0f32, 0.0, 0.0);
931        let v1 = Vector3::new(1.0f32, 0.0, 0.0);
932        let v2 = Vector3::new(2.0f32, 0.0, 0.0);
933
934        let tri = Tri3::new([v0, v1, v2]);
935        let test_point = Vector3::new(0.5f32, 0.0, 0.0);
936        let bary = tri.barycentric_coordinates(&test_point);
937        assert!(!bary.x.is_finite());
938        assert!(!bary.y.is_finite());
939        assert!(!bary.z.is_finite());
940    }
941
942    #[test]
943    pub fn test_barycentric_interpolation() {
944        // Test that we can reconstruct points using barycentric coordinates
945        let v0 = Vector3::new(1.0, 0.0, 0.0);
946        let v1 = Vector3::new(0.0, 1.0, 0.0);
947        let v2 = Vector3::new(0.0, 0.0, 1.0);
948
949        let tri = Tri3::new([v0, v1, v2]);
950
951        // Test arbitrary point inside triangle
952        let test_point = Vector3::new(0.2, 0.3, 0.5);
953        let bary = tri.barycentric_coordinates(&test_point);
954
955        // Reconstruct the point using barycentric coordinates
956        let reconstructed = v0 * bary.x + v1 * bary.y + v2 * bary.z;
957
958        assert!(f32::abs(reconstructed.x - test_point.x) < 0.001);
959        assert!(f32::abs(reconstructed.y - test_point.y) < 0.001);
960        assert!(f32::abs(reconstructed.z - test_point.z) < 0.001);
961    }
962
963    #[test]
964    pub fn test_box3_overlap() {
965        let a = Box3::new(&Vector3::new(0.0, 0.0, 0.0), &Vector3::new(1.0, 1.0, 1.0));
966        let b = Box3::new(&Vector3::new(0.5, 0.5, 0.5), &Vector3::new(1.5, 1.5, 1.5));
967        assert!(a.overlap(&b));
968
969        let c = Box3::new(&Vector3::new(2.0, 0.0, 0.0), &Vector3::new(3.0, 1.0, 1.0));
970        assert!(!a.overlap(&c));
971
972        let d = Box3::new(&Vector3::new(0.0, 2.0, 0.0), &Vector3::new(1.0, 3.0, 1.0));
973        assert!(!a.overlap(&d));
974
975        let e = Box3::new(&Vector3::new(0.0, 0.0, 2.0), &Vector3::new(1.0, 1.0, 3.0));
976        assert!(!a.overlap(&e));
977
978        let f = Box3::new(&Vector3::new(1.0, 0.0, 0.0), &Vector3::new(2.0, 1.0, 1.0));
979        assert!(a.overlap(&f));
980    }
981
982    #[test]
983    fn test_line_new_zero_direction() {
984        let p = Vector3::new(0.0f32, 0.0, 0.0);
985        let d = Vector3::new(0.0f32, 0.0, 0.0);
986        assert!(Line::new(&p, &d, EPS_F32).is_none());
987    }
988
989    #[test]
990    fn test_line_from_start_end_zero_direction() {
991        let p = Vector3::new(1.0f32, 2.0, 3.0);
992        assert!(Line::from_start_end(&p, &p, EPS_F32).is_none());
993    }
994
995    #[test]
996    fn test_line_closest_point_valid() {
997        let p = Vector3::new(0.0f32, 0.0, 0.0);
998        let d = Vector3::new(1.0f32, 0.0, 0.0);
999        let line = Line::new(&p, &d, EPS_F32).expect("line should be valid");
1000        let target = Vector3::new(2.0f32, 1.0, 0.0);
1001        let (t, closest) = line
1002            .closest_point_on_line(&target, EPS_F32)
1003            .expect("closest point should exist");
1004        assert!((t - 2.0).abs() < 0.001);
1005        assert!((closest.x - 2.0).abs() < 0.001);
1006        assert!(closest.y.abs() < 0.001);
1007        assert!(closest.z.abs() < 0.001);
1008    }
1009
1010    #[test]
1011    fn test_line_normalize_zero_direction() {
1012        let line = Line {
1013            p: Vector3::new(0.0f32, 0.0, 0.0),
1014            d: Vector3::new(0.0f32, 0.0, 0.0),
1015            t: core::marker::PhantomData,
1016        };
1017        assert!(line.normalize(EPS_F32).is_none());
1018    }
1019
1020    #[test]
1021    fn test_line_normalize_valid_direction() {
1022        let p = Vector3::new(0.0f32, 0.0, 0.0);
1023        let d = Vector3::new(2.0f32, 0.0, 0.0);
1024        let line = Line::new(&p, &d, EPS_F32).expect("line should be valid");
1025        let norm = line.normalize(EPS_F32).expect("normalize should succeed");
1026        assert!((norm.d.length() - 1.0).abs() < 0.001);
1027    }
1028
1029    #[test]
1030    fn test_segment_distance_zero_length() {
1031        let p = Vector3::new(0.0f32, 0.0, 0.0);
1032        let seg = Segment::new(&p, &p);
1033        let target = Vector3::new(1.0f32, 0.0, 0.0);
1034        assert!(seg.distance(&target, EPS_F32).is_none());
1035        assert!(seg.closest_point_on_segment(&target, EPS_F32).is_none());
1036    }
1037
1038    #[test]
1039    fn test_ray_new_zero_direction() {
1040        let p = Vector3::new(0.0f32, 0.0, 0.0);
1041        let d = Vector3::new(0.0f32, 0.0, 0.0);
1042        assert!(Ray::new(&p, &d, EPS_F32).is_none());
1043    }
1044
1045    #[test]
1046    fn test_sphere_new_abs_radius() {
1047        let sphere = Sphere3::new(Vector3::new(0.0f32, 0.0, 0.0), -2.5);
1048        assert!((sphere.radius - 2.5).abs() < 0.001);
1049    }
1050
1051    #[test]
1052    fn test_shortest_segment_parallel_lines() {
1053        let p0 = Vector3::new(0.0f32, 0.0, 0.0);
1054        let p1 = Vector3::new(0.0f32, 1.0, 0.0);
1055        let d = Vector3::new(1.0f32, 0.0, 0.0);
1056        let l0 = Line::new(&p0, &d, EPS_F32).expect("line should be valid");
1057        let l1 = Line::new(&p1, &d, EPS_F32).expect("line should be valid");
1058        assert!(shortest_segment3d_between_lines3d(&l0, &l1, EPS_F32).is_none());
1059    }
1060
1061    #[test]
1062    fn test_ray_intersect_plane_parallel() {
1063        let ray = Ray::new(
1064            &Vector3::new(0.0f32, 0.0, 0.0),
1065            &Vector3::new(1.0, 0.0, 0.0),
1066            EPS_F32,
1067        )
1068        .expect("ray should be valid");
1069        let plane = Plane::new(
1070            &Vector3::new(0.0f32, 1.0, 0.0),
1071            &Vector3::new(0.0, 0.0, 0.0),
1072        );
1073        assert!(ray.intersect_plane(&plane, EPS_F32).is_none());
1074        assert!(plane.intersect_ray(&ray, EPS_F32).is_none());
1075    }
1076
1077    #[test]
1078    fn test_ray_intersect_plane_hit() {
1079        let ray = Ray::new(
1080            &Vector3::new(0.0f32, -1.0, 0.0),
1081            &Vector3::new(0.0, 1.0, 0.0),
1082            EPS_F32,
1083        )
1084        .expect("ray should be valid");
1085        let plane = Plane::new(
1086            &Vector3::new(0.0f32, 1.0, 0.0),
1087            &Vector3::new(0.0, 0.0, 0.0),
1088        );
1089        let hit = ray.intersect_plane(&plane, EPS_F32).expect("should hit");
1090        assert!(hit.y.abs() < 0.001);
1091    }
1092
1093    #[test]
1094    fn test_plane_try_new_zero_normal_none() {
1095        let plane = Plane::try_new(
1096            &Vector3::new(0.0f32, 0.0, 0.0),
1097            &Vector3::new(0.0, 0.0, 0.0),
1098            EPS_F32,
1099        );
1100        assert!(plane.is_none());
1101    }
1102
1103    #[test]
1104    fn test_box3_center_extent_subdivide() {
1105        let b = Box3::new(
1106            &Vector3::new(0.0f32, 0.0, 0.0),
1107            &Vector3::new(2.0, 2.0, 2.0),
1108        );
1109        let center = b.center();
1110        let extent = b.extent();
1111        assert!((center.x - 1.0).abs() < 0.001);
1112        assert!((center.y - 1.0).abs() < 0.001);
1113        assert!((center.z - 1.0).abs() < 0.001);
1114        assert!((extent.x - 1.0).abs() < 0.001);
1115        assert!((extent.y - 1.0).abs() < 0.001);
1116        assert!((extent.z - 1.0).abs() < 0.001);
1117
1118        let subs = b.subdivide();
1119        assert_eq!(subs.len(), 8);
1120        for sub in subs.iter() {
1121            assert!(sub.min.x >= 0.0 && sub.min.y >= 0.0 && sub.min.z >= 0.0);
1122            assert!(sub.max.x <= 2.0 && sub.max.y <= 2.0 && sub.max.z <= 2.0);
1123        }
1124    }
1125
1126    #[test]
1127    fn test_parametric_plane_project() {
1128        let plane = ParametricPlane::new(
1129            &Vector3::new(1.0f32, 2.0, 3.0),
1130            &Vector3::new(1.0, 0.0, 0.0),
1131            &Vector3::new(0.0, 1.0, 0.0),
1132        );
1133        let point = Vector3::new(3.0f32, 6.0, 3.0);
1134        let uv = plane.project(&point);
1135        assert!((uv.x - 2.0).abs() < 0.001);
1136        assert!((uv.y - 4.0).abs() < 0.001);
1137    }
1138
1139    #[test]
1140    fn test_parametric_plane_project_non_orthogonal_axes() {
1141        let plane = ParametricPlane::new(
1142            &Vector3::new(0.0f32, 0.0, 0.0),
1143            &Vector3::new(1.0, 0.0, 0.0),
1144            &Vector3::new(1.0, 1.0, 0.0),
1145        );
1146        let point = Vector3::new(2.0f32, 1.0, 0.0);
1147        let uv = plane.project(&point);
1148        assert!((uv.x - 1.0).abs() < 0.001);
1149        assert!((uv.y - 1.0).abs() < 0.001);
1150    }
1151
1152    #[test]
1153    fn test_parametric_plane_try_normal_parallel_axes_none() {
1154        let plane = ParametricPlane::new(
1155            &Vector3::new(0.0f32, 0.0, 0.0),
1156            &Vector3::new(1.0, 0.0, 0.0),
1157            &Vector3::new(2.0, 0.0, 0.0),
1158        );
1159        assert!(plane.try_normal(EPS_F32).is_none());
1160        assert!(plane.try_plane(EPS_F32).is_none());
1161    }
1162}