Skip to content

Commit 6ac5800

Browse files
robemnicopap
authored andcommitted
Add more documentation and tests to collide_aabb::collide() (#5910)
While looking into `collide()`, I wrote some tests to confirm the behavior I read in the code. This PR adds those tests and improves the documentation. Co-authored-by: robem <[email protected]>
1 parent 1065092 commit 6ac5800

File tree

1 file changed

+135
-1
lines changed

1 file changed

+135
-1
lines changed

crates/bevy_sprite/src/collide_aabb.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use bevy_math::{Vec2, Vec3};
44

5-
#[derive(Debug)]
5+
#[derive(Debug, PartialEq, Eq)]
66
pub enum Collision {
77
Left,
88
Right,
@@ -16,6 +16,11 @@ pub enum Collision {
1616
/// * `a_pos` and `b_pos` are the center positions of the rectangles, typically obtained by
1717
/// extracting the `translation` field from a `Transform` component
1818
/// * `a_size` and `b_size` are the dimensions (width and height) of the rectangles.
19+
///
20+
/// The return value is the side of `B` that `A` has collided with. `Left` means that
21+
/// `A` collided with `B`'s left side. `Top` means that `A` collided with `B`'s top side.
22+
/// If the collision occurs on multiple sides, the side with the deepest penetration is returned.
23+
/// If all sides are involved, `Inside` is returned.
1924
pub fn collide(a_pos: Vec3, a_size: Vec2, b_pos: Vec3, b_size: Vec2) -> Option<Collision> {
2025
let a_min = a_pos.truncate() - a_size / 2.0;
2126
let a_max = a_pos.truncate() + a_size / 2.0;
@@ -55,3 +60,132 @@ pub fn collide(a_pos: Vec3, a_size: Vec2, b_pos: Vec3, b_size: Vec2) -> Option<C
5560
None
5661
}
5762
}
63+
64+
#[cfg(test)]
65+
mod test {
66+
use super::*;
67+
68+
fn collide_two_rectangles(
69+
// (x, y, size x, size y)
70+
a: (f32, f32, f32, f32),
71+
b: (f32, f32, f32, f32),
72+
) -> Option<Collision> {
73+
collide(
74+
Vec3::new(a.0, a.1, 0.),
75+
Vec2::new(a.2, a.3),
76+
Vec3::new(b.0, b.1, 0.),
77+
Vec2::new(b.2, b.3),
78+
)
79+
}
80+
81+
#[test]
82+
fn inside_collision() {
83+
// Identical
84+
#[rustfmt::skip]
85+
let res = collide_two_rectangles(
86+
(1., 1., 1., 1.),
87+
(1., 1., 1., 1.),
88+
);
89+
assert_eq!(res, Some(Collision::Inside));
90+
// B inside A
91+
#[rustfmt::skip]
92+
let res = collide_two_rectangles(
93+
(2., 2., 2., 2.),
94+
(2., 2., 1., 1.),
95+
);
96+
assert_eq!(res, Some(Collision::Inside));
97+
// A inside B
98+
#[rustfmt::skip]
99+
let res = collide_two_rectangles(
100+
(2., 2., 1., 1.),
101+
(2., 2., 2., 2.),
102+
);
103+
assert_eq!(res, Some(Collision::Inside));
104+
}
105+
106+
#[test]
107+
fn collision_based_on_b() {
108+
// Right of B
109+
#[rustfmt::skip]
110+
let res = collide_two_rectangles(
111+
(3., 2., 2., 2.),
112+
(2., 2., 2., 2.),
113+
);
114+
assert_eq!(res, Some(Collision::Right));
115+
// Left of B
116+
#[rustfmt::skip]
117+
let res = collide_two_rectangles(
118+
(1., 2., 2., 2.),
119+
(2., 2., 2., 2.),
120+
);
121+
assert_eq!(res, Some(Collision::Left));
122+
// Top of B
123+
#[rustfmt::skip]
124+
let res = collide_two_rectangles(
125+
(2., 3., 2., 2.),
126+
(2., 2., 2., 2.),
127+
);
128+
assert_eq!(res, Some(Collision::Top));
129+
// Bottom of B
130+
#[rustfmt::skip]
131+
let res = collide_two_rectangles(
132+
(2., 1., 2., 2.),
133+
(2., 2., 2., 2.),
134+
);
135+
assert_eq!(res, Some(Collision::Bottom));
136+
}
137+
138+
// In case the X-collision depth is equal to the Y-collision depth, always
139+
// prefer X-collision, meaning, `Left` or `Right` over `Top` and `Bottom`.
140+
#[test]
141+
fn prefer_x_collision() {
142+
// Bottom-left collision
143+
#[rustfmt::skip]
144+
let res = collide_two_rectangles(
145+
(1., 1., 2., 2.),
146+
(2., 2., 2., 2.),
147+
);
148+
assert_eq!(res, Some(Collision::Left));
149+
// Top-left collision
150+
#[rustfmt::skip]
151+
let res = collide_two_rectangles(
152+
(1., 3., 2., 2.),
153+
(2., 2., 2., 2.),
154+
);
155+
assert_eq!(res, Some(Collision::Left));
156+
// Bottom-right collision
157+
#[rustfmt::skip]
158+
let res = collide_two_rectangles(
159+
(3., 1., 2., 2.),
160+
(2., 2., 2., 2.),
161+
);
162+
assert_eq!(res, Some(Collision::Right));
163+
// Top-right collision
164+
#[rustfmt::skip]
165+
let res = collide_two_rectangles(
166+
(3., 3., 2., 2.),
167+
(2., 2., 2., 2.),
168+
);
169+
assert_eq!(res, Some(Collision::Right));
170+
}
171+
172+
// If the collision intersection area stretches more along the Y-axis then
173+
// return `Top` or `Bottom`. Otherwise, `Left` or `Right`.
174+
#[test]
175+
fn collision_depth_wins() {
176+
// Top-right collision
177+
#[rustfmt::skip]
178+
let res = collide_two_rectangles(
179+
(3., 3., 2., 2.),
180+
(2.5, 2.,2., 2.),
181+
);
182+
assert_eq!(res, Some(Collision::Top));
183+
// Top-right collision
184+
#[rustfmt::skip]
185+
let res = collide_two_rectangles(
186+
(3., 3., 2., 2.),
187+
(2., 2.5, 2., 2.),
188+
);
189+
assert_eq!(res, Some(Collision::Right));
190+
}
191+
}

0 commit comments

Comments
 (0)