From 301ecf65ba5115d4a2bb8f9e43a1f727aab7fcb4 Mon Sep 17 00:00:00 2001 From: robem Date: Mon, 12 Sep 2022 01:25:34 +0000 Subject: [PATCH] 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 <669201+robem@users.noreply.github.com> --- crates/bevy_sprite/src/collide_aabb.rs | 136 ++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/crates/bevy_sprite/src/collide_aabb.rs b/crates/bevy_sprite/src/collide_aabb.rs index 05a4e9fe2a..fa49f13aed 100644 --- a/crates/bevy_sprite/src/collide_aabb.rs +++ b/crates/bevy_sprite/src/collide_aabb.rs @@ -2,7 +2,7 @@ use bevy_math::{Vec2, Vec3}; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Collision { Left, Right, @@ -16,6 +16,11 @@ pub enum Collision { /// * `a_pos` and `b_pos` are the center positions of the rectangles, typically obtained by /// extracting the `translation` field from a `Transform` component /// * `a_size` and `b_size` are the dimensions (width and height) of the rectangles. +/// +/// The return value is the side of `B` that `A` has collided with. `Left` means that +/// `A` collided with `B`'s left side. `Top` means that `A` collided with `B`'s top side. +/// If the collision occurs on multiple sides, the side with the deepest penetration is returned. +/// If all sides are involved, `Inside` is returned. pub fn collide(a_pos: Vec3, a_size: Vec2, b_pos: Vec3, b_size: Vec2) -> Option { let a_min = a_pos.truncate() - a_size / 2.0; 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 Option { + collide( + Vec3::new(a.0, a.1, 0.), + Vec2::new(a.2, a.3), + Vec3::new(b.0, b.1, 0.), + Vec2::new(b.2, b.3), + ) + } + + #[test] + fn inside_collision() { + // Identical + #[rustfmt::skip] + let res = collide_two_rectangles( + (1., 1., 1., 1.), + (1., 1., 1., 1.), + ); + assert_eq!(res, Some(Collision::Inside)); + // B inside A + #[rustfmt::skip] + let res = collide_two_rectangles( + (2., 2., 2., 2.), + (2., 2., 1., 1.), + ); + assert_eq!(res, Some(Collision::Inside)); + // A inside B + #[rustfmt::skip] + let res = collide_two_rectangles( + (2., 2., 1., 1.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Inside)); + } + + #[test] + fn collision_based_on_b() { + // Right of B + #[rustfmt::skip] + let res = collide_two_rectangles( + (3., 2., 2., 2.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Right)); + // Left of B + #[rustfmt::skip] + let res = collide_two_rectangles( + (1., 2., 2., 2.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Left)); + // Top of B + #[rustfmt::skip] + let res = collide_two_rectangles( + (2., 3., 2., 2.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Top)); + // Bottom of B + #[rustfmt::skip] + let res = collide_two_rectangles( + (2., 1., 2., 2.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Bottom)); + } + + // In case the X-collision depth is equal to the Y-collision depth, always + // prefer X-collision, meaning, `Left` or `Right` over `Top` and `Bottom`. + #[test] + fn prefer_x_collision() { + // Bottom-left collision + #[rustfmt::skip] + let res = collide_two_rectangles( + (1., 1., 2., 2.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Left)); + // Top-left collision + #[rustfmt::skip] + let res = collide_two_rectangles( + (1., 3., 2., 2.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Left)); + // Bottom-right collision + #[rustfmt::skip] + let res = collide_two_rectangles( + (3., 1., 2., 2.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Right)); + // Top-right collision + #[rustfmt::skip] + let res = collide_two_rectangles( + (3., 3., 2., 2.), + (2., 2., 2., 2.), + ); + assert_eq!(res, Some(Collision::Right)); + } + + // If the collision intersection area stretches more along the Y-axis then + // return `Top` or `Bottom`. Otherwise, `Left` or `Right`. + #[test] + fn collision_depth_wins() { + // Top-right collision + #[rustfmt::skip] + let res = collide_two_rectangles( + (3., 3., 2., 2.), + (2.5, 2.,2., 2.), + ); + assert_eq!(res, Some(Collision::Top)); + // Top-right collision + #[rustfmt::skip] + let res = collide_two_rectangles( + (3., 3., 2., 2.), + (2., 2.5, 2., 2.), + ); + assert_eq!(res, Some(Collision::Right)); + } +}