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}