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, 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 return;
99 }
100
101 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 !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 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 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 Self::PassToEntity => false,
268 }
269 }
270}
271
272pub 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}