Skip to main content

azalea_physics/collision/
world_collisions.rs

1use std::{collections::HashSet, sync::Arc};
2
3use azalea_block::{BlockState, fluid_state::FluidState};
4use azalea_core::{
5    cursor3d::{Cursor3d, CursorIteration, CursorIterationType},
6    math::EPSILON,
7    position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos, Vec3},
8};
9use azalea_inventory::ItemStack;
10use azalea_world::{Chunk, World};
11use bevy_ecs::entity::Entity;
12use parking_lot::RwLock;
13
14use super::{BLOCK_SHAPE, Shapes};
15use crate::collision::{Aabb, BlockWithShape, VoxelShape};
16
17pub fn get_block_collisions(world: &World, aabb: &Aabb) -> Vec<VoxelShape> {
18    let mut state = BlockCollisionsState::new(world, aabb, EntityCollisionContext::of(None));
19    let mut block_collisions = Vec::new();
20
21    let initial_chunk_pos = ChunkPos::from(state.cursor.origin());
22    let initial_chunk = world.chunks.get(&initial_chunk_pos);
23    let initial_chunk = initial_chunk.as_deref().map(RwLock::read);
24
25    while let Some(item) = state.cursor.next() {
26        state.compute_next(
27            item,
28            &mut block_collisions,
29            initial_chunk_pos,
30            initial_chunk.as_deref(),
31        );
32    }
33
34    block_collisions
35}
36
37pub fn get_block_and_liquid_collisions(world: &World, aabb: &Aabb) -> Vec<VoxelShape> {
38    let mut state = BlockCollisionsState::new(
39        world,
40        aabb,
41        EntityCollisionContext::of(None).with_include_liquids(true),
42    );
43    let mut block_collisions = Vec::new();
44
45    let initial_chunk_pos = ChunkPos::from(state.cursor.origin());
46    let initial_chunk = world.chunks.get(&initial_chunk_pos);
47    let initial_chunk = initial_chunk.as_deref().map(RwLock::read);
48
49    while let Some(item) = state.cursor.next() {
50        state.compute_next(
51            item,
52            &mut block_collisions,
53            initial_chunk_pos,
54            initial_chunk.as_deref(),
55        );
56    }
57
58    block_collisions
59}
60
61pub struct BlockCollisionsState<'a> {
62    pub world: &'a World,
63    pub aabb: &'a Aabb,
64    pub entity_shape: VoxelShape,
65    pub cursor: Cursor3d,
66
67    _context: EntityCollisionContext,
68
69    cached_sections: Vec<(ChunkSectionPos, azalea_world::Section)>,
70    cached_block_shapes: Vec<(BlockState, &'static VoxelShape)>,
71}
72
73impl<'a> BlockCollisionsState<'a> {
74    fn compute_next(
75        &mut self,
76        item: CursorIteration,
77        block_collisions: &mut Vec<VoxelShape>,
78        initial_chunk_pos: ChunkPos,
79        initial_chunk: Option<&Chunk>,
80    ) {
81        if item.iteration_type == CursorIterationType::Corner {
82            return;
83        }
84
85        let item_chunk_pos = ChunkPos::from(item.pos);
86        let block_state: BlockState = if item_chunk_pos == initial_chunk_pos {
87            initial_chunk
88                .and_then(|chunk| {
89                    chunk.get_block_state(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
90                })
91                .unwrap_or(BlockState::AIR)
92        } else {
93            self.get_block_state(item.pos)
94        };
95
96        if block_state.is_air() {
97            // fast path since we can't collide with air
98            return;
99        }
100
101        // TODO: if self.only_suffocating_blocks, return if the block isn't suffocating
102
103        // if it's a full block do a faster collision check
104        if block_state.is_collision_shape_full() {
105            if !self.aabb.intersects_aabb(&Aabb {
106                min: item.pos.to_vec3_floored(),
107                max: (item.pos + 1).to_vec3_floored(),
108            }) {
109                return;
110            }
111
112            block_collisions.push(BLOCK_SHAPE.move_relative(item.pos.to_vec3_floored()));
113            return;
114        }
115
116        let block_shape = self.get_block_shape(block_state);
117
118        let block_shape = block_shape.move_relative(item.pos.to_vec3_floored());
119        // if the entity shape and block shape don't collide, continue
120        if !Shapes::matches_anywhere(&block_shape, &self.entity_shape, |a, b| a && b) {
121            return;
122        }
123
124        block_collisions.push(block_shape);
125    }
126
127    pub fn new(world: &'a World, aabb: &'a Aabb, context: EntityCollisionContext) -> Self {
128        let origin = BlockPos {
129            x: (aabb.min.x - EPSILON).floor() as i32 - 1,
130            y: (aabb.min.y - EPSILON).floor() as i32 - 1,
131            z: (aabb.min.z - EPSILON).floor() as i32 - 1,
132        };
133
134        let end = BlockPos {
135            x: (aabb.max.x + EPSILON).floor() as i32 + 1,
136            y: (aabb.max.y + EPSILON).floor() as i32 + 1,
137            z: (aabb.max.z + EPSILON).floor() as i32 + 1,
138        };
139
140        let cursor = Cursor3d::new(origin, end);
141
142        Self {
143            world,
144            aabb,
145            entity_shape: VoxelShape::from(aabb),
146            cursor,
147
148            _context: context,
149
150            cached_sections: Vec::new(),
151            cached_block_shapes: Vec::new(),
152        }
153    }
154
155    fn get_chunk(&self, block_x: i32, block_z: i32) -> Option<Arc<RwLock<Chunk>>> {
156        let chunk_x = ChunkSectionPos::block_to_section_coord(block_x);
157        let chunk_z = ChunkSectionPos::block_to_section_coord(block_z);
158        let chunk_pos = ChunkPos::new(chunk_x, chunk_z);
159
160        // TODO: minecraft caches chunk here
161        // int chunkX = SectionPos.blockToSectionCoord(blockX);
162        // int chunkZ = SectionPos.blockToSectionCoord(blockZ);
163        // long chunkPosLong = ChunkPos.asLong(chunkX, chunkZ);
164        // if (this.cachedBlockGetter != null && this.cachedBlockGetterPos == var5) {
165        //    return this.cachedBlockGetter;
166        // } else {
167        //    BlockGetter var7 = this.collisionGetter.getChunkForCollisions(chunkX,
168        // chunkZ);    this.cachedBlockGetter = var7;
169        //    this.cachedBlockGetterPos = chunkPosLong;
170        //    return var7;
171        // }
172
173        self.world.chunks.get(&chunk_pos)
174    }
175
176    fn get_block_state(&mut self, block_pos: BlockPos) -> BlockState {
177        if block_pos.y < self.world.chunks.min_y {
178            // below the world
179            return BlockState::AIR;
180        }
181
182        let section_pos = ChunkSectionPos::from(block_pos);
183        let section_block_pos = ChunkSectionBlockPos::from(block_pos);
184
185        for (cached_section_pos, cached_section) in &self.cached_sections {
186            if section_pos == *cached_section_pos {
187                return cached_section.get_block_state(section_block_pos);
188            }
189        }
190
191        let chunk = self.get_chunk(block_pos.x, block_pos.z);
192        let Some(chunk) = chunk else {
193            return BlockState::AIR;
194        };
195        let chunk = chunk.read();
196
197        let sections = &chunk.sections;
198        let section_index =
199            azalea_world::chunk_storage::section_index(block_pos.y, self.world.chunks.min_y)
200                as usize;
201
202        let Some(section) = sections.get(section_index) else {
203            return BlockState::AIR;
204        };
205
206        self.cached_sections.push((section_pos, section.clone()));
207
208        section.get_block_state(section_block_pos)
209    }
210
211    fn get_block_shape(&mut self, block_state: BlockState) -> &'static VoxelShape {
212        for (cached_block_state, cached_shape) in &self.cached_block_shapes {
213            if block_state == *cached_block_state {
214                return cached_shape;
215            }
216        }
217
218        let shape = block_state.collision_shape();
219        self.cached_block_shapes.push((block_state, shape));
220
221        shape
222    }
223}
224
225pub struct EntityCollisionContext {
226    pub descending: bool,
227    pub entity_bottom: f64,
228    pub held_item: ItemStack,
229    can_stand_on_fluid_predicate: CanStandOnFluidPredicate,
230    pub entity: Option<Entity>,
231}
232
233impl EntityCollisionContext {
234    pub fn of(entity: Option<Entity>) -> Self {
235        Self {
236            descending: false,
237            entity_bottom: 0.0,
238            held_item: ItemStack::Empty,
239            can_stand_on_fluid_predicate: CanStandOnFluidPredicate::PassToEntity,
240            entity,
241        }
242    }
243    pub fn with_include_liquids(mut self, include_liquids: bool) -> Self {
244        self.can_stand_on_fluid_predicate = if include_liquids {
245            CanStandOnFluidPredicate::AlwaysTrue
246        } else {
247            CanStandOnFluidPredicate::PassToEntity
248        };
249        self
250    }
251
252    pub fn can_stand_on_fluid(&self, above: &FluidState, target: &FluidState) -> bool {
253        self.can_stand_on_fluid_predicate.matches(target) && !above.is_same_kind(target)
254    }
255}
256
257enum CanStandOnFluidPredicate {
258    PassToEntity,
259    AlwaysTrue,
260}
261impl CanStandOnFluidPredicate {
262    pub fn matches(&self, _state: &FluidState) -> bool {
263        match self {
264            Self::AlwaysTrue => true,
265            // minecraft sometimes returns true for striders here, false for every other entity
266            // though
267            Self::PassToEntity => false,
268        }
269    }
270}
271
272/// This basically gets all the chunks that an entity colliding with
273/// that bounding box could be in.
274///
275/// This is forEachAccessibleNonEmptySection in vanilla Minecraft because they
276/// sort entities into sections instead of just chunks. In theory this might be
277/// a performance loss for Azalea. If this ever turns out to be a bottleneck,
278/// then maybe you should try having it do that instead.
279pub fn for_entities_in_chunks_colliding_with(
280    world: &World,
281    aabb: &Aabb,
282    mut consumer: impl FnMut(ChunkPos, &HashSet<Entity>),
283) {
284    let min_section = ChunkSectionPos::from(aabb.min - Vec3::new(2., 4., 2.));
285    let max_section = ChunkSectionPos::from(aabb.max + Vec3::new(2., 0., 2.));
286
287    let min_chunk = ChunkPos::from(min_section);
288    let max_chunk = ChunkPos::from(max_section);
289
290    for chunk_x in min_chunk.x..=max_chunk.x {
291        for chunk_z in min_chunk.z..=max_chunk.z {
292            let chunk_pos = ChunkPos::new(chunk_x, chunk_z);
293            if let Some(entities) = world.entities_by_chunk.get(&chunk_pos) {
294                consumer(chunk_pos, entities);
295            }
296        }
297    }
298}