rust_training_tool/
collision.rs

1//! Simple collision resolution logic.
2
3use eframe::egui::{Rect, Vec2};
4
5/// The direction in which an object colliding with something should bounce.
6#[derive(Debug, Eq, PartialEq, Hash)]
7pub enum BounceDirection {
8    Up,
9    Left,
10    Down,
11    Right,
12}
13
14/// A function to check collision between some moving object with a bounding box and the
15/// edges of some boundary. If the boundary rectangle does not fully contain
16/// the bounding box, return the direction the object must bounce to stay within
17/// the boundary.
18///
19/// ### Example
20///
21/// ```
22/// use rust_training_tool::collision;
23/// use rust_training_tool::egui::{Pos2, Rect, Vec2};
24/// let boundary = Rect::from_center_size(Pos2::ZERO, Vec2::new(10.0, 10.0));
25///
26/// // boundary box is inside the boundary rect
27/// let mut bounding_box = Rect::from_center_size(Pos2::ZERO, Vec2::new(1.0, 1.0));
28/// assert!(collision::collide_with_boundary(&bounding_box, &boundary).is_none());
29///
30/// // move bounding box so it intersects the boundary on the right
31/// bounding_box.set_center(Pos2::new(9.1, 0.0));
32/// assert!(matches!(
33///     collision::collide_with_boundary(&bounding_box, &boundary),
34///     Some(collision::BounceDirection::Left)
35/// ));
36/// ```
37pub fn collide_with_boundary(bounding_box: &Rect, boundary: &Rect) -> Option<BounceDirection> {
38    if !boundary.contains_rect(*bounding_box) {
39        if bounding_box.left() < boundary.left() {
40            Some(BounceDirection::Right)
41        } else if bounding_box.right() > boundary.right() {
42            Some(BounceDirection::Left)
43        } else if bounding_box.top() < boundary.top() {
44            Some(BounceDirection::Down)
45        } else if bounding_box.bottom() > boundary.bottom() {
46            Some(BounceDirection::Up)
47        } else {
48            eprintln!("WARNING: outside but not colliding?");
49            None
50        }
51    } else {
52        None
53    }
54}
55
56/// A function to check collision between an object moving in `self_direction` with a bounding box
57/// `self_bounding_box`, and some other bounding box. If bounding boxes intersect,
58/// return the direction the moving object should bounce, otherwise, return None.
59///
60/// ### Example
61///
62/// ```
63/// use rust_training_tool::collision;
64/// use rust_training_tool::egui::{Pos2, Rect, Vec2};
65///
66/// // I am moving up
67/// let self_direction = -Vec2::Y;
68/// let self_bounding_box = Rect::from_center_size(Pos2::ZERO, Vec2::new(1.0, 1.0));
69/// let other_bounding_box = Rect::from_center_size(
70///     Pos2::new(1.0, 0.9),
71///     Vec2::new(1.0, 1.0),
72/// );
73/// assert!(matches!(
74///     collision::collide_with_rect(
75///         &self_direction,
76///         &self_bounding_box,
77///         &other_bounding_box
78///     ),
79///     Some(collision::BounceDirection::Down)
80/// ));
81///
82/// // I am moving right
83/// let self_direction = Vec2::X;
84/// let other_bounding_box = Rect::from_center_size(
85///     Pos2::new(0.9, 1.0),
86///     Vec2::new(1.0, 1.0),
87/// );
88/// assert!(matches!(
89///     collision::collide_with_rect(
90///         &self_direction,
91///         &self_bounding_box,
92///         &other_bounding_box
93///     ),
94///     Some(collision::BounceDirection::Left)
95/// ));
96/// ```
97pub fn collide_with_rect(
98    self_direction: &Vec2,
99    self_bounding_box: &Rect,
100    other_bounding_box: &Rect,
101) -> Option<BounceDirection> {
102    if self_bounding_box.intersects(*other_bounding_box) {
103        let inside_bottom = -(self_bounding_box.top() - other_bounding_box.bottom());
104        let inside_right = -(self_bounding_box.left() - other_bounding_box.right());
105        let inside_left = self_bounding_box.right() - other_bounding_box.left();
106        let inside_top = self_bounding_box.bottom() - other_bounding_box.top();
107
108        let mut min_in: f32 = 999.0;
109        if inside_bottom >= 0.0 && self_direction.y < 0.0 {
110            min_in = min_in.min(inside_bottom);
111        }
112        if inside_right >= 0.0 && self_direction.x < 0.0 {
113            min_in = min_in.min(inside_right);
114        }
115        if inside_left >= 0.0 && self_direction.x > 0.0 {
116            min_in = min_in.min(inside_left);
117        }
118        if inside_top >= 0.0 && self_direction.y > 0.0 {
119            min_in = min_in.min(inside_top);
120        }
121
122        if min_in == inside_bottom {
123            Some(BounceDirection::Down)
124        } else if min_in == inside_right {
125            Some(BounceDirection::Right)
126        } else if min_in == inside_left {
127            Some(BounceDirection::Left)
128        } else if min_in == inside_top {
129            Some(BounceDirection::Up)
130        } else {
131            eprintln!("WARNING: colliding but not inside?");
132            None
133        }
134    } else {
135        None
136    }
137}