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 mut movement: Vec3,
263 mut entity_box: AABB,
264 collision_boxes: &[VoxelShape],
265) -> Vec3 {
266 if collision_boxes.is_empty() {
267 return movement;
268 }
269
270 if movement.y != 0. {
271 movement.y = Shapes::collide(Axis::Y, &entity_box, collision_boxes, movement.y);
272 if movement.y != 0. {
273 entity_box = entity_box.move_relative(Vec3::new(0., movement.y, 0.));
274 }
275 }
276
277 let more_z_movement = movement.x.abs() < movement.z.abs();
280
281 if more_z_movement && movement.z != 0. {
282 movement.z = Shapes::collide(Axis::Z, &entity_box, collision_boxes, movement.z);
283 if movement.z != 0. {
284 entity_box = entity_box.move_relative(Vec3::new(0., 0., movement.z));
285 }
286 }
287
288 if movement.x != 0. {
289 movement.x = Shapes::collide(Axis::X, &entity_box, collision_boxes, movement.x);
290 if movement.x != 0. {
291 entity_box = entity_box.move_relative(Vec3::new(movement.x, 0., 0.));
292 }
293 }
294
295 if !more_z_movement && movement.z != 0. {
296 movement.z = Shapes::collide(Axis::Z, &entity_box, collision_boxes, movement.z);
297 }
298
299 movement
300}
301
302pub fn fluid_shape(fluid: &FluidState, world: &ChunkStorage, pos: BlockPos) -> &'static VoxelShape {
307 if fluid.amount == 9 {
308 let fluid_state_above = world.get_fluid_state(pos.up(1)).unwrap_or_default();
309 if fluid_state_above.kind == fluid.kind {
310 return &BLOCK_SHAPE;
311 }
312 }
313 if fluid.amount > 9 {
314 warn!("Tried to calculate shape for fluid with height > 9: {fluid:?} at {pos}");
315 return &EMPTY_SHAPE;
316 }
317
318 static FLUID_SHAPES: LazyLock<[VoxelShape; 10]> = LazyLock::new(|| {
322 [
323 calculate_shape_for_fluid(0),
324 calculate_shape_for_fluid(1),
325 calculate_shape_for_fluid(2),
326 calculate_shape_for_fluid(3),
327 calculate_shape_for_fluid(4),
328 calculate_shape_for_fluid(5),
329 calculate_shape_for_fluid(6),
330 calculate_shape_for_fluid(7),
331 calculate_shape_for_fluid(8),
332 calculate_shape_for_fluid(9),
333 ]
334 });
335
336 &FLUID_SHAPES[fluid.amount as usize]
337}
338fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
339 box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0)
340}
341
342pub fn legacy_blocks_motion(block: BlockState) -> bool {
346 if block == BlockState::AIR {
347 return false;
349 }
350
351 let registry_block = azalea_registry::Block::from(block);
352 legacy_calculate_solid(block)
353 && registry_block != azalea_registry::Block::Cobweb
354 && registry_block != azalea_registry::Block::BambooSapling
355}
356
357pub fn legacy_calculate_solid(block: BlockState) -> bool {
358 let block_trait = Box::<dyn azalea_block::BlockTrait>::from(block);
360 if let Some(solid) = block_trait.behavior().force_solid {
361 return solid;
362 }
363
364 let shape = block.collision_shape();
365 if shape.is_empty() {
366 return false;
367 }
368 let bounds = shape.bounds();
369 bounds.size() >= 0.7291666666666666 || bounds.get_size(Axis::Y) >= 1.0
370}