Skip to main content

euv_engine/collider/
impl.rs

1use crate::*;
2
3/// Implements `Collider` trait and convenience methods for `AabbCollider`.
4impl AabbCollider {
5    /// Creates a new AABB collider from a center point and dimensions.
6    ///
7    /// # Arguments
8    ///
9    /// - `Vector2D` - The center point.
10    /// - `f64` - The width.
11    /// - `f64` - The height.
12    ///
13    /// # Returns
14    ///
15    /// - `AabbCollider` - The new collider.
16    pub fn from_center(center: Vector2D, width: f64, height: f64) -> AabbCollider {
17        AabbCollider::new(Rect::from_center(center, width, height))
18    }
19
20    /// Tests collision with another AABB collider and returns the collision result.
21    ///
22    /// # Arguments
23    ///
24    /// - `&AabbCollider` - The other collider.
25    ///
26    /// # Returns
27    ///
28    /// - `Option<CollisionResult>` - The collision result, or `None` if no collision.
29    pub fn collide_with_aabb(&self, other: &AabbCollider) -> Option<CollisionResult> {
30        let self_rect: Rect = self.get_rect();
31        let other_rect: Rect = other.get_rect();
32        let a_min: Vector2D = self_rect.min();
33        let a_max: Vector2D = self_rect.max();
34        let b_min: Vector2D = other_rect.min();
35        let b_max: Vector2D = other_rect.max();
36        let overlap_x: f64 =
37            (a_max.get_x().min(b_max.get_x()) - a_min.get_x().max(b_min.get_x())).max(0.0);
38        let overlap_y: f64 =
39            (a_max.get_y().min(b_max.get_y()) - a_min.get_y().max(b_min.get_y())).max(0.0);
40        if overlap_x <= COLLIDER_CONTACT_EPSILON || overlap_y <= COLLIDER_CONTACT_EPSILON {
41            return None;
42        }
43        let (normal, depth) = if overlap_x < overlap_y {
44            let direction: f64 = if self_rect.center().get_x() < other_rect.center().get_x() {
45                -1.0
46            } else {
47                1.0
48            };
49            (Vector2D::new(direction, 0.0), overlap_x)
50        } else {
51            let direction: f64 = if self_rect.center().get_y() < other_rect.center().get_y() {
52                -1.0
53            } else {
54                1.0
55            };
56            (Vector2D::new(0.0, direction), overlap_y)
57        };
58        let contact_point: Vector2D = Vector2D::new(
59            a_min
60                .get_x()
61                .max(b_min.get_x())
62                .min(a_max.get_x().min(b_max.get_x())),
63            a_min
64                .get_y()
65                .max(b_min.get_y())
66                .min(a_max.get_y().min(b_max.get_y())),
67        );
68        Some(CollisionResult::new(normal, depth, contact_point))
69    }
70
71    /// Tests collision with a circle collider and returns the collision result.
72    ///
73    /// # Arguments
74    ///
75    /// - `&CircleCollider` - The circle collider.
76    ///
77    /// # Returns
78    ///
79    /// - `Option<CollisionResult>` - The collision result, or `None` if no collision.
80    pub fn collide_with_circle(&self, circle: &CircleCollider) -> Option<CollisionResult> {
81        let self_rect: Rect = self.get_rect();
82        let circle_inner: Circle = circle.get_circle();
83        let rect_min: Vector2D = self_rect.min();
84        let rect_max: Vector2D = self_rect.max();
85        let closest_x: f64 = circle_inner
86            .get_center()
87            .get_x()
88            .clamp(rect_min.get_x(), rect_max.get_x());
89        let closest_y: f64 = circle_inner
90            .get_center()
91            .get_y()
92            .clamp(rect_min.get_y(), rect_max.get_y());
93        let delta: Vector2D = circle_inner.get_center() - Vector2D::new(closest_x, closest_y);
94        let distance_sq: f64 = delta.magnitude_squared();
95        if distance_sq >= circle_inner.get_radius() * circle_inner.get_radius() {
96            return None;
97        }
98        let distance: f64 = distance_sq.sqrt();
99        let normal: Vector2D = if distance < EPSILON {
100            let aabb_center: Vector2D = self_rect.center();
101            let center_delta: Vector2D = circle_inner.get_center() - aabb_center;
102            if center_delta.magnitude() < EPSILON {
103                Vector2D::up()
104            } else {
105                center_delta.normalized()
106            }
107        } else {
108            delta.scaled(1.0 / distance)
109        };
110        let depth: f64 = circle_inner.get_radius() - distance;
111        let contact_point: Vector2D = Vector2D::new(closest_x, closest_y);
112        Some(CollisionResult::new(normal, depth, contact_point))
113    }
114}
115
116/// Implements the `Collider` trait for `AabbCollider`.
117impl Collider for AabbCollider {
118    fn shape(&self) -> ColliderShape {
119        ColliderShape::Aabb
120    }
121
122    fn bounding_box(&self) -> Rect {
123        self.get_rect()
124    }
125
126    fn contains_point(&self, point: Vector2D) -> bool {
127        self.get_rect().contains(point)
128    }
129
130    fn center(&self) -> Vector2D {
131        self.get_rect().center()
132    }
133}
134
135/// Implements `Collider` trait and convenience methods for `CircleCollider`.
136impl CircleCollider {
137    /// Creates a new circle collider from a center point and radius.
138    ///
139    /// # Arguments
140    ///
141    /// - `Vector2D` - The center point.
142    /// - `f64` - The radius.
143    ///
144    /// # Returns
145    ///
146    /// - `CircleCollider` - The new collider.
147    pub fn from_center(center: Vector2D, radius: f64) -> CircleCollider {
148        CircleCollider::new(Circle::new(center, radius))
149    }
150
151    /// Tests collision with another circle collider and returns the collision result.
152    ///
153    /// # Arguments
154    ///
155    /// - `&CircleCollider` - The other collider.
156    ///
157    /// # Returns
158    ///
159    /// - `Option<CollisionResult>` - The collision result, or `None` if no collision.
160    pub fn collide_with_circle(&self, other: &CircleCollider) -> Option<CollisionResult> {
161        let self_circle: Circle = self.get_circle();
162        let other_circle: Circle = other.get_circle();
163        let delta: Vector2D = other_circle.get_center() - self_circle.get_center();
164        let distance: f64 = delta.magnitude();
165        let radius_sum: f64 = self_circle.get_radius() + other_circle.get_radius();
166        if distance >= radius_sum {
167            return None;
168        }
169        let normal: Vector2D = if distance < EPSILON {
170            Vector2D::right()
171        } else {
172            delta.scaled(1.0 / distance)
173        };
174        let depth: f64 = radius_sum - distance;
175        let contact_point: Vector2D =
176            self_circle.get_center() + normal.scaled(self_circle.get_radius());
177        Some(CollisionResult::new(normal, depth, contact_point))
178    }
179}
180
181/// Implements the `Collider` trait for `CircleCollider`.
182impl Collider for CircleCollider {
183    fn shape(&self) -> ColliderShape {
184        ColliderShape::Circle
185    }
186
187    fn bounding_box(&self) -> Rect {
188        let circle: Circle = self.get_circle();
189        let diameter: f64 = circle.get_radius() * 2.0;
190        Rect::from_center(circle.get_center(), diameter, diameter)
191    }
192
193    fn contains_point(&self, point: Vector2D) -> bool {
194        self.get_circle().contains(point)
195    }
196
197    fn center(&self) -> Vector2D {
198        self.get_circle().get_center()
199    }
200}
201
202/// Implements `Collider3D` trait and convenience methods for `AabbCollider3D`.
203impl AabbCollider3D {
204    /// Creates a new 3D AABB collider from a center point and dimensions.
205    ///
206    /// # Arguments
207    ///
208    /// - `Vector3D` - The center point.
209    /// - `f64` - The width.
210    /// - `f64` - The height.
211    /// - `f64` - The depth.
212    ///
213    /// # Returns
214    ///
215    /// - `AabbCollider3D` - The new collider.
216    pub fn from_center(center: Vector3D, width: f64, height: f64, depth: f64) -> AabbCollider3D {
217        AabbCollider3D::new(AABB3D::from_center(center, width, height, depth))
218    }
219
220    /// Tests collision with another 3D AABB collider and returns the collision result.
221    ///
222    /// # Arguments
223    ///
224    /// - `&AabbCollider3D` - The other collider.
225    ///
226    /// # Returns
227    ///
228    /// - `Option<CollisionResult3D>` - The collision result, or `None` if no collision.
229    pub fn collide_with_aabb(&self, other: &AabbCollider3D) -> Option<CollisionResult3D> {
230        let self_aabb: AABB3D = self.get_aabb();
231        let other_aabb: AABB3D = other.get_aabb();
232        let a_center: Vector3D = self_aabb.center();
233        let b_center: Vector3D = other_aabb.center();
234        let a_size: Vector3D = self_aabb.size();
235        let b_size: Vector3D = other_aabb.size();
236        let overlap_x: f64 =
237            (a_size.get_x() + b_size.get_x()) * 0.5 - (a_center.get_x() - b_center.get_x()).abs();
238        if overlap_x <= COLLIDER_CONTACT_EPSILON {
239            return None;
240        }
241        let overlap_y: f64 =
242            (a_size.get_y() + b_size.get_y()) * 0.5 - (a_center.get_y() - b_center.get_y()).abs();
243        if overlap_y <= COLLIDER_CONTACT_EPSILON {
244            return None;
245        }
246        let overlap_z: f64 =
247            (a_size.get_z() + b_size.get_z()) * 0.5 - (a_center.get_z() - b_center.get_z()).abs();
248        if overlap_z <= COLLIDER_CONTACT_EPSILON {
249            return None;
250        }
251        let (normal, depth) = if overlap_x <= overlap_y && overlap_x <= overlap_z {
252            let direction: f64 = if a_center.get_x() < b_center.get_x() {
253                -1.0
254            } else {
255                1.0
256            };
257            (Vector3D::new(direction, 0.0, 0.0), overlap_x)
258        } else if overlap_y <= overlap_z {
259            let direction: f64 = if a_center.get_y() < b_center.get_y() {
260                -1.0
261            } else {
262                1.0
263            };
264            (Vector3D::new(0.0, direction, 0.0), overlap_y)
265        } else {
266            let direction: f64 = if a_center.get_z() < b_center.get_z() {
267                -1.0
268            } else {
269                1.0
270            };
271            (Vector3D::new(0.0, 0.0, direction), overlap_z)
272        };
273        let self_min: Vector3D = self_aabb.get_min();
274        let self_max: Vector3D = self_aabb.get_max();
275        let other_min: Vector3D = other_aabb.get_min();
276        let other_max: Vector3D = other_aabb.get_max();
277        let contact_point: Vector3D = Vector3D::new(
278            self_min
279                .get_x()
280                .max(other_min.get_x())
281                .min(self_max.get_x().min(other_max.get_x())),
282            self_min
283                .get_y()
284                .max(other_min.get_y())
285                .min(self_max.get_y().min(other_max.get_y())),
286            self_min
287                .get_z()
288                .max(other_min.get_z())
289                .min(self_max.get_z().min(other_max.get_z())),
290        );
291        Some(CollisionResult3D::new(normal, depth, contact_point))
292    }
293
294    /// Tests collision with a sphere collider and returns the collision result.
295    ///
296    /// # Arguments
297    ///
298    /// - `&SphereCollider3D` - The sphere collider.
299    ///
300    /// # Returns
301    ///
302    /// - `Option<CollisionResult3D>` - The collision result, or `None` if no collision.
303    pub fn collide_with_sphere(&self, sphere: &SphereCollider3D) -> Option<CollisionResult3D> {
304        let self_aabb: AABB3D = self.get_aabb();
305        let sphere_inner: Sphere = sphere.get_sphere();
306        let aabb_min: Vector3D = self_aabb.get_min();
307        let aabb_max: Vector3D = self_aabb.get_max();
308        let closest_x: f64 = sphere_inner
309            .get_center()
310            .get_x()
311            .clamp(aabb_min.get_x(), aabb_max.get_x());
312        let closest_y: f64 = sphere_inner
313            .get_center()
314            .get_y()
315            .clamp(aabb_min.get_y(), aabb_max.get_y());
316        let closest_z: f64 = sphere_inner
317            .get_center()
318            .get_z()
319            .clamp(aabb_min.get_z(), aabb_max.get_z());
320        let closest: Vector3D = Vector3D::new(closest_x, closest_y, closest_z);
321        let delta: Vector3D = sphere_inner.get_center() - closest;
322        let distance_sq: f64 = delta.magnitude_squared();
323        if distance_sq >= sphere_inner.get_radius() * sphere_inner.get_radius() {
324            return None;
325        }
326        let distance: f64 = distance_sq.sqrt();
327        let normal: Vector3D = if distance < EPSILON {
328            let aabb_center: Vector3D = self_aabb.center();
329            let center_delta: Vector3D = sphere_inner.get_center() - aabb_center;
330            if center_delta.magnitude() < EPSILON {
331                Vector3D::up()
332            } else {
333                center_delta.normalized()
334            }
335        } else {
336            delta.scaled(1.0 / distance)
337        };
338        let depth: f64 = sphere_inner.get_radius() - distance;
339        let contact_point: Vector3D = closest;
340        Some(CollisionResult3D::new(normal, depth, contact_point))
341    }
342}
343
344/// Implements the `Collider3D` trait for `AabbCollider3D`.
345impl Collider3D for AabbCollider3D {
346    fn shape(&self) -> ColliderShape3D {
347        ColliderShape3D::Aabb
348    }
349
350    fn bounding_box(&self) -> AABB3D {
351        self.get_aabb()
352    }
353
354    fn contains_point(&self, point: Vector3D) -> bool {
355        self.get_aabb().contains(point)
356    }
357
358    fn center(&self) -> Vector3D {
359        self.get_aabb().center()
360    }
361}
362
363/// Implements `Collider3D` trait and convenience methods for `SphereCollider3D`.
364impl SphereCollider3D {
365    /// Creates a new 3D sphere collider from a center point and radius.
366    ///
367    /// # Arguments
368    ///
369    /// - `Vector3D` - The center point.
370    /// - `f64` - The radius.
371    ///
372    /// # Returns
373    ///
374    /// - `SphereCollider3D` - The new collider.
375    pub fn from_center(center: Vector3D, radius: f64) -> SphereCollider3D {
376        SphereCollider3D::new(Sphere::new(center, radius))
377    }
378
379    /// Tests collision with another sphere collider and returns the collision result.
380    ///
381    /// # Arguments
382    ///
383    /// - `&SphereCollider3D` - The other collider.
384    ///
385    /// # Returns
386    ///
387    /// - `Option<CollisionResult3D>` - The collision result, or `None` if no collision.
388    pub fn collide_with_sphere(&self, other: &SphereCollider3D) -> Option<CollisionResult3D> {
389        let self_sphere: Sphere = self.get_sphere();
390        let other_sphere: Sphere = other.get_sphere();
391        let delta: Vector3D = other_sphere.get_center() - self_sphere.get_center();
392        let distance: f64 = delta.magnitude();
393        let radius_sum: f64 = self_sphere.get_radius() + other_sphere.get_radius();
394        if distance >= radius_sum {
395            return None;
396        }
397        let normal: Vector3D = if distance < EPSILON {
398            Vector3D::right()
399        } else {
400            delta.scaled(1.0 / distance)
401        };
402        let depth: f64 = radius_sum - distance;
403        let contact_point: Vector3D =
404            self_sphere.get_center() + normal.scaled(self_sphere.get_radius());
405        Some(CollisionResult3D::new(normal, depth, contact_point))
406    }
407}
408
409/// Implements the `Collider3D` trait for `SphereCollider3D`.
410impl Collider3D for SphereCollider3D {
411    fn shape(&self) -> ColliderShape3D {
412        ColliderShape3D::Sphere
413    }
414
415    fn bounding_box(&self) -> AABB3D {
416        let sphere: Sphere = self.get_sphere();
417        let diameter: f64 = sphere.get_radius() * 2.0;
418        AABB3D::from_center(sphere.get_center(), diameter, diameter, diameter)
419    }
420
421    fn contains_point(&self, point: Vector3D) -> bool {
422        self.get_sphere().contains(point)
423    }
424
425    fn center(&self) -> Vector3D {
426        self.get_sphere().get_center()
427    }
428}
429
430/// Implements broad-phase collision checking for `AABB3D`.
431impl AABB3D {
432    /// Performs a broad-phase check using 3D bounding boxes to quickly reject non-colliding pairs.
433    ///
434    /// # Arguments
435    ///
436    /// - `AABB3D` - The first bounding box.
437    /// - `AABB3D` - The second bounding box.
438    ///
439    /// # Returns
440    ///
441    /// - `bool` - True if the bounding boxes overlap.
442    pub fn broad_phase(a: AABB3D, b: AABB3D) -> bool {
443        a.intersects(b)
444    }
445}