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}