2
2
3
3
use bevy_math:: { Vec2 , Vec3 } ;
4
4
5
- #[ derive( Debug ) ]
5
+ #[ derive( Debug , PartialEq , Eq ) ]
6
6
pub enum Collision {
7
7
Left ,
8
8
Right ,
@@ -16,6 +16,11 @@ pub enum Collision {
16
16
/// * `a_pos` and `b_pos` are the center positions of the rectangles, typically obtained by
17
17
/// extracting the `translation` field from a `Transform` component
18
18
/// * `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.
19
24
pub fn collide ( a_pos : Vec3 , a_size : Vec2 , b_pos : Vec3 , b_size : Vec2 ) -> Option < Collision > {
20
25
let a_min = a_pos. truncate ( ) - a_size / 2.0 ;
21
26
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
55
60
None
56
61
}
57
62
}
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