azalea_physics/collision/
mod.rs1mod blocks;
2mod discrete_voxel_shape;
3pub mod entity_collisions;
4mod mergers;
5mod shape;
6pub mod world_collisions;
7
8use std::{ops::Add, sync::LazyLock};
9
10use azalea_block::{BlockState, fluid_state::FluidState};
11use azalea_core::{
12 aabb::AABB,
13 direction::Axis,
14 math::EPSILON,
15 position::{BlockPos, Vec3},
16};
17use azalea_world::{ChunkStorage, Instance, MoveEntityError};
18use bevy_ecs::{entity::Entity, world::Mut};
19pub use blocks::BlockWithShape;
20pub use discrete_voxel_shape::*;
21use entity_collisions::{CollidableEntityQuery, PhysicsQuery, get_entity_collisions};
22pub use shape::*;
23use tracing::warn;
24
25use self::world_collisions::get_block_collisions;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum MoverType {
29 Own,
30 Player,
31 Piston,
32 ShulkerBox,
33 Shulker,
34}
35
36fn collide(
38 movement: &Vec3,
39 world: &Instance,
40 physics: &azalea_entity::Physics,
41 source_entity: Option<Entity>,
42 physics_query: &PhysicsQuery,
43 collidable_entity_query: &CollidableEntityQuery,
44) -> Vec3 {
45 let entity_bounding_box = physics.bounding_box;
46 let entity_collisions = get_entity_collisions(
47 world,
48 &entity_bounding_box.expand_towards(movement),
49 source_entity,
50 physics_query,
51 collidable_entity_query,
52 );
53 let collided_delta = if movement.length_squared() == 0.0 {
54 *movement
55 } else {
56 collide_bounding_box(movement, &entity_bounding_box, world, &entity_collisions)
57 };
58
59 let x_collision = movement.x != collided_delta.x;
60 let y_collision = movement.y != collided_delta.y;
61 let z_collision = movement.z != collided_delta.z;
62
63 let on_ground = physics.on_ground() || y_collision && movement.y < 0.;
64
65 let max_up_step = 0.6;
66 if max_up_step > 0. && on_ground && (x_collision || z_collision) {
67 let mut step_to_delta = collide_bounding_box(
68 &movement.with_y(max_up_step),
69 &entity_bounding_box,
70 world,
71 &entity_collisions,
72 );
73 let directly_up_delta = collide_bounding_box(
74 &Vec3::ZERO.with_y(max_up_step),
75 &entity_bounding_box.expand_towards(&Vec3::new(movement.x, 0., movement.z)),
76 world,
77 &entity_collisions,
78 );
79 if directly_up_delta.y < max_up_step {
80 let target_movement = collide_bounding_box(
81 &movement.with_y(0.),
82 &entity_bounding_box.move_relative(directly_up_delta),
83 world,
84 &entity_collisions,
85 )
86 .add(directly_up_delta);
87 if target_movement.horizontal_distance_squared()
88 > step_to_delta.horizontal_distance_squared()
89 {
90 step_to_delta = target_movement;
91 }
92 }
93
94 if step_to_delta.horizontal_distance_squared()
95 > collided_delta.horizontal_distance_squared()
96 {
97 return step_to_delta.add(collide_bounding_box(
98 &Vec3::ZERO.with_y(-step_to_delta.y + movement.y),
99 &entity_bounding_box.move_relative(step_to_delta),
100 world,
101 &entity_collisions,
102 ));
103 }
104 }
105
106 collided_delta
107}
108
109#[allow(clippy::too_many_arguments)]
113pub fn move_colliding(
114 _mover_type: MoverType,
115 movement: &Vec3,
116 world: &Instance,
117 position: &mut Mut<azalea_entity::Position>,
118 physics: &mut azalea_entity::Physics,
119 source_entity: Option<Entity>,
120 physics_query: &PhysicsQuery,
121 collidable_entity_query: &CollidableEntityQuery,
122) -> Result<(), MoveEntityError> {
123 let collide_result = collide(
145 movement,
146 world,
147 physics,
148 source_entity,
149 physics_query,
150 collidable_entity_query,
151 );
152
153 let move_distance = collide_result.length_squared();
154
155 if move_distance > EPSILON {
156 let new_pos = {
159 Vec3 {
160 x: position.x + collide_result.x,
161 y: position.y + collide_result.y,
162 z: position.z + collide_result.z,
163 }
164 };
165
166 if new_pos != ***position {
167 ***position = new_pos;
168 }
169 }
170
171 let x_collision = movement.x != collide_result.x;
172 let z_collision = movement.z != collide_result.z;
173 let horizontal_collision = x_collision || z_collision;
174 let vertical_collision = movement.y != collide_result.y;
175 let on_ground = vertical_collision && movement.y < 0.;
176
177 physics.horizontal_collision = horizontal_collision;
178 physics.vertical_collision = vertical_collision;
179 physics.set_on_ground(on_ground);
180
181 let _block_pos_below = azalea_entity::on_pos_legacy(&world.chunks, position);
184 if horizontal_collision {
195 let delta_movement = &physics.velocity;
196 physics.velocity = Vec3 {
197 x: if x_collision { 0. } else { delta_movement.x },
198 y: delta_movement.y,
199 z: if z_collision { 0. } else { delta_movement.z },
200 }
201 }
202
203 if vertical_collision {
204 physics.velocity.y = 0.;
208 }
209
210 if on_ground {
211 }
214
215 Ok(())
239}
240
241fn collide_bounding_box(
242 movement: &Vec3,
243 entity_bounding_box: &AABB,
244 world: &Instance,
245 entity_collisions: &[VoxelShape],
246) -> Vec3 {
247 let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1);
248
249 if !entity_collisions.is_empty() {
250 collision_boxes.extend_from_slice(entity_collisions);
251 }
252
253 let block_collisions =
256 get_block_collisions(world, &entity_bounding_box.expand_towards(movement));
257 collision_boxes.extend(block_collisions);
258 collide_with_shapes(movement, *entity_bounding_box, &collision_boxes)
259}
260
261fn collide_with_shapes(
262 movement: &Vec3,
263 mut entity_box: AABB,
264 collision_boxes: &Vec<VoxelShape>,
265) -> Vec3 {
266 if collision_boxes.is_empty() {
267 return *movement;
268 }
269
270 let mut x_movement = movement.x;
271 let mut y_movement = movement.y;
272 let mut z_movement = movement.z;
273 if y_movement != 0. {
274 y_movement = Shapes::collide(&Axis::Y, &entity_box, collision_boxes, y_movement);
275 if y_movement != 0. {
276 entity_box = entity_box.move_relative(Vec3 {
277 x: 0.,
278 y: y_movement,
279 z: 0.,
280 });
281 }
282 }
283
284 let more_z_movement = x_movement.abs() < z_movement.abs();
287
288 if more_z_movement && z_movement != 0. {
289 z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
290 if z_movement != 0. {
291 entity_box = entity_box.move_relative(Vec3 {
292 x: 0.,
293 y: 0.,
294 z: z_movement,
295 });
296 }
297 }
298
299 if x_movement != 0. {
300 x_movement = Shapes::collide(&Axis::X, &entity_box, collision_boxes, x_movement);
301 if x_movement != 0. {
302 entity_box = entity_box.move_relative(Vec3 {
303 x: x_movement,
304 y: 0.,
305 z: 0.,
306 });
307 }
308 }
309
310 if !more_z_movement && z_movement != 0. {
311 z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
312 }
313
314 Vec3 {
315 x: x_movement,
316 y: y_movement,
317 z: z_movement,
318 }
319}
320
321pub fn fluid_shape(
326 fluid: &FluidState,
327 world: &ChunkStorage,
328 pos: &BlockPos,
329) -> &'static VoxelShape {
330 if fluid.amount == 9 {
331 let fluid_state_above = world.get_fluid_state(&pos.up(1)).unwrap_or_default();
332 if fluid_state_above.kind == fluid.kind {
333 return &BLOCK_SHAPE;
334 }
335 }
336 if fluid.amount > 9 {
337 warn!("Tried to calculate shape for fluid with height > 9: {fluid:?} at {pos}");
338 return &EMPTY_SHAPE;
339 }
340
341 static FLUID_SHAPES: LazyLock<[VoxelShape; 10]> = LazyLock::new(|| {
345 [
346 calculate_shape_for_fluid(0),
347 calculate_shape_for_fluid(1),
348 calculate_shape_for_fluid(2),
349 calculate_shape_for_fluid(3),
350 calculate_shape_for_fluid(4),
351 calculate_shape_for_fluid(5),
352 calculate_shape_for_fluid(6),
353 calculate_shape_for_fluid(7),
354 calculate_shape_for_fluid(8),
355 calculate_shape_for_fluid(9),
356 ]
357 });
358
359 &FLUID_SHAPES[fluid.amount as usize]
360}
361fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
362 box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0)
363}
364
365pub fn legacy_blocks_motion(block: BlockState) -> bool {
369 if block == BlockState::AIR {
370 return false;
372 }
373
374 let registry_block = azalea_registry::Block::from(block);
375 legacy_calculate_solid(block)
376 && registry_block != azalea_registry::Block::Cobweb
377 && registry_block != azalea_registry::Block::BambooSapling
378}
379
380pub fn legacy_calculate_solid(block: BlockState) -> bool {
381 let block_trait = Box::<dyn azalea_block::Block>::from(block);
383 if let Some(solid) = block_trait.behavior().force_solid {
384 return solid;
385 }
386
387 let shape = block.collision_shape();
388 if shape.is_empty() {
389 return false;
390 }
391 let bounds = shape.bounds();
392 bounds.size() >= 0.7291666666666666 || bounds.get_size(Axis::Y) >= 1.0
393}