azalea_physics/collision/
world_collisions.rs1use 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, Instance};
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: &Instance, 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: &Instance, 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 Instance,
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 match &initial_chunk {
88 Some(initial_chunk) => initial_chunk
89 .get(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y)
90 .unwrap_or(BlockState::AIR),
91 _ => BlockState::AIR,
92 }
93 } else {
94 self.get_block_state(item.pos)
95 };
96
97 if block_state.is_air() {
98 return;
100 }
101
102 if block_state.is_collision_shape_full() {
107 if !self.aabb.intersects_aabb(&AABB {
108 min: item.pos.to_vec3_floored(),
109 max: (item.pos + 1).to_vec3_floored(),
110 }) {
111 return;
112 }
113
114 block_collisions.push(BLOCK_SHAPE.move_relative(item.pos.to_vec3_floored()));
115 return;
116 }
117
118 let block_shape = self.get_block_shape(block_state);
119
120 let block_shape = block_shape.move_relative(item.pos.to_vec3_floored());
121 if !Shapes::matches_anywhere(&block_shape, &self.entity_shape, |a, b| a && b) {
123 return;
124 }
125
126 block_collisions.push(block_shape);
127 }
128
129 pub fn new(world: &'a Instance, aabb: &'a AABB, context: EntityCollisionContext) -> Self {
130 let origin = BlockPos {
131 x: (aabb.min.x - EPSILON).floor() as i32 - 1,
132 y: (aabb.min.y - EPSILON).floor() as i32 - 1,
133 z: (aabb.min.z - EPSILON).floor() as i32 - 1,
134 };
135
136 let end = BlockPos {
137 x: (aabb.max.x + EPSILON).floor() as i32 + 1,
138 y: (aabb.max.y + EPSILON).floor() as i32 + 1,
139 z: (aabb.max.z + EPSILON).floor() as i32 + 1,
140 };
141
142 let cursor = Cursor3d::new(origin, end);
143
144 Self {
145 world,
146 aabb,
147 entity_shape: VoxelShape::from(aabb),
148 cursor,
149
150 _context: context,
151
152 cached_sections: Vec::new(),
153 cached_block_shapes: Vec::new(),
154 }
155 }
156
157 fn get_chunk(&self, block_x: i32, block_z: i32) -> Option<Arc<RwLock<Chunk>>> {
158 let chunk_x = ChunkSectionPos::block_to_section_coord(block_x);
159 let chunk_z = ChunkSectionPos::block_to_section_coord(block_z);
160 let chunk_pos = ChunkPos::new(chunk_x, chunk_z);
161
162 self.world.chunks.get(&chunk_pos)
176 }
177
178 fn get_block_state(&mut self, block_pos: BlockPos) -> BlockState {
179 if block_pos.y < self.world.chunks.min_y {
180 return BlockState::AIR;
182 }
183
184 let section_pos = ChunkSectionPos::from(block_pos);
185 let section_block_pos = ChunkSectionBlockPos::from(block_pos);
186
187 for (cached_section_pos, cached_section) in &self.cached_sections {
188 if section_pos == *cached_section_pos {
189 return cached_section.get(section_block_pos);
190 }
191 }
192
193 let chunk = self.get_chunk(block_pos.x, block_pos.z);
194 let Some(chunk) = chunk else {
195 return BlockState::AIR;
196 };
197 let chunk = chunk.read();
198
199 let sections = &chunk.sections;
200 let section_index =
201 azalea_world::chunk_storage::section_index(block_pos.y, self.world.chunks.min_y)
202 as usize;
203
204 let Some(section) = sections.get(section_index) else {
205 return BlockState::AIR;
206 };
207
208 self.cached_sections.push((section_pos, section.clone()));
209
210 section.get(section_block_pos)
215 }
216
217 fn get_block_shape(&mut self, block_state: BlockState) -> &'static VoxelShape {
218 for (cached_block_state, cached_shape) in &self.cached_block_shapes {
219 if block_state == *cached_block_state {
220 return cached_shape;
221 }
222 }
223
224 let shape = block_state.collision_shape();
225 self.cached_block_shapes.push((block_state, shape));
226
227 shape
228 }
229}
230
231pub struct EntityCollisionContext {
232 pub descending: bool,
233 pub entity_bottom: f64,
234 pub held_item: ItemStack,
235 can_stand_on_fluid_predicate: CanStandOnFluidPredicate,
236 pub entity: Option<Entity>,
237}
238
239impl EntityCollisionContext {
240 pub fn of(entity: Option<Entity>) -> Self {
241 Self {
242 descending: false,
243 entity_bottom: 0.0,
244 held_item: ItemStack::Empty,
245 can_stand_on_fluid_predicate: CanStandOnFluidPredicate::PassToEntity,
246 entity,
247 }
248 }
249 pub fn with_include_liquids(mut self, include_liquids: bool) -> Self {
250 self.can_stand_on_fluid_predicate = if include_liquids {
251 CanStandOnFluidPredicate::AlwaysTrue
252 } else {
253 CanStandOnFluidPredicate::PassToEntity
254 };
255 self
256 }
257
258 pub fn can_stand_on_fluid(&self, above: &FluidState, target: &FluidState) -> bool {
259 self.can_stand_on_fluid_predicate.matches(target) && !above.is_same_kind(target)
260 }
261}
262
263enum CanStandOnFluidPredicate {
264 PassToEntity,
265 AlwaysTrue,
266}
267impl CanStandOnFluidPredicate {
268 pub fn matches(&self, _state: &FluidState) -> bool {
269 match self {
270 Self::AlwaysTrue => true,
271 Self::PassToEntity => false,
274 }
275 }
276}
277
278pub fn for_entities_in_chunks_colliding_with(
286 world: &Instance,
287 aabb: &AABB,
288 mut consumer: impl FnMut(ChunkPos, &HashSet<Entity>),
289) {
290 let min_section = ChunkSectionPos::from(aabb.min - Vec3::new(2., 4., 2.));
291 let max_section = ChunkSectionPos::from(aabb.max + Vec3::new(2., 0., 2.));
292
293 let min_chunk = ChunkPos::from(min_section);
294 let max_chunk = ChunkPos::from(max_section);
295
296 for chunk_x in min_chunk.x..=max_chunk.x {
297 for chunk_z in min_chunk.z..=max_chunk.z {
298 let chunk_pos = ChunkPos::new(chunk_x, chunk_z);
299 if let Some(entities) = world.entities_by_chunk.get(&chunk_pos) {
300 consumer(chunk_pos, entities);
301 }
302 }
303 }
304}