azalea_physics/collision/
entity_collisions.rs

1use azalea_core::aabb::Aabb;
2use azalea_entity::{
3    Physics,
4    metadata::{AbstractBoat, Shulker},
5};
6use azalea_world::Instance;
7use bevy_ecs::{
8    component::Component,
9    entity::Entity,
10    query::{Changed, Or, With},
11    system::{Commands, Query},
12};
13use tracing::error;
14
15use super::VoxelShape;
16
17/// This query matches on entities that we can collide with. That is, boats and
18/// shulkers.
19///
20/// If you want to use this in a more complex query, use
21/// [`CollidableEntityFilter`] as a filter instead.
22pub type CollidableEntityQuery<'world, 'state> = Query<'world, 'state, (), CollidableEntityFilter>;
23/// This filter matches on entities that we can collide with (boats and
24/// shulkers).
25///
26/// Use [`CollidableEntityQuery`] if you want an empty query that matches with
27/// this.
28pub type CollidableEntityFilter = Or<(With<AbstractBoat>, With<Shulker>)>;
29
30/// A component that mirrors the Physics::bounding_box of every entity, but is
31/// updated before client-side physics is done.
32#[derive(Component)]
33pub struct LastBoundingBox(pub Aabb);
34
35pub type AabbQuery<'world, 'state, 'a> = Query<'world, 'state, &'a LastBoundingBox>;
36
37/// Update the [`LastBoundingBox`] for every entity.
38pub fn update_last_bounding_box(
39    mut commands: Commands,
40    mut query: Query<(Entity, Option<&mut LastBoundingBox>, &Physics), Changed<Physics>>,
41) {
42    for (entity, mut last_bounding_box, physics) in &mut query {
43        if let Some(last_bounding_box) = last_bounding_box.as_mut() {
44            last_bounding_box.0 = physics.bounding_box;
45        } else {
46            commands
47                .entity(entity)
48                .insert(LastBoundingBox(physics.bounding_box));
49        }
50    }
51}
52
53pub fn get_entity_collisions(
54    world: &Instance,
55    aabb: &Aabb,
56    source_entity: Option<Entity>,
57    aabb_query: &AabbQuery,
58    collidable_entity_query: &CollidableEntityQuery,
59) -> Vec<VoxelShape> {
60    if aabb.size() < 1.0E-7 {
61        return vec![];
62    }
63
64    let collision_predicate = |entity| collidable_entity_query.get(entity).is_ok();
65
66    let collidable_entities = get_entities(
67        world,
68        source_entity,
69        &aabb.inflate_all(1.0E-7),
70        &collision_predicate,
71        aabb_query,
72    );
73
74    collidable_entities
75        .into_iter()
76        .map(|(_entity, aabb)| VoxelShape::from(aabb))
77        .collect()
78}
79
80/// Return all entities that are colliding with the given bounding box and match
81/// the given predicate.
82///
83/// `source_entity` is the entity that the bounding box belongs to, and won't be
84/// one of the returned entities.
85pub fn get_entities(
86    world: &Instance,
87    source_entity: Option<Entity>,
88    aabb: &Aabb,
89    predicate: &dyn Fn(Entity) -> bool,
90    aabb_query: &AabbQuery,
91) -> Vec<(Entity, Aabb)> {
92    let mut matches = Vec::new();
93
94    super::world_collisions::for_entities_in_chunks_colliding_with(
95        world,
96        aabb,
97        |_chunk_pos, entities_in_chunk| {
98            // now check if the entity itself collides
99            for &candidate in entities_in_chunk {
100                if Some(candidate) != source_entity && predicate(candidate) {
101                    let Ok(candidate_aabb) = aabb_query.get(candidate) else {
102                        error!(
103                            "Entity {candidate} (found from for_entities_in_chunks_colliding_with) is missing required components."
104                        );
105                        continue;
106                    };
107                    let candidate_aabb = &candidate_aabb.0;
108
109                    if aabb.intersects_aabb(candidate_aabb) {
110                        matches.push((candidate, candidate_aabb.to_owned()));
111                    }
112                }
113            }
114        },
115    );
116
117    matches
118}