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 broad-phase collision checking for `Rect`.
203impl Rect {
204    /// Performs a broad-phase check using bounding boxes to quickly reject non-colliding pairs.
205    ///
206    /// # Arguments
207    ///
208    /// - `Rect` - The first bounding box.
209    /// - `Rect` - The second bounding box.
210    ///
211    /// # Returns
212    ///
213    /// - `bool` - True if the bounding boxes overlap.
214    pub fn broad_phase_check(a: Rect, b: Rect) -> bool {
215        a.intersects(b)
216    }
217}
218
219/// Implements `Collider3D` trait and convenience methods for `AabbCollider3D`.
220impl AabbCollider3D {
221    /// Creates a new 3D AABB collider from a center point and dimensions.
222    ///
223    /// # Arguments
224    ///
225    /// - `Vector3D` - The center point.
226    /// - `f64` - The width.
227    /// - `f64` - The height.
228    /// - `f64` - The depth.
229    ///
230    /// # Returns
231    ///
232    /// - `AabbCollider3D` - The new collider.
233    pub fn from_center(center: Vector3D, width: f64, height: f64, depth: f64) -> AabbCollider3D {
234        AabbCollider3D::new(AABB3D::from_center(center, width, height, depth))
235    }
236
237    /// Tests collision with another 3D AABB collider and returns the collision result.
238    ///
239    /// # Arguments
240    ///
241    /// - `&AabbCollider3D` - The other collider.
242    ///
243    /// # Returns
244    ///
245    /// - `Option<CollisionResult3D>` - The collision result, or `None` if no collision.
246    pub fn collide_with_aabb(&self, other: &AabbCollider3D) -> Option<CollisionResult3D> {
247        let self_aabb: AABB3D = self.get_aabb();
248        let other_aabb: AABB3D = other.get_aabb();
249        let a_center: Vector3D = self_aabb.center();
250        let b_center: Vector3D = other_aabb.center();
251        let a_size: Vector3D = self_aabb.size();
252        let b_size: Vector3D = other_aabb.size();
253        let overlap_x: f64 =
254            (a_size.get_x() + b_size.get_x()) * 0.5 - (a_center.get_x() - b_center.get_x()).abs();
255        if overlap_x <= COLLIDER_CONTACT_EPSILON {
256            return None;
257        }
258        let overlap_y: f64 =
259            (a_size.get_y() + b_size.get_y()) * 0.5 - (a_center.get_y() - b_center.get_y()).abs();
260        if overlap_y <= COLLIDER_CONTACT_EPSILON {
261            return None;
262        }
263        let overlap_z: f64 =
264            (a_size.get_z() + b_size.get_z()) * 0.5 - (a_center.get_z() - b_center.get_z()).abs();
265        if overlap_z <= COLLIDER_CONTACT_EPSILON {
266            return None;
267        }
268        let (normal, depth) = if overlap_x <= overlap_y && overlap_x <= overlap_z {
269            let direction: f64 = if a_center.get_x() < b_center.get_x() {
270                -1.0
271            } else {
272                1.0
273            };
274            (Vector3D::new(direction, 0.0, 0.0), overlap_x)
275        } else if overlap_y <= overlap_z {
276            let direction: f64 = if a_center.get_y() < b_center.get_y() {
277                -1.0
278            } else {
279                1.0
280            };
281            (Vector3D::new(0.0, direction, 0.0), overlap_y)
282        } else {
283            let direction: f64 = if a_center.get_z() < b_center.get_z() {
284                -1.0
285            } else {
286                1.0
287            };
288            (Vector3D::new(0.0, 0.0, direction), overlap_z)
289        };
290        let self_min: Vector3D = self_aabb.get_min();
291        let self_max: Vector3D = self_aabb.get_max();
292        let other_min: Vector3D = other_aabb.get_min();
293        let other_max: Vector3D = other_aabb.get_max();
294        let contact_point: Vector3D = Vector3D::new(
295            self_min
296                .get_x()
297                .max(other_min.get_x())
298                .min(self_max.get_x().min(other_max.get_x())),
299            self_min
300                .get_y()
301                .max(other_min.get_y())
302                .min(self_max.get_y().min(other_max.get_y())),
303            self_min
304                .get_z()
305                .max(other_min.get_z())
306                .min(self_max.get_z().min(other_max.get_z())),
307        );
308        Some(CollisionResult3D::new(normal, depth, contact_point))
309    }
310
311    /// Tests collision with a sphere collider and returns the collision result.
312    ///
313    /// # Arguments
314    ///
315    /// - `&SphereCollider3D` - The sphere collider.
316    ///
317    /// # Returns
318    ///
319    /// - `Option<CollisionResult3D>` - The collision result, or `None` if no collision.
320    pub fn collide_with_sphere(&self, sphere: &SphereCollider3D) -> Option<CollisionResult3D> {
321        let self_aabb: AABB3D = self.get_aabb();
322        let sphere_inner: Sphere = sphere.get_sphere();
323        let aabb_min: Vector3D = self_aabb.get_min();
324        let aabb_max: Vector3D = self_aabb.get_max();
325        let closest_x: f64 = sphere_inner
326            .get_center()
327            .get_x()
328            .clamp(aabb_min.get_x(), aabb_max.get_x());
329        let closest_y: f64 = sphere_inner
330            .get_center()
331            .get_y()
332            .clamp(aabb_min.get_y(), aabb_max.get_y());
333        let closest_z: f64 = sphere_inner
334            .get_center()
335            .get_z()
336            .clamp(aabb_min.get_z(), aabb_max.get_z());
337        let closest: Vector3D = Vector3D::new(closest_x, closest_y, closest_z);
338        let delta: Vector3D = sphere_inner.get_center() - closest;
339        let distance_sq: f64 = delta.magnitude_squared();
340        if distance_sq >= sphere_inner.get_radius() * sphere_inner.get_radius() {
341            return None;
342        }
343        let distance: f64 = distance_sq.sqrt();
344        let normal: Vector3D = if distance < EPSILON {
345            let aabb_center: Vector3D = self_aabb.center();
346            let center_delta: Vector3D = sphere_inner.get_center() - aabb_center;
347            if center_delta.magnitude() < EPSILON {
348                Vector3D::up()
349            } else {
350                center_delta.normalized()
351            }
352        } else {
353            delta.scaled(1.0 / distance)
354        };
355        let depth: f64 = sphere_inner.get_radius() - distance;
356        let contact_point: Vector3D = closest;
357        Some(CollisionResult3D::new(normal, depth, contact_point))
358    }
359}
360
361/// Implements the `Collider3D` trait for `AabbCollider3D`.
362impl Collider3D for AabbCollider3D {
363    fn shape(&self) -> ColliderShape3D {
364        ColliderShape3D::Aabb
365    }
366
367    fn bounding_box(&self) -> AABB3D {
368        self.get_aabb()
369    }
370
371    fn contains_point(&self, point: Vector3D) -> bool {
372        self.get_aabb().contains(point)
373    }
374
375    fn center(&self) -> Vector3D {
376        self.get_aabb().center()
377    }
378}
379
380/// Implements `Collider3D` trait and convenience methods for `SphereCollider3D`.
381impl SphereCollider3D {
382    /// Creates a new 3D sphere collider from a center point and radius.
383    ///
384    /// # Arguments
385    ///
386    /// - `Vector3D` - The center point.
387    /// - `f64` - The radius.
388    ///
389    /// # Returns
390    ///
391    /// - `SphereCollider3D` - The new collider.
392    pub fn from_center(center: Vector3D, radius: f64) -> SphereCollider3D {
393        SphereCollider3D::new(Sphere::new(center, radius))
394    }
395
396    /// Tests collision with another sphere collider and returns the collision result.
397    ///
398    /// # Arguments
399    ///
400    /// - `&SphereCollider3D` - The other collider.
401    ///
402    /// # Returns
403    ///
404    /// - `Option<CollisionResult3D>` - The collision result, or `None` if no collision.
405    pub fn collide_with_sphere(&self, other: &SphereCollider3D) -> Option<CollisionResult3D> {
406        let self_sphere: Sphere = self.get_sphere();
407        let other_sphere: Sphere = other.get_sphere();
408        let delta: Vector3D = other_sphere.get_center() - self_sphere.get_center();
409        let distance: f64 = delta.magnitude();
410        let radius_sum: f64 = self_sphere.get_radius() + other_sphere.get_radius();
411        if distance >= radius_sum {
412            return None;
413        }
414        let normal: Vector3D = if distance < EPSILON {
415            Vector3D::right()
416        } else {
417            delta.scaled(1.0 / distance)
418        };
419        let depth: f64 = radius_sum - distance;
420        let contact_point: Vector3D =
421            self_sphere.get_center() + normal.scaled(self_sphere.get_radius());
422        Some(CollisionResult3D::new(normal, depth, contact_point))
423    }
424}
425
426/// Implements the `Collider3D` trait for `SphereCollider3D`.
427impl Collider3D for SphereCollider3D {
428    fn shape(&self) -> ColliderShape3D {
429        ColliderShape3D::Sphere
430    }
431
432    fn bounding_box(&self) -> AABB3D {
433        let sphere: Sphere = self.get_sphere();
434        let diameter: f64 = sphere.get_radius() * 2.0;
435        AABB3D::from_center(sphere.get_center(), diameter, diameter, diameter)
436    }
437
438    fn contains_point(&self, point: Vector3D) -> bool {
439        self.get_sphere().contains(point)
440    }
441
442    fn center(&self) -> Vector3D {
443        self.get_sphere().get_center()
444    }
445}
446
447/// Implements broad-phase collision checking for `AABB3D`.
448impl AABB3D {
449    /// Performs a broad-phase check using 3D bounding boxes to quickly reject non-colliding pairs.
450    ///
451    /// # Arguments
452    ///
453    /// - `AABB3D` - The first bounding box.
454    /// - `AABB3D` - The second bounding box.
455    ///
456    /// # Returns
457    ///
458    /// - `bool` - True if the bounding boxes overlap.
459    pub fn broad_phase_check(a: AABB3D, b: AABB3D) -> bool {
460        a.intersects(b)
461    }
462}