Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/math/bounding_2d.rs
6592 views
1
//! This example demonstrates bounding volume intersections.
2
3
use bevy::{
4
color::palettes::css::*,
5
math::{bounding::*, ops, Isometry2d},
6
prelude::*,
7
};
8
9
fn main() {
10
App::new()
11
.add_plugins(DefaultPlugins)
12
.init_state::<Test>()
13
.add_systems(Startup, setup)
14
.add_systems(
15
Update,
16
(update_text, spin, update_volumes, update_test_state),
17
)
18
.add_systems(
19
PostUpdate,
20
(
21
render_shapes,
22
(
23
aabb_intersection_system.run_if(in_state(Test::AabbSweep)),
24
circle_intersection_system.run_if(in_state(Test::CircleSweep)),
25
ray_cast_system.run_if(in_state(Test::RayCast)),
26
aabb_cast_system.run_if(in_state(Test::AabbCast)),
27
bounding_circle_cast_system.run_if(in_state(Test::CircleCast)),
28
),
29
render_volumes,
30
)
31
.chain(),
32
)
33
.run();
34
}
35
36
#[derive(Component)]
37
struct Spin;
38
39
fn spin(time: Res<Time>, mut query: Query<&mut Transform, With<Spin>>) {
40
for mut transform in query.iter_mut() {
41
transform.rotation *= Quat::from_rotation_z(time.delta_secs() / 5.);
42
}
43
}
44
45
#[derive(States, Default, Debug, Hash, PartialEq, Eq, Clone, Copy)]
46
enum Test {
47
AabbSweep,
48
CircleSweep,
49
#[default]
50
RayCast,
51
AabbCast,
52
CircleCast,
53
}
54
55
fn update_test_state(
56
keycode: Res<ButtonInput<KeyCode>>,
57
cur_state: Res<State<Test>>,
58
mut state: ResMut<NextState<Test>>,
59
) {
60
if !keycode.just_pressed(KeyCode::Space) {
61
return;
62
}
63
64
use Test::*;
65
let next = match **cur_state {
66
AabbSweep => CircleSweep,
67
CircleSweep => RayCast,
68
RayCast => AabbCast,
69
AabbCast => CircleCast,
70
CircleCast => AabbSweep,
71
};
72
state.set(next);
73
}
74
75
fn update_text(mut text: Single<&mut Text>, cur_state: Res<State<Test>>) {
76
if !cur_state.is_changed() {
77
return;
78
}
79
80
text.clear();
81
82
text.push_str("Intersection test:\n");
83
use Test::*;
84
for &test in &[AabbSweep, CircleSweep, RayCast, AabbCast, CircleCast] {
85
let s = if **cur_state == test { "*" } else { " " };
86
text.push_str(&format!(" {s} {test:?} {s}\n"));
87
}
88
text.push_str("\nPress space to cycle");
89
}
90
91
#[derive(Component)]
92
enum Shape {
93
Rectangle(Rectangle),
94
Circle(Circle),
95
Triangle(Triangle2d),
96
Line(Segment2d),
97
Capsule(Capsule2d),
98
Polygon(RegularPolygon),
99
}
100
101
fn render_shapes(mut gizmos: Gizmos, query: Query<(&Shape, &Transform)>) {
102
let color = GRAY;
103
for (shape, transform) in query.iter() {
104
let translation = transform.translation.xy();
105
let rotation = transform.rotation.to_euler(EulerRot::YXZ).2;
106
let isometry = Isometry2d::new(translation, Rot2::radians(rotation));
107
match shape {
108
Shape::Rectangle(r) => {
109
gizmos.primitive_2d(r, isometry, color);
110
}
111
Shape::Circle(c) => {
112
gizmos.primitive_2d(c, isometry, color);
113
}
114
Shape::Triangle(t) => {
115
gizmos.primitive_2d(t, isometry, color);
116
}
117
Shape::Line(l) => {
118
gizmos.primitive_2d(l, isometry, color);
119
}
120
Shape::Capsule(c) => {
121
gizmos.primitive_2d(c, isometry, color);
122
}
123
Shape::Polygon(p) => {
124
gizmos.primitive_2d(p, isometry, color);
125
}
126
}
127
}
128
}
129
130
#[derive(Component)]
131
enum DesiredVolume {
132
Aabb,
133
Circle,
134
}
135
136
#[derive(Component, Debug)]
137
enum CurrentVolume {
138
Aabb(Aabb2d),
139
Circle(BoundingCircle),
140
}
141
142
fn update_volumes(
143
mut commands: Commands,
144
query: Query<
145
(Entity, &DesiredVolume, &Shape, &Transform),
146
Or<(Changed<DesiredVolume>, Changed<Shape>, Changed<Transform>)>,
147
>,
148
) {
149
for (entity, desired_volume, shape, transform) in query.iter() {
150
let translation = transform.translation.xy();
151
let rotation = transform.rotation.to_euler(EulerRot::YXZ).2;
152
let isometry = Isometry2d::new(translation, Rot2::radians(rotation));
153
match desired_volume {
154
DesiredVolume::Aabb => {
155
let aabb = match shape {
156
Shape::Rectangle(r) => r.aabb_2d(isometry),
157
Shape::Circle(c) => c.aabb_2d(isometry),
158
Shape::Triangle(t) => t.aabb_2d(isometry),
159
Shape::Line(l) => l.aabb_2d(isometry),
160
Shape::Capsule(c) => c.aabb_2d(isometry),
161
Shape::Polygon(p) => p.aabb_2d(isometry),
162
};
163
commands.entity(entity).insert(CurrentVolume::Aabb(aabb));
164
}
165
DesiredVolume::Circle => {
166
let circle = match shape {
167
Shape::Rectangle(r) => r.bounding_circle(isometry),
168
Shape::Circle(c) => c.bounding_circle(isometry),
169
Shape::Triangle(t) => t.bounding_circle(isometry),
170
Shape::Line(l) => l.bounding_circle(isometry),
171
Shape::Capsule(c) => c.bounding_circle(isometry),
172
Shape::Polygon(p) => p.bounding_circle(isometry),
173
};
174
commands
175
.entity(entity)
176
.insert(CurrentVolume::Circle(circle));
177
}
178
}
179
}
180
}
181
182
fn render_volumes(mut gizmos: Gizmos, query: Query<(&CurrentVolume, &Intersects)>) {
183
for (volume, intersects) in query.iter() {
184
let color = if **intersects { AQUA } else { ORANGE_RED };
185
match volume {
186
CurrentVolume::Aabb(a) => {
187
gizmos.rect_2d(a.center(), a.half_size() * 2., color);
188
}
189
CurrentVolume::Circle(c) => {
190
gizmos.circle_2d(c.center(), c.radius(), color);
191
}
192
}
193
}
194
}
195
196
#[derive(Component, Deref, DerefMut, Default)]
197
struct Intersects(bool);
198
199
const OFFSET_X: f32 = 125.;
200
const OFFSET_Y: f32 = 75.;
201
202
fn setup(mut commands: Commands) {
203
commands.spawn(Camera2d);
204
205
commands.spawn((
206
Transform::from_xyz(-OFFSET_X, OFFSET_Y, 0.),
207
Shape::Circle(Circle::new(45.)),
208
DesiredVolume::Aabb,
209
Intersects::default(),
210
));
211
212
commands.spawn((
213
Transform::from_xyz(0., OFFSET_Y, 0.),
214
Shape::Rectangle(Rectangle::new(80., 80.)),
215
Spin,
216
DesiredVolume::Circle,
217
Intersects::default(),
218
));
219
220
commands.spawn((
221
Transform::from_xyz(OFFSET_X, OFFSET_Y, 0.),
222
Shape::Triangle(Triangle2d::new(
223
Vec2::new(-40., -40.),
224
Vec2::new(-20., 40.),
225
Vec2::new(40., 50.),
226
)),
227
Spin,
228
DesiredVolume::Aabb,
229
Intersects::default(),
230
));
231
232
commands.spawn((
233
Transform::from_xyz(-OFFSET_X, -OFFSET_Y, 0.),
234
Shape::Line(Segment2d::from_direction_and_length(
235
Dir2::from_xy(1., 0.3).unwrap(),
236
90.,
237
)),
238
Spin,
239
DesiredVolume::Circle,
240
Intersects::default(),
241
));
242
243
commands.spawn((
244
Transform::from_xyz(0., -OFFSET_Y, 0.),
245
Shape::Capsule(Capsule2d::new(25., 50.)),
246
Spin,
247
DesiredVolume::Aabb,
248
Intersects::default(),
249
));
250
251
commands.spawn((
252
Transform::from_xyz(OFFSET_X, -OFFSET_Y, 0.),
253
Shape::Polygon(RegularPolygon::new(50., 6)),
254
Spin,
255
DesiredVolume::Circle,
256
Intersects::default(),
257
));
258
259
commands.spawn((
260
Text::default(),
261
Node {
262
position_type: PositionType::Absolute,
263
top: px(12),
264
left: px(12),
265
..default()
266
},
267
));
268
}
269
270
fn draw_filled_circle(gizmos: &mut Gizmos, position: Vec2, color: Srgba) {
271
for r in [1., 2., 3.] {
272
gizmos.circle_2d(position, r, color);
273
}
274
}
275
276
fn draw_ray(gizmos: &mut Gizmos, ray: &RayCast2d) {
277
gizmos.line_2d(
278
ray.ray.origin,
279
ray.ray.origin + *ray.ray.direction * ray.max,
280
WHITE,
281
);
282
draw_filled_circle(gizmos, ray.ray.origin, FUCHSIA);
283
}
284
285
fn get_and_draw_ray(gizmos: &mut Gizmos, time: &Time) -> RayCast2d {
286
let ray = Vec2::new(ops::cos(time.elapsed_secs()), ops::sin(time.elapsed_secs()));
287
let dist = 150. + ops::sin(0.5 * time.elapsed_secs()).abs() * 500.;
288
289
let aabb_ray = Ray2d {
290
origin: ray * 250.,
291
direction: Dir2::new_unchecked(-ray),
292
};
293
let ray_cast = RayCast2d::from_ray(aabb_ray, dist - 20.);
294
295
draw_ray(gizmos, &ray_cast);
296
ray_cast
297
}
298
299
fn ray_cast_system(
300
mut gizmos: Gizmos,
301
time: Res<Time>,
302
mut volumes: Query<(&CurrentVolume, &mut Intersects)>,
303
) {
304
let ray_cast = get_and_draw_ray(&mut gizmos, &time);
305
306
for (volume, mut intersects) in volumes.iter_mut() {
307
let toi = match volume {
308
CurrentVolume::Aabb(a) => ray_cast.aabb_intersection_at(a),
309
CurrentVolume::Circle(c) => ray_cast.circle_intersection_at(c),
310
};
311
**intersects = toi.is_some();
312
if let Some(toi) = toi {
313
draw_filled_circle(
314
&mut gizmos,
315
ray_cast.ray.origin + *ray_cast.ray.direction * toi,
316
LIME,
317
);
318
}
319
}
320
}
321
322
fn aabb_cast_system(
323
mut gizmos: Gizmos,
324
time: Res<Time>,
325
mut volumes: Query<(&CurrentVolume, &mut Intersects)>,
326
) {
327
let ray_cast = get_and_draw_ray(&mut gizmos, &time);
328
let aabb_cast = AabbCast2d {
329
aabb: Aabb2d::new(Vec2::ZERO, Vec2::splat(15.)),
330
ray: ray_cast,
331
};
332
333
for (volume, mut intersects) in volumes.iter_mut() {
334
let toi = match *volume {
335
CurrentVolume::Aabb(a) => aabb_cast.aabb_collision_at(a),
336
CurrentVolume::Circle(_) => None,
337
};
338
339
**intersects = toi.is_some();
340
if let Some(toi) = toi {
341
gizmos.rect_2d(
342
aabb_cast.ray.ray.origin + *aabb_cast.ray.ray.direction * toi,
343
aabb_cast.aabb.half_size() * 2.,
344
LIME,
345
);
346
}
347
}
348
}
349
350
fn bounding_circle_cast_system(
351
mut gizmos: Gizmos,
352
time: Res<Time>,
353
mut volumes: Query<(&CurrentVolume, &mut Intersects)>,
354
) {
355
let ray_cast = get_and_draw_ray(&mut gizmos, &time);
356
let circle_cast = BoundingCircleCast {
357
circle: BoundingCircle::new(Vec2::ZERO, 15.),
358
ray: ray_cast,
359
};
360
361
for (volume, mut intersects) in volumes.iter_mut() {
362
let toi = match *volume {
363
CurrentVolume::Aabb(_) => None,
364
CurrentVolume::Circle(c) => circle_cast.circle_collision_at(c),
365
};
366
367
**intersects = toi.is_some();
368
if let Some(toi) = toi {
369
gizmos.circle_2d(
370
circle_cast.ray.ray.origin + *circle_cast.ray.ray.direction * toi,
371
circle_cast.circle.radius(),
372
LIME,
373
);
374
}
375
}
376
}
377
378
fn get_intersection_position(time: &Time) -> Vec2 {
379
let x = ops::cos(0.8 * time.elapsed_secs()) * 250.;
380
let y = ops::sin(0.4 * time.elapsed_secs()) * 100.;
381
Vec2::new(x, y)
382
}
383
384
fn aabb_intersection_system(
385
mut gizmos: Gizmos,
386
time: Res<Time>,
387
mut volumes: Query<(&CurrentVolume, &mut Intersects)>,
388
) {
389
let center = get_intersection_position(&time);
390
let aabb = Aabb2d::new(center, Vec2::splat(50.));
391
gizmos.rect_2d(center, aabb.half_size() * 2., YELLOW);
392
393
for (volume, mut intersects) in volumes.iter_mut() {
394
let hit = match volume {
395
CurrentVolume::Aabb(a) => aabb.intersects(a),
396
CurrentVolume::Circle(c) => aabb.intersects(c),
397
};
398
399
**intersects = hit;
400
}
401
}
402
403
fn circle_intersection_system(
404
mut gizmos: Gizmos,
405
time: Res<Time>,
406
mut volumes: Query<(&CurrentVolume, &mut Intersects)>,
407
) {
408
let center = get_intersection_position(&time);
409
let circle = BoundingCircle::new(center, 50.);
410
gizmos.circle_2d(center, circle.radius(), YELLOW);
411
412
for (volume, mut intersects) in volumes.iter_mut() {
413
let hit = match volume {
414
CurrentVolume::Aabb(a) => circle.intersects(a),
415
CurrentVolume::Circle(c) => circle.intersects(c),
416
};
417
418
**intersects = hit;
419
}
420
}
421
422