Skip to main content

goud_engine/ecs/collision/
contact.rs

1//! Contact information returned by collision detection algorithms.
2
3use crate::core::math::Vec2;
4
5/// Contact information from a collision.
6///
7/// When two shapes collide, this struct contains all information needed to
8/// resolve the collision:
9///
10/// - **point**: The contact point in world space
11/// - **normal**: The collision normal (points from A to B)
12/// - **penetration**: How deep the shapes overlap (positive = overlapping)
13///
14/// # Example
15///
16/// ```
17/// use goud_engine::ecs::collision::{circle_circle_collision, Contact};
18/// use goud_engine::core::math::Vec2;
19///
20/// let contact = circle_circle_collision(
21///     Vec2::new(0.0, 0.0), 1.0,
22///     Vec2::new(1.5, 0.0), 1.0
23/// ).unwrap();
24///
25/// assert!(contact.penetration > 0.0);
26/// assert_eq!(contact.normal, Vec2::new(1.0, 0.0));
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct Contact {
30    /// Contact point in world space.
31    ///
32    /// This is the point where the two shapes are touching. For penetrating
33    /// collisions, this is typically the deepest penetration point.
34    pub point: Vec2,
35
36    /// Collision normal (unit vector from shape A to shape B).
37    ///
38    /// This vector points from the first shape to the second shape and is
39    /// normalized to unit length. It indicates the direction to separate
40    /// the shapes to resolve the collision.
41    pub normal: Vec2,
42
43    /// Penetration depth (positive = overlapping, negative = separated).
44    ///
45    /// This is the distance the shapes overlap. A positive value means the
46    /// shapes are penetrating. To resolve the collision, move the shapes
47    /// apart by this distance along the normal.
48    pub penetration: f32,
49}
50
51impl Contact {
52    /// Creates a new contact.
53    ///
54    /// # Arguments
55    ///
56    /// * `point` - Contact point in world space
57    /// * `normal` - Collision normal (should be normalized)
58    /// * `penetration` - Penetration depth
59    ///
60    /// # Example
61    ///
62    /// ```
63    /// use goud_engine::ecs::collision::Contact;
64    /// use goud_engine::core::math::Vec2;
65    ///
66    /// let contact = Contact::new(
67    ///     Vec2::new(1.0, 0.0),
68    ///     Vec2::new(1.0, 0.0),
69    ///     0.5
70    /// );
71    /// ```
72    pub fn new(point: Vec2, normal: Vec2, penetration: f32) -> Self {
73        Self {
74            point,
75            normal,
76            penetration,
77        }
78    }
79
80    /// Returns true if the contact represents a collision (positive penetration).
81    ///
82    /// # Example
83    ///
84    /// ```
85    /// use goud_engine::ecs::collision::Contact;
86    /// use goud_engine::core::math::Vec2;
87    ///
88    /// let colliding = Contact::new(Vec2::zero(), Vec2::unit_x(), 0.5);
89    /// let separated = Contact::new(Vec2::zero(), Vec2::unit_x(), -0.1);
90    ///
91    /// assert!(colliding.is_colliding());
92    /// assert!(!separated.is_colliding());
93    /// ```
94    pub fn is_colliding(&self) -> bool {
95        self.penetration > 0.0
96    }
97
98    /// Returns the separation distance needed to resolve the collision.
99    ///
100    /// This is the magnitude of the vector needed to separate the shapes.
101    ///
102    /// # Example
103    ///
104    /// ```
105    /// use goud_engine::ecs::collision::Contact;
106    /// use goud_engine::core::math::Vec2;
107    ///
108    /// let contact = Contact::new(Vec2::zero(), Vec2::unit_x(), 0.5);
109    /// assert_eq!(contact.separation_distance(), 0.5);
110    /// ```
111    pub fn separation_distance(&self) -> f32 {
112        self.penetration.abs()
113    }
114
115    /// Returns the separation vector needed to resolve the collision.
116    ///
117    /// This is the vector (normal * penetration) that would separate the shapes.
118    ///
119    /// # Example
120    ///
121    /// ```
122    /// use goud_engine::ecs::collision::Contact;
123    /// use goud_engine::core::math::Vec2;
124    ///
125    /// let contact = Contact::new(
126    ///     Vec2::zero(),
127    ///     Vec2::new(1.0, 0.0),
128    ///     0.5
129    /// );
130    /// assert_eq!(contact.separation_vector(), Vec2::new(0.5, 0.0));
131    /// ```
132    pub fn separation_vector(&self) -> Vec2 {
133        self.normal * self.penetration
134    }
135
136    /// Returns a contact with reversed normal (swaps A and B).
137    ///
138    /// This is useful when the collision detection function expects shapes
139    /// in a specific order but you have them reversed.
140    ///
141    /// # Example
142    ///
143    /// ```
144    /// use goud_engine::ecs::collision::Contact;
145    /// use goud_engine::core::math::Vec2;
146    ///
147    /// let contact = Contact::new(
148    ///     Vec2::zero(),
149    ///     Vec2::new(1.0, 0.0),
150    ///     0.5
151    /// );
152    /// let reversed = contact.reversed();
153    ///
154    /// assert_eq!(reversed.normal, Vec2::new(-1.0, 0.0));
155    /// assert_eq!(reversed.penetration, contact.penetration);
156    /// ```
157    pub fn reversed(&self) -> Self {
158        Self {
159            point: self.point,
160            normal: self.normal * -1.0,
161            penetration: self.penetration,
162        }
163    }
164}
165
166impl Default for Contact {
167    /// Returns a contact with no collision (zero penetration).
168    fn default() -> Self {
169        Self {
170            point: Vec2::zero(),
171            normal: Vec2::unit_x(),
172            penetration: 0.0,
173        }
174    }
175}