phys_geom/
shape.rs

1// Copyright (C) 2020-2025 phys-geom authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Geometric shape implementations for 3D physics simulations.
16//!
17//! This module provides a collection of primitive geometric shapes commonly used in physics
18//! engines, along with their basic properties and operations. All shapes are designed to be
19//! FFI-safe through the `#[repr(C)]` attribute and support optional serialization via the `serde`
20//! feature.
21//!
22//! ## Available Shapes
23//!
24//! - [`Sphere`] - A perfect sphere defined by its radius
25//! - [`Capsule`] - A capsule with cylindrical body and hemispherical caps
26//! - [`Cuboid`] - A rectangular box (also known as a box or rectangular prism)
27//! - [`Cylinder`] - A cylinder with circular cross-section
28//! - [`InfinitePlane`] - An infinite plane (primarily for geometric computations)
29//! - [`Tetrahedron`] - A tetrahedron defined by four vertices
30//!
31//! ## Common Properties
32//!
33//! All shapes share these characteristics:
34//! - **FFI-safe**: All shapes use `#[repr(C)]` for C compatibility
35//! - **Serializable**: Support `serde` serialization when the feature is enabled
36//! - **Validated**: Construction validates parameters (e.g., positive dimensions)
37//! - **Copy-friendly**: All shapes implement `Copy` and `Clone`
38//!
39//! ## Usage Examples
40//!
41//! ```rust
42//! use phys_geom::shape::{Sphere, Capsule, Cuboid, Cylinder};
43//!
44//! // Create a sphere
45//! let sphere = Sphere::new(1.0);
46//! assert_eq!(sphere.radius(), 1.0);
47//!
48//! // Create a capsule with cylindrical half-height 2.0 and radius 0.5
49//! let capsule = Capsule::new(2.0, 0.5);
50//! assert_eq!(capsule.height(), 4.0); // cylinder height (excluding caps)
51//!
52//! // Create a cuboid (box) with dimensions 2x3x1
53//! let cuboid = Cuboid::new([2.0, 3.0, 1.0]);
54//! assert_eq!(cuboid.length(), [2.0, 3.0, 1.0]);
55//!
56//! // Create a cylinder
57//! let cylinder = Cylinder::new(1.5, 0.8); // half-height=1.5, radius=0.8
58//! assert_eq!(cylinder.height(), 3.0);
59//! ```
60
61use crate::math::*;
62
63/// A sphere shape centered at the origin.
64///
65/// The sphere is defined by its radius and is commonly used for simple collision detection
66/// and physics calculations. The sphere is centered at the origin (0, 0, 0).
67///
68/// # Examples
69///
70/// ```rust
71/// use phys_geom::shape::Sphere;
72///
73/// // Create a sphere with radius 1.0
74/// let sphere = Sphere::new(1.0);
75/// assert_eq!(sphere.radius(), 1.0);
76///
77/// // Use the predefined unit sphere
78/// let unit_sphere = Sphere::UNIT;
79/// assert_eq!(unit_sphere.radius(), 0.5);
80/// ```
81///
82/// # Panics
83///
84/// - [`Sphere::new()`] panics if the radius is not positive
85/// - [`Sphere::set_radius()`] panics if the new radius is not positive
86#[repr(C)]
87#[derive(Debug, Copy, Clone, Default, PartialEq)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct Sphere {
90    radius: Real,
91}
92
93impl Sphere {
94    /// A unit sphere with radius 0.5.
95    ///
96    /// This is useful as a reference size for normalized shapes.
97    pub const UNIT: Sphere = Sphere { radius: 0.5 };
98
99    /// Creates a new sphere with the given radius.
100    ///
101    /// # Arguments
102    ///
103    /// * `radius` - The radius of the sphere (must be positive)
104    ///
105    /// # Returns
106    ///
107    /// A new `Sphere` instance with the specified radius.
108    ///
109    /// # Panics
110    ///
111    /// Panics if `radius` is not positive.
112    ///
113    /// # Examples
114    ///
115    /// ```rust
116    /// use phys_geom::shape::Sphere;
117    ///
118    /// let sphere = Sphere::new(2.5);
119    /// assert_eq!(sphere.radius(), 2.5);
120    /// ```
121    #[inline]
122    #[must_use]
123    pub fn new(radius: Real) -> Sphere {
124        assert!(radius > 0.0);
125        Sphere { radius }
126    }
127
128    /// Returns the radius of the sphere.
129    ///
130    /// # Returns
131    ///
132    /// The radius as a `Real` (f32 or f64 depending on features).
133    ///
134    /// # Examples
135    ///
136    /// ```rust
137    /// use phys_geom::shape::Sphere;
138    ///
139    /// let sphere = Sphere::new(3.0);
140    /// assert_eq!(sphere.radius(), 3.0);
141    /// ```
142    #[inline]
143    pub const fn radius(&self) -> Real {
144        self.radius
145    }
146
147    /// Sets the radius of the sphere.
148    ///
149    /// # Arguments
150    ///
151    /// * `value` - The new radius (must be positive)
152    ///
153    /// # Panics
154    ///
155    /// Panics if `value` is not positive.
156    ///
157    /// # Examples
158    ///
159    /// ```rust
160    /// use phys_geom::shape::Sphere;
161    ///
162    /// let mut sphere = Sphere::new(1.0);
163    /// sphere.set_radius(2.0);
164    /// assert_eq!(sphere.radius(), 2.0);
165    /// ```
166    #[inline]
167    pub fn set_radius(&mut self, value: Real) {
168        *self = Self::new(value);
169    }
170}
171
172/// A capsule shape centered at the origin, aligned along the Y-axis.
173///
174/// A capsule consists of a cylinder with hemispherical caps at both ends.
175/// It's commonly used in physics engines for character controllers because
176/// it provides good collision detection while being easy to rotate.
177///
178/// The capsule is centered at the origin (0, 0, 0) and extends along the Y-axis.
179///
180/// # Examples
181///
182/// ```rust
183/// use phys_geom::shape::Capsule;
184///
185/// // Create a capsule with half-height 2.0 and radius 0.5
186/// let capsule = Capsule::new(2.0, 0.5);
187/// assert_eq!(capsule.half_height(), 2.0);
188/// assert_eq!(capsule.radius(), 0.5);
189/// assert_eq!(capsule.height(), 4.0); // total height
190/// ```
191///
192/// # Dimensions
193///
194/// - `half_height`: Height of the cylindrical portion (excluding caps)
195/// - `radius`: Radius of both the cylinder and the hemispherical caps
196/// - Total height = `2 * half_height + 2 * radius`
197#[repr(C)]
198#[derive(Debug, Copy, Clone, Default, PartialEq)]
199#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
200pub struct Capsule {
201    half_height: Real,
202    radius: Real,
203}
204
205impl Capsule {
206    /// Creates a new capsule with the given dimensions.
207    ///
208    /// # Arguments
209    ///
210    /// * `half_height` - Half the height of the cylindrical portion (must be non-negative)
211    /// * `radius` - The radius of the capsule (must be positive)
212    ///
213    /// # Returns
214    ///
215    /// A new `Capsule` instance.
216    ///
217    /// # Panics
218    ///
219    /// Panics if:
220    /// - `half_height` is negative
221    /// - `radius` is not positive
222    ///
223    /// # Examples
224    ///
225    /// ```rust
226    /// use phys_geom::shape::Capsule;
227    ///
228    /// // Create a capsule with cylindrical portion 3 units tall and 0.5 unit radius
229    /// let capsule = Capsule::new(1.5, 0.5);
230    /// assert_eq!(capsule.height(), 3.0);
231    /// ```
232    #[must_use]
233    pub fn new(half_height: Real, radius: Real) -> Self {
234        assert!(half_height >= 0.0);
235        assert!(radius > 0.0);
236        Self {
237            half_height,
238            radius,
239        }
240    }
241
242    /// Returns the height of the cylindrical portion of the capsule.
243    ///
244    /// This is the height of just the cylindrical part, excluding the hemispherical caps.
245    /// Height = 2 * half_height
246    ///
247    /// # Returns
248    ///
249    /// The height of the cylindrical portion as a `Real`.
250    ///
251    /// # Examples
252    ///
253    /// ```rust
254    /// use phys_geom::shape::Capsule;
255    ///
256    /// let capsule = Capsule::new(2.0, 0.5);
257    /// assert_eq!(capsule.height(), 4.0); // cylinder height = 2 * half_height
258    /// ```
259    #[inline]
260    pub fn height(&self) -> Real {
261        self.half_height + self.half_height
262    }
263
264    /// Returns the half height of the cylindrical portion.
265    ///
266    /// This is the height of the cylindrical part excluding the hemispherical caps.
267    ///
268    /// # Returns
269    ///
270    /// The half height as a `Real`.
271    ///
272    /// # Examples
273    ///
274    /// ```rust
275    /// use phys_geom::shape::Capsule;
276    ///
277    /// let capsule = Capsule::new(2.0, 0.5);
278    /// assert_eq!(capsule.half_height(), 2.0);
279    /// ```
280    #[inline]
281    pub fn half_height(&self) -> Real {
282        self.half_height
283    }
284
285    /// Returns the radius of the capsule.
286    ///
287    /// This is the radius of both the cylindrical body and the hemispherical caps.
288    ///
289    /// # Returns
290    ///
291    /// The radius as a `Real`.
292    ///
293    /// # Examples
294    ///
295    /// ```rust
296    /// use phys_geom::shape::Capsule;
297    ///
298    /// let capsule = Capsule::new(2.0, 0.5);
299    /// assert_eq!(capsule.radius(), 0.5);
300    /// ```
301    #[inline]
302    pub fn radius(&self) -> Real {
303        self.radius
304    }
305}
306
307/// A cuboid (rectangular box) shape centered at the origin.
308///
309/// A cuboid is a three-dimensional rectangular box with six rectangular faces.
310/// It's one of the most commonly used shapes in physics engines for representing
311/// boxes, containers, and general rectangular objects.
312///
313/// The cuboid is centered at the origin (0, 0, 0) with its edges aligned to the coordinate axes.
314///
315/// # Examples
316///
317/// ```rust
318/// use phys_geom::shape::Cuboid;
319///
320/// // Create a cuboid with dimensions 2x3x1
321/// let cuboid = Cuboid::new([2.0, 3.0, 1.0]);
322/// assert_eq!(cuboid.length(), [2.0, 3.0, 1.0]);
323/// assert_eq!(cuboid.half_length(), [1.0, 1.5, 0.5]);
324///
325/// // Create a unit cube
326/// let unit_cube = Cuboid::UNIT;
327/// assert_eq!(unit_cube.length(), [1.0, 1.0, 1.0]);
328/// ```
329///
330/// # Dimensions
331///
332/// - `half_length`: Half-lengths along each axis (x, y, z)
333/// - Total dimensions: `[x, y, z] = [2 * half_length[0], 2 * half_length[1], 2 * half_length[2]]`
334///
335/// # Construction
336///
337/// The cuboid can be constructed from:
338/// - An array of three lengths: `Cuboid::new([x, y, z])`
339/// - Individual components: `Cuboid::new_xyz(x, y, z)`
340#[repr(C)]
341#[derive(Debug, Copy, Clone, Default, PartialEq)]
342#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
343pub struct Cuboid {
344    half_length: [Real; 3],
345}
346
347impl Cuboid {
348    /// A unit cube with side length 1.0.
349    ///
350    /// This is useful as a reference size for normalized shapes.
351    /// The cube extends from -0.5 to +0.5 along each axis.
352    pub const UNIT: Cuboid = Cuboid {
353        half_length: [0.5, 0.5, 0.5],
354    };
355
356    /// Creates a new cuboid with the given dimensions.
357    ///
358    /// # Arguments
359    ///
360    /// * `length` - The dimensions of the cuboid as a 3-element array [x, y, z]
361    ///
362    /// # Returns
363    ///
364    /// A new `Cuboid` instance.
365    ///
366    /// # Panics
367    ///
368    /// Panics if any component of length is not positive.
369    ///
370    /// # Examples
371    ///
372    /// ```rust
373    /// use phys_geom::shape::Cuboid;
374    ///
375    /// // Create from array
376    /// let cuboid1 = Cuboid::new([2.0, 3.0, 1.0]);
377    ///
378    /// // Create from tuple (via Into trait)
379    /// let cuboid2 = Cuboid::new((2.0, 3.0, 1.0));
380    ///
381    /// assert_eq!(cuboid1.length(), cuboid2.length());
382    /// ```
383    #[inline]
384    #[must_use]
385    pub fn new(length: impl Into<[Real; 3]>) -> Cuboid {
386        let length = length.into();
387        assert!(length[0] > 0.0);
388        assert!(length[1] > 0.0);
389        assert!(length[2] > 0.0);
390
391        const HALF: Real = 0.5;
392        Cuboid {
393            half_length: [length[0] * HALF, length[1] * HALF, length[2] * HALF],
394        }
395    }
396
397    /// Creates a new cuboid with individual x, y, z dimensions.
398    ///
399    /// This is a convenience method equivalent to `Cuboid::new([x, y, z])`.
400    ///
401    /// # Arguments
402    ///
403    /// * `x` - Length along the X-axis
404    /// * `y` - Length along the Y-axis
405    /// * `z` - Length along the Z-axis
406    ///
407    /// # Returns
408    ///
409    /// A new `Cuboid` instance.
410    ///
411    /// # Examples
412    ///
413    /// ```rust
414    /// use phys_geom::shape::Cuboid;
415    ///
416    /// let cuboid = Cuboid::new_xyz(2.0, 3.0, 1.0);
417    /// assert_eq!(cuboid.length(), [2.0, 3.0, 1.0]);
418    /// ```
419    #[inline]
420    pub fn new_xyz(x: Real, y: Real, z: Real) -> Cuboid {
421        Self::new([x, y, z])
422    }
423
424    /// Returns the full dimensions of the cuboid.
425    ///
426    /// Returns an array [x, y, z] representing the total length along each axis.
427    ///
428    /// # Returns
429    ///
430    /// An array of three `Real` values [x, y, z].
431    ///
432    /// # Examples
433    ///
434    /// ```rust
435    /// use phys_geom::shape::Cuboid;
436    ///
437    /// let cuboid = Cuboid::new([2.0, 3.0, 1.0]);
438    /// assert_eq!(cuboid.length(), [2.0, 3.0, 1.0]);
439    /// ```
440    #[inline]
441    pub fn length(&self) -> [Real; 3] {
442        const DOUBLE: Real = 2.0;
443        [
444            self.half_length[0] * DOUBLE,
445            self.half_length[1] * DOUBLE,
446            self.half_length[2] * DOUBLE,
447        ]
448    }
449
450    /// Returns the half-lengths of the cuboid.
451    ///
452    /// Returns an array [x/2, y/2, z/2] representing the distance from the center
453    /// to each face along the respective axes.
454    ///
455    /// # Returns
456    ///
457    /// An array of three `Real` values [x/2, y/2, z/2].
458    ///
459    /// # Examples
460    ///
461    /// ```rust
462    /// use phys_geom::shape::Cuboid;
463    ///
464    /// let cuboid = Cuboid::new([2.0, 3.0, 1.0]);
465    /// assert_eq!(cuboid.half_length(), [1.0, 1.5, 0.5]);
466    /// ```
467    #[inline]
468    pub fn half_length(&self) -> [Real; 3] {
469        self.half_length
470    }
471
472    /// Sets the dimensions of the cuboid.
473    ///
474    /// # Arguments
475    ///
476    /// * `length` - The new dimensions as a 3-element array [x, y, z]
477    ///
478    /// # Panics
479    ///
480    /// Panics if any component of length is not positive.
481    ///
482    /// # Examples
483    ///
484    /// ```rust
485    /// use phys_geom::shape::Cuboid;
486    ///
487    /// let mut cuboid = Cuboid::new([1.0, 1.0, 1.0]);
488    /// cuboid.set_length([2.0, 3.0, 1.0]);
489    /// assert_eq!(cuboid.length(), [2.0, 3.0, 1.0]);
490    /// ```
491    #[inline]
492    pub fn set_length(&mut self, length: impl Into<[Real; 3]>) {
493        *self = Self::new(length);
494    }
495}
496
497/// A cylinder shape centered at the origin, aligned along the Y-axis.
498///
499/// A cylinder is a three-dimensional shape with a circular cross-section.
500/// It's commonly used in physics engines for representing pillars, pipes,
501/// wheels, and other cylindrical objects.
502///
503/// The cylinder is centered at the origin (0, 0, 0) with its axis
504/// aligned to the Y-axis. It extends from -half_height to +half_height
505/// along the Y-axis.
506///
507/// # Examples
508///
509/// ```rust
510/// use phys_geom::shape::Cylinder;
511///
512/// // Create a cylinder with height 3.0 and radius 0.8
513/// let cylinder = Cylinder::new(1.5, 0.8);
514/// assert_eq!(cylinder.height(), 3.0);
515/// assert_eq!(cylinder.radius(), 0.8);
516/// ```
517///
518/// # Dimensions
519///
520/// - `half_height`: Half the height of the cylinder
521/// - `radius`: Radius of the circular cross-section
522/// - Total height = `2 * half_height`
523#[repr(C)]
524#[derive(Debug, Clone, Default, Copy, PartialEq)]
525#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
526pub struct Cylinder {
527    half_height: Real,
528    radius: Real,
529}
530
531impl Cylinder {
532    /// Creates a new cylinder with the given dimensions.
533    ///
534    /// # Arguments
535    ///
536    /// * `half_height` - Half the height of the cylinder (must be positive)
537    /// * `radius` - The radius of the cylinder (must be positive)
538    ///
539    /// # Returns
540    ///
541    /// A new `Cylinder` instance.
542    ///
543    /// # Panics
544    ///
545    /// Panics if `half_height` or `radius` is not positive.
546    ///
547    /// # Examples
548    ///
549    /// ```rust
550    /// use phys_geom::shape::Cylinder;
551    ///
552    /// // Create a cylinder with total height 4.0 and radius 1.0
553    /// let cylinder = Cylinder::new(2.0, 1.0);
554    /// assert_eq!(cylinder.height(), 4.0);
555    /// assert_eq!(cylinder.radius(), 1.0);
556    /// ```
557    #[inline]
558    #[must_use]
559    pub fn new(half_height: Real, radius: Real) -> Self {
560        assert!(radius > 0.0);
561        assert!(half_height > 0.0);
562        Self {
563            half_height,
564            radius,
565        }
566    }
567
568    /// Returns the total height of the cylinder.
569    ///
570    /// # Returns
571    ///
572    /// The total height as a `Real`.
573    ///
574    /// # Examples
575    ///
576    /// ```rust
577    /// use phys_geom::shape::Cylinder;
578    ///
579    /// let cylinder = Cylinder::new(1.5, 0.8);
580    /// assert_eq!(cylinder.height(), 3.0);
581    /// ```
582    #[inline]
583    pub fn height(&self) -> Real {
584        self.half_height * 2.0
585    }
586
587    /// Returns the half height of the cylinder.
588    ///
589    /// This is the distance from the center to either end of the cylinder.
590    ///
591    /// # Returns
592    ///
593    /// The half height as a `Real`.
594    ///
595    /// # Examples
596    ///
597    /// ```rust
598    /// use phys_geom::shape::Cylinder;
599    ///
600    /// let cylinder = Cylinder::new(1.5, 0.8);
601    /// assert_eq!(cylinder.half_height(), 1.5);
602    /// ```
603    #[inline]
604    pub fn half_height(&self) -> Real {
605        self.half_height
606    }
607
608    /// Returns the radius of the cylinder.
609    ///
610    /// # Returns
611    ///
612    /// The radius as a `Real`.
613    ///
614    /// # Examples
615    ///
616    /// ```rust
617    /// use phys_geom::shape::Cylinder;
618    ///
619    /// let cylinder = Cylinder::new(1.5, 0.8);
620    /// assert_eq!(cylinder.radius(), 0.8);
621    /// ```
622    #[inline]
623    pub fn radius(&self) -> Real {
624        self.radius
625    }
626}
627
628/// An infinite plane in 3D space.
629///
630/// An infinite plane represents a flat surface that extends infinitely in all directions.
631/// This is primarily used for geometric computations and as a conceptual shape in
632/// physics simulations (e.g., ground planes, collision surfaces).
633///
634/// Note: This is a marker struct for type system purposes. The actual plane definition
635/// would typically be handled separately with a normal vector and distance from origin.
636///
637/// # FFI Safety
638///
639/// The struct includes padding to ensure FFI safety, as empty structs are not
640/// FFI-safe in Rust (see [Rust issue #17679]).
641///
642/// # Examples
643///
644/// ```rust
645/// use phys_geom::shape::InfinitePlane;
646///
647/// // Create an infinite plane
648/// let plane = InfinitePlane::default();
649/// ```
650#[repr(C)]
651#[derive(Default, Debug, Clone, Copy, PartialEq)]
652#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
653pub struct InfinitePlane {
654    // empty struct is not FFI-safe, https://github.com/rust-lang/rust/issues/17679
655    _padding: i32,
656}
657
658/// A triangle defined by three vertices in 3D space.
659///
660/// A triangle is the simplest polygon in 3D space, consisting of three vertices
661/// connected by three edges. It's a fundamental building block in computer graphics,
662/// computational geometry, and physics simulations.
663///
664/// The triangle is defined by three vertices A, B, and C. The orientation and
665/// properties are computed based on the relative positions of these vertices.
666///
667/// # Examples
668///
669/// ```rust
670/// use phys_geom::shape::Triangle;
671/// use phys_geom::math::Point3;
672///
673/// // Create a triangle
674/// let triangle = Triangle::new(
675///     Point3::new(0.0, 0.0, 0.0),
676///     Point3::new(1.0, 0.0, 0.0),
677///     Point3::new(0.0, 1.0, 0.0),
678/// );
679/// ```
680///
681/// # Mathematical Properties
682///
683/// - Area = 0.5 * |(B-A) × (C-A)|
684/// - Normal = normalize((B-A) × (C-A))
685/// - Center = (A + B + C) / 3
686#[repr(C)]
687#[derive(Debug, Clone, Copy, Default, PartialEq)]
688#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
689pub struct Triangle {
690    /// First vertex of the triangle
691    pub a: Point3,
692    /// Second vertex of the triangle
693    pub b: Point3,
694    /// Third vertex of the triangle
695    pub c: Point3,
696}
697
698impl Triangle {
699    /// Creates a new triangle from three vertices.
700    ///
701    /// # Arguments
702    ///
703    /// * `a` - First vertex
704    /// * `b` - Second vertex
705    /// * `c` - Third vertex
706    ///
707    /// # Returns
708    ///
709    /// A new `Triangle` instance.
710    ///
711    /// # Examples
712    ///
713    /// ```rust
714    /// use phys_geom::shape::Triangle;
715    /// use phys_geom::math::Point3;
716    ///
717    /// let triangle = Triangle::new(
718    ///     Point3::new(0.0, 0.0, 0.0),
719    ///     Point3::new(1.0, 0.0, 0.0),
720    ///     Point3::new(0.0, 1.0, 0.0),
721    /// );
722    /// ```
723    #[inline]
724    #[must_use]
725    pub fn new(a: impl Into<[Real; 3]>, b: impl Into<[Real; 3]>, c: impl Into<[Real; 3]>) -> Self {
726        Self {
727            a: Point3::from(a.into()),
728            b: Point3::from(b.into()),
729            c: Point3::from(c.into()),
730        }
731    }
732
733    /// Computes the geometric center (centroid) of the triangle.
734    ///
735    /// The centroid is calculated as the average of all three vertices:
736    /// C = (A + B + C) / 3
737    ///
738    /// # Returns
739    ///
740    /// The centroid coordinates as a `Point3`.
741    ///
742    /// # Examples
743    ///
744    /// ```rust
745    /// use phys_geom::shape::Triangle;
746    /// use phys_geom::math::Point3;
747    ///
748    /// let triangle = Triangle::new(
749    ///     Point3::new(0.0, 0.0, 0.0),
750    ///     Point3::new(2.0, 0.0, 0.0),
751    ///     Point3::new(0.0, 2.0, 0.0),
752    /// );
753    ///
754    /// let center = triangle.center();
755    /// assert_eq!(center, Point3::new(2.0/3.0, 2.0/3.0, 0.0));
756    /// ```
757    #[inline]
758    pub fn center(&self) -> Point3 {
759        let a: Vec3 = self.a.coords;
760        let b: Vec3 = self.b.coords;
761        let c: Vec3 = self.c.coords;
762        Point3::from((a + b + c) / 3.0)
763    }
764
765    /// Computes the normal vector of the triangle.
766    ///
767    /// The normal is calculated using the cross product of two edges:
768    /// N = normalize((B-A) × (C-A))
769    ///
770    /// The direction follows the right-hand rule based on the vertex order (A, B, C).
771    ///
772    /// # Returns
773    ///
774    /// The unit normal vector as a `UnitVec3`.
775    ///
776    /// # Examples
777    ///
778    /// ```rust
779    /// use phys_geom::shape::Triangle;
780    /// use phys_geom::math::{Point3, UnitVec3};
781    ///
782    /// let triangle = Triangle::new(
783    ///     Point3::new(0.0, 0.0, 0.0),
784    ///     Point3::new(1.0, 0.0, 0.0),
785    ///     Point3::new(0.0, 1.0, 0.0),
786    /// );
787    ///
788    /// let normal = triangle.normal();
789    /// // Triangle lies in the XY plane, normal points in +Z direction
790    /// assert!(normal.z > 0.9); // Close to 1.0 for unit vector
791    /// ```
792    #[inline]
793    pub fn normal(&self) -> UnitVec3 {
794        let edge1 = self.b - self.a;
795        let edge2 = self.c - self.a;
796        UnitVec3::new_normalize(edge1.cross(&edge2))
797    }
798
799    /// Computes the area of the triangle.
800    ///
801    /// The area is calculated using half the magnitude of the cross product:
802    /// Area = 0.5 * |(B-A) × (C-A)|
803    ///
804    /// # Returns
805    ///
806    /// The area as a `Real` value.
807    ///
808    /// # Examples
809    ///
810    /// ```rust
811    /// use phys_geom::shape::Triangle;
812    /// use phys_geom::math::Point3;
813    ///
814    /// let triangle = Triangle::new(
815    ///     Point3::new(0.0, 0.0, 0.0),
816    ///     Point3::new(2.0, 0.0, 0.0),
817    ///     Point3::new(0.0, 2.0, 0.0),
818    /// );
819    ///
820    /// let area = triangle.area();
821    /// assert_eq!(area, 2.0); // Right triangle with legs of length 2
822    /// ```
823    #[inline]
824    pub fn area(&self) -> Real {
825        let edge1 = self.b - self.a;
826        let edge2 = self.c - self.a;
827        edge1.cross(&edge2).magnitude() / 2.0
828    }
829
830    /// Checks if the triangle is degenerate (has zero area).
831    ///
832    /// A triangle is degenerate if all three vertices are collinear, which means
833    /// the cross product of two edges has zero (or near-zero) magnitude.
834    ///
835    /// # Returns
836    ///
837    /// `true` if the triangle is degenerate, `false` otherwise.
838    ///
839    /// # Examples
840    ///
841    /// ```rust
842    /// use phys_geom::shape::Triangle;
843    /// use phys_geom::math::Point3;
844    ///
845    /// // Non-degenerate triangle
846    /// let triangle1 = Triangle::new(
847    ///     Point3::new(0.0, 0.0, 0.0),
848    ///     Point3::new(1.0, 0.0, 0.0),
849    ///     Point3::new(0.0, 1.0, 0.0),
850    /// );
851    /// assert!(!triangle1.is_degenerate());
852    ///
853    /// // Degenerate triangle (all points on a line)
854    /// let triangle2 = Triangle::new(
855    ///     Point3::new(0.0, 0.0, 0.0),
856    ///     Point3::new(1.0, 0.0, 0.0),
857    ///     Point3::new(2.0, 0.0, 0.0),
858    /// );
859    /// assert!(triangle2.is_degenerate());
860    /// ```
861    #[inline]
862    pub fn is_degenerate(&self) -> bool {
863        self.area() <= Real::EPSILON
864    }
865}
866
867use crate::{Aabb3, ComputeAabb3};
868
869impl ComputeAabb3 for Triangle {
870    #[inline]
871    fn compute_aabb(&self) -> Aabb3 {
872        let p1 = self.a.coords.inf(&self.b.coords).inf(&self.c.coords);
873        let p2 = self.a.coords.sup(&self.b.coords).sup(&self.c.coords);
874        Aabb3::new(Point3::from(p1), Point3::from(p2))
875    }
876}
877
878/// A tetrahedron defined by four vertices in 3D space.
879///
880/// A tetrahedron is a polyhedron with four triangular faces, six edges, and four vertices.
881/// It's the simplest type of polyhedron and is commonly used in computational geometry,
882/// finite element analysis, and physics simulations.
883///
884/// The tetrahedron is defined by four vertices A, B, C, and D. The volume and other
885/// properties are computed based on the relative positions of these vertices.
886///
887/// # Examples
888///
889/// ```rust
890/// use phys_geom::shape::Tetrahedron;
891///
892/// // Create a regular tetrahedron
893/// let tetrahedron = Tetrahedron {
894///     a: [1.0, 1.0, 1.0],
895///     b: [1.0, -1.0, -1.0],
896///     c: [-1.0, 1.0, -1.0],
897///     d: [-1.0, -1.0, 1.0],
898/// };
899///
900/// // Compute its signed volume
901/// let volume = tetrahedron.signed_volume();
902/// let center = tetrahedron.center();
903/// ```
904///
905/// # Volume Sign Convention
906///
907/// The signed volume follows the right-hand rule:
908/// - Positive volume: Vertex D is on the side pointed by the normal of triangle ABC
909/// - Negative volume: Vertex D is on the opposite side
910/// - Zero volume: All vertices are coplanar
911///
912/// # Mathematical Properties
913///
914/// - Volume = (1/6) * |(B-A) · ((C-A) × (D-A))|
915/// - Center = (A + B + C + D) / 4
916pub struct Tetrahedron {
917    /// First vertex of the tetrahedron
918    pub a: [Real; 3],
919    /// Second vertex of the tetrahedron
920    pub b: [Real; 3],
921    /// Third vertex of the tetrahedron
922    pub c: [Real; 3],
923    /// Fourth vertex of the tetrahedron
924    pub d: [Real; 3],
925}
926
927impl Tetrahedron {
928    /// Computes the signed volume of this tetrahedron.
929    ///
930    /// The signed volume is calculated using the scalar triple product:
931    /// V = (1/6) * ((B-A) · ((C-A) × (D-A)))
932    ///
933    /// # Returns
934    ///
935    /// The signed volume as a `Real` value.
936    ///
937    /// # Volume Sign Convention
938    ///
939    /// - **Positive**: Vertex D is on the half-space pointed by the normal of the oriented triangle
940    ///   (A, B, C)
941    /// - **Negative**: Vertex D is on the opposite side
942    /// - **Zero**: All four vertices are coplanar
943    ///
944    /// # Examples
945    ///
946    /// ```rust
947    /// use phys_geom::shape::Tetrahedron;
948    ///
949    /// let tetrahedron = Tetrahedron {
950    ///     a: [0.0, 0.0, 0.0],
951    ///     b: [1.0, 0.0, 0.0],
952    ///     c: [0.0, 1.0, 0.0],
953    ///     d: [0.0, 0.0, 1.0],
954    /// };
955    ///
956    /// let volume = tetrahedron.signed_volume();
957    /// assert!(volume > 0.0);
958    /// ```
959    #[inline]
960    pub fn signed_volume(&self) -> Real {
961        let a: Vec3 = self.a.into();
962        let b: Vec3 = self.b.into();
963        let c: Vec3 = self.c.into();
964        let d: Vec3 = self.d.into();
965        let v1 = b - a;
966        let v2 = c - a;
967        let v3 = d - a;
968        v1.dot(&v2.cross(&v3)) / 6.0
969    }
970
971    /// Computes the centroid (geometric center) of the tetrahedron.
972    ///
973    /// The centroid is calculated as the average of all four vertices:
974    /// C = (A + B + C + D) / 4
975    ///
976    /// # Returns
977    ///
978    /// The centroid coordinates as a 3-element array [x, y, z].
979    ///
980    /// # Examples
981    ///
982    /// ```rust
983    /// use phys_geom::shape::Tetrahedron;
984    ///
985    /// let tetrahedron = Tetrahedron {
986    ///     a: [0.0, 0.0, 0.0],
987    ///     b: [2.0, 0.0, 0.0],
988    ///     c: [0.0, 2.0, 0.0],
989    ///     d: [0.0, 0.0, 2.0],
990    /// };
991    ///
992    /// let center = tetrahedron.center();
993    /// assert_eq!(center, [0.5, 0.5, 0.5]);
994    /// ```
995    #[inline]
996    pub fn center(&self) -> [Real; 3] {
997        let a: Vec3 = self.a.into();
998        let b: Vec3 = self.b.into();
999        let c: Vec3 = self.c.into();
1000        let d: Vec3 = self.d.into();
1001        let center = (a + b + c + d) / 4.0;
1002        center.into()
1003    }
1004}
1005
1006#[cfg(test)]
1007mod tests {
1008
1009    use super::*;
1010    use crate::ComputeAabb3;
1011
1012    #[test]
1013    fn test_sphere() {
1014        let mut sphere = Sphere::new(1.0);
1015        assert_eq!(sphere.radius(), 1.0);
1016
1017        sphere.set_radius(2.0);
1018        assert_eq!(sphere.radius(), 2.0);
1019    }
1020
1021    #[test]
1022    fn test_capsule() {
1023        let capsule = Capsule::new(1.0, 1.0);
1024        assert_eq!(capsule.half_height(), 1.0);
1025        assert_eq!(capsule.radius(), 1.0);
1026    }
1027
1028    #[test]
1029    fn test_cuboid() {
1030        let cuboid = Cuboid::new([1.0, 1.0, 1.0]);
1031        assert_eq!(cuboid.length(), [1.0, 1.0, 1.0]);
1032        assert_eq!(cuboid.half_length(), [0.5, 0.5, 0.5]);
1033    }
1034
1035    #[test]
1036    fn test_cylinder() {
1037        let cylinder = Cylinder::new(1.0, 1.0);
1038        assert_eq!(cylinder.height(), 2.0);
1039        assert_eq!(cylinder.half_height(), 1.0);
1040        assert_eq!(cylinder.radius(), 1.0);
1041    }
1042
1043    #[test]
1044    fn test_triangle() {
1045        let triangle = Triangle::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1046
1047        assert_eq!(triangle.a, Point3::new(0.0, 0.0, 0.0));
1048        assert_eq!(triangle.b, Point3::new(1.0, 0.0, 0.0));
1049        assert_eq!(triangle.c, Point3::new(0.0, 1.0, 0.0));
1050    }
1051
1052    #[test]
1053    fn test_triangle_center() {
1054        let triangle = Triangle::new(
1055            Point3::new(0.0, 0.0, 0.0),
1056            Point3::new(2.0, 0.0, 0.0),
1057            Point3::new(0.0, 2.0, 0.0),
1058        );
1059
1060        let center = triangle.center();
1061        assert_eq!(center, Point3::new(2.0 / 3.0, 2.0 / 3.0, 0.0));
1062    }
1063
1064    #[test]
1065    fn test_triangle_normal() {
1066        let triangle = Triangle::new(
1067            Point3::new(0.0, 0.0, 0.0),
1068            Point3::new(1.0, 0.0, 0.0),
1069            Point3::new(0.0, 1.0, 0.0),
1070        );
1071
1072        let normal = triangle.normal();
1073        // Triangle lies in the XY plane, normal should point in +Z direction
1074        assert!(normal.z > 0.9);
1075        assert!(normal.x.abs() < 0.1);
1076        assert!(normal.y.abs() < 0.1);
1077    }
1078
1079    #[test]
1080    fn test_triangle_area() {
1081        let triangle = Triangle::new(
1082            Point3::new(0.0, 0.0, 0.0),
1083            Point3::new(2.0, 0.0, 0.0),
1084            Point3::new(0.0, 2.0, 0.0),
1085        );
1086
1087        let area = triangle.area();
1088        assert_eq!(area, 2.0); // Right triangle with legs of length 2
1089    }
1090
1091    #[test]
1092    fn test_triangle_degenerate() {
1093        // Non-degenerate triangle
1094        let triangle1 = Triangle::new(
1095            Point3::new(0.0, 0.0, 0.0),
1096            Point3::new(1.0, 0.0, 0.0),
1097            Point3::new(0.0, 1.0, 0.0),
1098        );
1099        assert!(!triangle1.is_degenerate());
1100
1101        // Degenerate triangle (all points on a line)
1102        let triangle2 = Triangle::new(
1103            Point3::new(0.0, 0.0, 0.0),
1104            Point3::new(1.0, 0.0, 0.0),
1105            Point3::new(2.0, 0.0, 0.0),
1106        );
1107        assert!(triangle2.is_degenerate());
1108    }
1109
1110    #[test]
1111    fn test_triangle_equality() {
1112        let triangle1 = Triangle::new(
1113            Point3::new(0.0, 0.0, 0.0),
1114            Point3::new(1.0, 0.0, 0.0),
1115            Point3::new(0.0, 1.0, 0.0),
1116        );
1117
1118        let triangle2 = Triangle::new(
1119            Point3::new(0.0, 0.0, 0.0),
1120            Point3::new(1.0, 0.0, 0.0),
1121            Point3::new(0.0, 1.0, 0.0),
1122        );
1123
1124        let triangle3 = Triangle::new(
1125            Point3::new(0.0, 0.0, 0.0),
1126            Point3::new(1.0, 0.0, 0.0),
1127            Point3::new(0.0, 2.0, 0.0),
1128        );
1129
1130        assert_eq!(triangle1, triangle2);
1131        assert_ne!(triangle1, triangle3);
1132    }
1133
1134    #[test]
1135    fn test_triangle_aabb() {
1136        let triangle = Triangle::new(
1137            Point3::new(0.0, 0.0, 0.0),
1138            Point3::new(1.0, 2.0, 1.0),
1139            Point3::new(0.0, 1.0, 0.0),
1140        );
1141
1142        let aabb = triangle.compute_aabb();
1143        assert_eq!(aabb.min(), Point3::new(0.0, 0.0, 0.0));
1144        assert_eq!(aabb.max(), Point3::new(1.0, 2.0, 1.0));
1145    }
1146}