azalea_physics/collision/
mod.rs1mod blocks;
2mod discrete_voxel_shape;
3mod mergers;
4mod shape;
5mod world_collisions;
6
7use std::{ops::Add, sync::LazyLock};
8
9use azalea_block::{fluid_state::FluidState, BlockState};
10use azalea_core::{
11 aabb::AABB,
12 direction::Axis,
13 math::EPSILON,
14 position::{BlockPos, Vec3},
15};
16use azalea_world::{ChunkStorage, Instance, MoveEntityError};
17use bevy_ecs::world::Mut;
18pub use blocks::BlockWithShape;
19pub use discrete_voxel_shape::*;
20pub use shape::*;
21use tracing::warn;
22
23use self::world_collisions::get_block_collisions;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum MoverType {
27 Own,
28 Player,
29 Piston,
30 ShulkerBox,
31 Shulker,
32}
33
34fn collide(movement: &Vec3, world: &Instance, physics: &azalea_entity::Physics) -> Vec3 {
64 let entity_bounding_box = physics.bounding_box;
65 let entity_collisions = Vec::new();
69 let collided_delta = if movement.length_squared() == 0.0 {
70 *movement
71 } else {
72 collide_bounding_box(
73 movement,
74 &entity_bounding_box,
75 world,
76 entity_collisions.clone(),
77 )
78 };
79
80 let x_collision = movement.x != collided_delta.x;
81 let y_collision = movement.y != collided_delta.y;
82 let z_collision = movement.z != collided_delta.z;
83
84 let on_ground = physics.on_ground() || y_collision && movement.y < 0.;
85
86 let max_up_step = 0.6;
87 if max_up_step > 0. && on_ground && (x_collision || z_collision) {
88 let mut step_to_delta = collide_bounding_box(
89 &Vec3 {
90 x: movement.x,
91 y: max_up_step,
92 z: movement.z,
93 },
94 &entity_bounding_box,
95 world,
96 entity_collisions.clone(),
97 );
98 let directly_up_delta = collide_bounding_box(
99 &Vec3 {
100 x: 0.,
101 y: max_up_step,
102 z: 0.,
103 },
104 &entity_bounding_box.expand_towards(&Vec3::new(movement.x, 0., movement.z)),
105 world,
106 entity_collisions.clone(),
107 );
108 if directly_up_delta.y < max_up_step {
109 let target_movement = collide_bounding_box(
110 &Vec3 {
111 x: movement.x,
112 y: 0.,
113 z: movement.z,
114 },
115 &entity_bounding_box.move_relative(directly_up_delta),
116 world,
117 entity_collisions.clone(),
118 )
119 .add(directly_up_delta);
120 if target_movement.horizontal_distance_squared()
121 > step_to_delta.horizontal_distance_squared()
122 {
123 step_to_delta = target_movement;
124 }
125 }
126
127 if step_to_delta.horizontal_distance_squared()
128 > collided_delta.horizontal_distance_squared()
129 {
130 return step_to_delta.add(collide_bounding_box(
131 &Vec3 {
132 x: 0.,
133 y: -step_to_delta.y + movement.y,
134 z: 0.,
135 },
136 &entity_bounding_box.move_relative(step_to_delta),
137 world,
138 entity_collisions.clone(),
139 ));
140 }
141 }
142
143 collided_delta
144}
145
146pub fn move_colliding(
150 _mover_type: MoverType,
151 movement: &Vec3,
152 world: &Instance,
153 position: &mut Mut<azalea_entity::Position>,
154 physics: &mut azalea_entity::Physics,
155) -> Result<(), MoveEntityError> {
156 let collide_result = collide(movement, world, physics);
178
179 let move_distance = collide_result.length_squared();
180
181 if move_distance > EPSILON {
182 let new_pos = {
185 Vec3 {
186 x: position.x + collide_result.x,
187 y: position.y + collide_result.y,
188 z: position.z + collide_result.z,
189 }
190 };
191
192 if new_pos != ***position {
193 ***position = new_pos;
194 }
195 }
196
197 let x_collision = movement.x != collide_result.x;
198 let z_collision = movement.z != collide_result.z;
199 let horizontal_collision = x_collision || z_collision;
200 let vertical_collision = movement.y != collide_result.y;
201 let on_ground = vertical_collision && movement.y < 0.;
202
203 physics.horizontal_collision = horizontal_collision;
204 physics.vertical_collision = vertical_collision;
205 physics.set_on_ground(on_ground);
206
207 let _block_pos_below = azalea_entity::on_pos_legacy(&world.chunks, position);
210 if horizontal_collision {
221 let delta_movement = &physics.velocity;
222 physics.velocity = Vec3 {
223 x: if x_collision { 0. } else { delta_movement.x },
224 y: delta_movement.y,
225 z: if z_collision { 0. } else { delta_movement.z },
226 }
227 }
228
229 if vertical_collision {
230 physics.velocity.y = 0.;
234 }
235
236 if on_ground {
237 }
240
241 Ok(())
265}
266
267fn collide_bounding_box(
268 movement: &Vec3,
269 entity_bounding_box: &AABB,
270 world: &Instance,
271 entity_collisions: Vec<VoxelShape>,
272) -> Vec3 {
273 let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1);
274
275 if !entity_collisions.is_empty() {
276 collision_boxes.extend(entity_collisions);
277 }
278
279 let block_collisions =
282 get_block_collisions(world, entity_bounding_box.expand_towards(movement));
283 collision_boxes.extend(block_collisions);
284 collide_with_shapes(movement, *entity_bounding_box, &collision_boxes)
285}
286
287fn collide_with_shapes(
288 movement: &Vec3,
289 mut entity_box: AABB,
290 collision_boxes: &Vec<VoxelShape>,
291) -> Vec3 {
292 if collision_boxes.is_empty() {
293 return *movement;
294 }
295
296 let mut x_movement = movement.x;
297 let mut y_movement = movement.y;
298 let mut z_movement = movement.z;
299 if y_movement != 0. {
300 y_movement = Shapes::collide(&Axis::Y, &entity_box, collision_boxes, y_movement);
301 if y_movement != 0. {
302 entity_box = entity_box.move_relative(Vec3 {
303 x: 0.,
304 y: y_movement,
305 z: 0.,
306 });
307 }
308 }
309
310 let more_z_movement = x_movement.abs() < z_movement.abs();
313
314 if more_z_movement && z_movement != 0. {
315 z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
316 if z_movement != 0. {
317 entity_box = entity_box.move_relative(Vec3 {
318 x: 0.,
319 y: 0.,
320 z: z_movement,
321 });
322 }
323 }
324
325 if x_movement != 0. {
326 x_movement = Shapes::collide(&Axis::X, &entity_box, collision_boxes, x_movement);
327 if x_movement != 0. {
328 entity_box = entity_box.move_relative(Vec3 {
329 x: x_movement,
330 y: 0.,
331 z: 0.,
332 });
333 }
334 }
335
336 if !more_z_movement && z_movement != 0. {
337 z_movement = Shapes::collide(&Axis::Z, &entity_box, collision_boxes, z_movement);
338 }
339
340 Vec3 {
341 x: x_movement,
342 y: y_movement,
343 z: z_movement,
344 }
345}
346
347pub fn fluid_shape(
352 fluid: &FluidState,
353 world: &ChunkStorage,
354 pos: &BlockPos,
355) -> &'static VoxelShape {
356 if fluid.amount == 9 {
357 let fluid_state_above = world.get_fluid_state(&pos.up(1)).unwrap_or_default();
358 if fluid_state_above.kind == fluid.kind {
359 return &BLOCK_SHAPE;
360 }
361 }
362 if fluid.amount > 9 {
363 warn!("Tried to calculate shape for fluid with height > 9: {fluid:?} at {pos}");
364 return &EMPTY_SHAPE;
365 }
366
367 static FLUID_SHAPES: LazyLock<[VoxelShape; 10]> = LazyLock::new(|| {
371 [
372 calculate_shape_for_fluid(0),
373 calculate_shape_for_fluid(1),
374 calculate_shape_for_fluid(2),
375 calculate_shape_for_fluid(3),
376 calculate_shape_for_fluid(4),
377 calculate_shape_for_fluid(5),
378 calculate_shape_for_fluid(6),
379 calculate_shape_for_fluid(7),
380 calculate_shape_for_fluid(8),
381 calculate_shape_for_fluid(9),
382 ]
383 });
384
385 &FLUID_SHAPES[fluid.amount as usize]
386}
387fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
388 box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0)
389}
390
391pub fn legacy_blocks_motion(block: BlockState) -> bool {
395 let registry_block = azalea_registry::Block::from(block);
396 legacy_calculate_solid(block)
397 && registry_block != azalea_registry::Block::Cobweb
398 && registry_block != azalea_registry::Block::BambooSapling
399}
400
401pub fn legacy_calculate_solid(block: BlockState) -> bool {
402 let block_trait = Box::<dyn azalea_block::Block>::from(block);
404 if let Some(solid) = block_trait.behavior().force_solid {
405 return solid;
406 }
407
408 let shape = block.collision_shape();
409 if shape.is_empty() {
410 return false;
411 }
412 let bounds = shape.bounds();
413 bounds.size() >= 0.7291666666666666 || bounds.get_size(Axis::Y) >= 1.0
414}