From b0bd992adcff71ee294dd05060e00e652f62a7b2 Mon Sep 17 00:00:00 2001 From: mat <27899617+mat-1@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:41:17 -0500 Subject: Fluid physics fixes (#210) * start fixing code related to fluid physics * implement force_solid for blocks * afk pool test --- azalea-physics/src/clip.rs | 11 -- azalea-physics/src/collision/entity_collisions.rs | 97 ++++++++++++ azalea-physics/src/collision/mod.rs | 10 +- azalea-physics/src/collision/shape.rs | 11 +- azalea-physics/src/collision/world_collisions.rs | 182 ++++++++++++++++++---- azalea-physics/src/fluids.rs | 68 ++++---- azalea-physics/src/lib.rs | 63 ++++---- azalea-physics/src/travel.rs | 131 ++++++++++++++-- 8 files changed, 446 insertions(+), 127 deletions(-) create mode 100644 azalea-physics/src/collision/entity_collisions.rs (limited to 'azalea-physics/src') diff --git a/azalea-physics/src/clip.rs b/azalea-physics/src/clip.rs index ea5940a5..7c16f5d4 100644 --- a/azalea-physics/src/clip.rs +++ b/azalea-physics/src/clip.rs @@ -11,9 +11,7 @@ use azalea_core::{ math::{self, EPSILON, lerp}, position::{BlockPos, Vec3}, }; -use azalea_inventory::ItemStack; use azalea_world::ChunkStorage; -use bevy_ecs::entity::Entity; use crate::collision::{BlockWithShape, EMPTY_SHAPE, VoxelShape}; @@ -92,15 +90,6 @@ impl FluidPickType { } } -#[derive(Debug, Clone)] -pub struct EntityCollisionContext { - pub descending: bool, - pub entity_bottom: f64, - pub held_item: ItemStack, - // pub can_stand_on_fluid: Box bool>, - pub entity: Entity, -} - pub fn clip(chunk_storage: &ChunkStorage, context: ClipContext) -> BlockHitResult { traverse_blocks( context.from, diff --git a/azalea-physics/src/collision/entity_collisions.rs b/azalea-physics/src/collision/entity_collisions.rs new file mode 100644 index 00000000..1300cf34 --- /dev/null +++ b/azalea-physics/src/collision/entity_collisions.rs @@ -0,0 +1,97 @@ +use azalea_core::aabb::AABB; +use azalea_entity::{ + LocalEntity, Physics, + metadata::{AbstractBoat, Shulker}, +}; +use azalea_world::Instance; +use bevy_ecs::{ + entity::Entity, + query::{Or, With, Without}, + system::Query, +}; +use tracing::error; + +use super::VoxelShape; + +/// This query matches on entities that we can collide with. That is, boats and +/// shulkers. +/// +/// If you want to use this in a more complex query, use +/// [`CollidableEntityFilter`] as a filter instead. +pub type CollidableEntityQuery<'world, 'state> = Query<'world, 'state, (), CollidableEntityFilter>; +/// This filter matches on entities that we can collide with (boats and +/// shulkers). +/// +/// Use [`CollidableEntityQuery`] if you want an empty query that matches with +/// this. +pub type CollidableEntityFilter = Or<(With, With)>; + +pub type PhysicsQuery<'world, 'state, 'a> = + Query<'world, 'state, &'a Physics, Without>; + +pub fn get_entity_collisions( + world: &Instance, + aabb: &AABB, + source_entity: Option, + physics_query: &PhysicsQuery, + collidable_entity_query: &CollidableEntityQuery, +) -> Vec { + if aabb.size() < 1.0E-7 { + return vec![]; + } + + let collision_predicate = |entity| collidable_entity_query.get(entity).is_ok(); + + let collidable_entities = get_entities( + world, + source_entity, + &aabb.inflate_all(1.0E-7), + &collision_predicate, + physics_query, + ); + + collidable_entities + .into_iter() + .map(|(_entity, aabb)| VoxelShape::from(aabb)) + .collect() +} + +/// Return all entities that are colliding with the given bounding box and match +/// the given predicate. +/// +/// `source_entity` is the entity that the bounding box belongs to, and won't be +/// one of the returned entities. +pub fn get_entities( + world: &Instance, + source_entity: Option, + aabb: &AABB, + predicate: &dyn Fn(Entity) -> bool, + physics_query: &PhysicsQuery, +) -> Vec<(Entity, AABB)> { + let mut matches = Vec::new(); + + super::world_collisions::for_entities_in_chunks_colliding_with( + world, + aabb, + |_chunk_pos, entities_in_chunk| { + // now check if the entity itself collides + for &candidate in entities_in_chunk { + if Some(candidate) != source_entity && predicate(candidate) { + let Ok(physics) = physics_query.get(candidate) else { + error!( + "Entity {candidate} (found from for_entities_in_chunks_colliding_with) is missing required components." + ); + continue; + }; + + let candidate_aabb = physics.bounding_box; + if aabb.intersects_aabb(&candidate_aabb) { + matches.push((candidate, physics.bounding_box)); + } + } + } + }, + ); + + matches +} diff --git a/azalea-physics/src/collision/mod.rs b/azalea-physics/src/collision/mod.rs index 540cf7d4..77af1232 100644 --- a/azalea-physics/src/collision/mod.rs +++ b/azalea-physics/src/collision/mod.rs @@ -1,8 +1,9 @@ mod blocks; mod discrete_voxel_shape; +pub mod entity_collisions; mod mergers; mod shape; -mod world_collisions; +pub mod world_collisions; use std::{ops::Add, sync::LazyLock}; @@ -279,7 +280,7 @@ fn collide_bounding_box( // TODO: world border let block_collisions = - get_block_collisions(world, entity_bounding_box.expand_towards(movement)); + get_block_collisions(world, &entity_bounding_box.expand_towards(movement)); collision_boxes.extend(block_collisions); collide_with_shapes(movement, *entity_bounding_box, &collision_boxes) } @@ -392,6 +393,11 @@ fn calculate_shape_for_fluid(amount: u8) -> VoxelShape { /// /// This is marked as deprecated in Minecraft. pub fn legacy_blocks_motion(block: BlockState) -> bool { + if block == BlockState::AIR { + // fast path + return false; + } + let registry_block = azalea_registry::Block::from(block); legacy_calculate_solid(block) && registry_block != azalea_registry::Block::Cobweb diff --git a/azalea-physics/src/collision/shape.rs b/azalea-physics/src/collision/shape.rs index 726e62ad..fc5615c3 100755 --- a/azalea-physics/src/collision/shape.rs +++ b/azalea-physics/src/collision/shape.rs @@ -194,7 +194,7 @@ impl Shapes { } /// Check if the op is true anywhere when joining the two shapes - /// vanilla calls this joinIsNotEmpty + /// vanilla calls this joinIsNotEmpty (join_is_not_empty). pub fn matches_anywhere( a: &VoxelShape, b: &VoxelShape, @@ -574,13 +574,18 @@ impl VoxelShape { } } -impl From for VoxelShape { - fn from(aabb: AABB) -> Self { +impl From<&AABB> for VoxelShape { + fn from(aabb: &AABB) -> Self { box_shape( aabb.min.x, aabb.min.y, aabb.min.z, aabb.max.x, aabb.max.y, aabb.max.z, ) } } +impl From for VoxelShape { + fn from(aabb: AABB) -> Self { + VoxelShape::from(&aabb) + } +} #[derive(Clone, PartialEq, Debug)] pub struct ArrayVoxelShape { diff --git a/azalea-physics/src/collision/world_collisions.rs b/azalea-physics/src/collision/world_collisions.rs index 3aede743..ded31275 100644 --- a/azalea-physics/src/collision/world_collisions.rs +++ b/azalea-physics/src/collision/world_collisions.rs @@ -1,19 +1,21 @@ -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; -use azalea_block::BlockState; +use azalea_block::{BlockState, fluid_state::FluidState}; use azalea_core::{ - cursor3d::{Cursor3d, CursorIterationType}, + cursor3d::{Cursor3d, CursorIteration, CursorIterationType}, math::EPSILON, - position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos}, + position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos, Vec3}, }; +use azalea_inventory::ItemStack; use azalea_world::{Chunk, Instance}; +use bevy_ecs::entity::Entity; use parking_lot::RwLock; use super::{BLOCK_SHAPE, Shapes}; use crate::collision::{AABB, BlockWithShape, VoxelShape}; -pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec { - let mut state = BlockCollisionsState::new(world, aabb); +pub fn get_block_collisions(world: &Instance, aabb: &AABB) -> Vec { + let mut state = BlockCollisionsState::new(world, aabb, EntityCollisionContext::of(None)); let mut block_collisions = Vec::new(); let initial_chunk_pos = ChunkPos::from(state.cursor.origin()); @@ -21,25 +23,80 @@ pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec { let initial_chunk = initial_chunk.as_deref().map(RwLock::read); while let Some(item) = state.cursor.next() { + state.compute_next( + item, + &mut block_collisions, + initial_chunk_pos, + initial_chunk.as_deref(), + ); + } + + block_collisions +} + +pub fn get_block_and_liquid_collisions(world: &Instance, aabb: &AABB) -> Vec { + let mut state = BlockCollisionsState::new( + world, + aabb, + EntityCollisionContext::of(None).with_include_liquids(true), + ); + let mut block_collisions = Vec::new(); + + let initial_chunk_pos = ChunkPos::from(state.cursor.origin()); + let initial_chunk = world.chunks.get(&initial_chunk_pos); + let initial_chunk = initial_chunk.as_deref().map(RwLock::read); + + while let Some(item) = state.cursor.next() { + state.compute_next( + item, + &mut block_collisions, + initial_chunk_pos, + initial_chunk.as_deref(), + ); + } + + block_collisions +} + +pub struct BlockCollisionsState<'a> { + pub world: &'a Instance, + pub aabb: &'a AABB, + pub entity_shape: VoxelShape, + pub cursor: Cursor3d, + + _context: EntityCollisionContext, + + cached_sections: Vec<(ChunkSectionPos, azalea_world::Section)>, + cached_block_shapes: Vec<(BlockState, &'static VoxelShape)>, +} + +impl<'a> BlockCollisionsState<'a> { + fn compute_next( + &mut self, + item: CursorIteration, + block_collisions: &mut Vec, + initial_chunk_pos: ChunkPos, + initial_chunk: Option<&Chunk>, + ) { if item.iteration_type == CursorIterationType::Corner { - continue; + return; } let item_chunk_pos = ChunkPos::from(item.pos); let block_state: BlockState = if item_chunk_pos == initial_chunk_pos { match &initial_chunk { Some(initial_chunk) => initial_chunk - .get(&ChunkBlockPos::from(item.pos), state.world.chunks.min_y) + .get(&ChunkBlockPos::from(item.pos), self.world.chunks.min_y) .unwrap_or(BlockState::AIR), _ => BlockState::AIR, } } else { - state.get_block_state(item.pos) + self.get_block_state(item.pos) }; if block_state.is_air() { // fast path since we can't collide with air - continue; + return; } // TODO: continue if self.only_suffocating_blocks and the block is not @@ -47,43 +104,29 @@ pub fn get_block_collisions(world: &Instance, aabb: AABB) -> Vec { // if it's a full block do a faster collision check if block_state.is_collision_shape_full() { - if !state.aabb.intersects_aabb(&AABB { + if !self.aabb.intersects_aabb(&AABB { min: item.pos.to_vec3_floored(), max: (item.pos + 1).to_vec3_floored(), }) { - continue; + return; } block_collisions.push(BLOCK_SHAPE.move_relative(item.pos.to_vec3_floored())); - continue; + return; } - let block_shape = state.get_block_shape(block_state); + let block_shape = self.get_block_shape(block_state); let block_shape = block_shape.move_relative(item.pos.to_vec3_floored()); // if the entity shape and block shape don't collide, continue - if !Shapes::matches_anywhere(&block_shape, &state.entity_shape, |a, b| a && b) { - continue; + if !Shapes::matches_anywhere(&block_shape, &self.entity_shape, |a, b| a && b) { + return; } block_collisions.push(block_shape); } - block_collisions -} - -pub struct BlockCollisionsState<'a> { - pub world: &'a Instance, - pub aabb: AABB, - pub entity_shape: VoxelShape, - pub cursor: Cursor3d, - - cached_sections: Vec<(ChunkSectionPos, azalea_world::Section)>, - cached_block_shapes: Vec<(BlockState, &'static VoxelShape)>, -} - -impl<'a> BlockCollisionsState<'a> { - pub fn new(world: &'a Instance, aabb: AABB) -> Self { + pub fn new(world: &'a Instance, aabb: &'a AABB, context: EntityCollisionContext) -> Self { let origin = BlockPos { x: (aabb.min.x - EPSILON).floor() as i32 - 1, y: (aabb.min.y - EPSILON).floor() as i32 - 1, @@ -104,6 +147,8 @@ impl<'a> BlockCollisionsState<'a> { entity_shape: VoxelShape::from(aabb), cursor, + _context: context, + cached_sections: Vec::new(), cached_block_shapes: Vec::new(), } @@ -182,3 +227,78 @@ impl<'a> BlockCollisionsState<'a> { shape } } + +pub struct EntityCollisionContext { + pub descending: bool, + pub entity_bottom: f64, + pub held_item: ItemStack, + can_stand_on_fluid_predicate: CanStandOnFluidPredicate, + pub entity: Option, +} + +impl EntityCollisionContext { + pub fn of(entity: Option) -> Self { + Self { + descending: false, + entity_bottom: 0.0, + held_item: ItemStack::Empty, + can_stand_on_fluid_predicate: CanStandOnFluidPredicate::PassToEntity, + entity, + } + } + pub fn with_include_liquids(mut self, include_liquids: bool) -> Self { + self.can_stand_on_fluid_predicate = if include_liquids { + CanStandOnFluidPredicate::AlwaysTrue + } else { + CanStandOnFluidPredicate::PassToEntity + }; + self + } + + pub fn can_stand_on_fluid(&self, above: &FluidState, target: &FluidState) -> bool { + self.can_stand_on_fluid_predicate.matches(target) && !above.is_same_kind(target) + } +} + +enum CanStandOnFluidPredicate { + PassToEntity, + AlwaysTrue, +} +impl CanStandOnFluidPredicate { + pub fn matches(&self, _state: &FluidState) -> bool { + match self { + Self::AlwaysTrue => true, + // minecraft sometimes returns true for striders here, false for every other entity + // though + Self::PassToEntity => false, + } + } +} + +/// This basically gets all the chunks that an entity colliding with +/// that bounding box could be in. +/// +/// This is forEachAccessibleNonEmptySection in vanilla Minecraft because they +/// sort entities into sections instead of just chunks. In theory this might be +/// a performance loss for Azalea. If this ever turns out to be a bottleneck, +/// then maybe you should try having it do that instead. +pub fn for_entities_in_chunks_colliding_with( + world: &Instance, + aabb: &AABB, + mut consumer: impl FnMut(ChunkPos, &HashSet), +) { + let min_section = ChunkSectionPos::from(aabb.min - Vec3::new(2., 4., 2.)); + let max_section = ChunkSectionPos::from(aabb.max + Vec3::new(2., 0., 2.)); + + let min_chunk = ChunkPos::from(min_section); + let max_chunk = ChunkPos::from(max_section); + + for chunk_x in min_chunk.x..=max_chunk.x { + for chunk_z in min_chunk.z..=max_chunk.z { + let chunk_pos = ChunkPos::new(chunk_x, chunk_z); + if let Some(entities) = world.entities_by_chunk.get(&chunk_pos) { + consumer(chunk_pos, entities); + } + } + } +} diff --git a/azalea-physics/src/fluids.rs b/azalea-physics/src/fluids.rs index bbc044f6..f8643b9c 100644 --- a/azalea-physics/src/fluids.rs +++ b/azalea-physics/src/fluids.rs @@ -27,6 +27,8 @@ pub fn update_in_water_state_and_do_fluid_pushing( .expect("All entities with InLoadedChunk should be in a valid world"); let world = world_lock.read(); + // reset the heights since they're going to be set in + // update_in_water_state_and_do_water_current_pushing physics.water_fluid_height = 0.; physics.lava_fluid_height = 0.; @@ -110,9 +112,9 @@ fn update_fluid_height_and_do_fluid_pushing( let mut additional_player_delta = Vec3::default(); let mut num_fluids_being_touched = 0; - for cur_x in min_x..=max_x { - for cur_y in min_y..=max_y { - for cur_z in min_z..=max_z { + for cur_x in min_x..max_x { + for cur_y in min_y..max_y { + for cur_z in min_z..max_z { let cur_pos = BlockPos::new(cur_x, cur_y, cur_z); let Some(fluid_at_cur_pos) = world.get_fluid_state(&cur_pos) else { continue; @@ -184,42 +186,44 @@ pub fn get_fluid_flow(fluid: &FluidState, world: &Instance, pos: BlockPos) -> Ve let mut z_flow: f64 = 0.; let mut x_flow: f64 = 0.; + let cur_fluid_height = fluid.height(); + for direction in Direction::HORIZONTAL { let adjacent_block_pos = pos.offset_with_direction(direction); - let adjacent_fluid_state = world - .get_fluid_state(&adjacent_block_pos) + + let adjacent_block_state = world + .get_block_state(&adjacent_block_pos) .unwrap_or_default(); - if fluid.affects_flow(&adjacent_fluid_state) { - let mut adjacent_fluid_height = adjacent_fluid_state.height(); - let mut adjacent_height_difference: f32 = 0.; - - if adjacent_fluid_height == 0. { - if !legacy_blocks_motion( - world - .get_block_state(&adjacent_block_pos) - .unwrap_or_default(), - ) { - let block_pos_below_adjacent = adjacent_block_pos.down(1); - let fluid_below_adjacent = world - .get_fluid_state(&block_pos_below_adjacent) - .unwrap_or_default(); - - if fluid.affects_flow(&fluid_below_adjacent) { - adjacent_fluid_height = fluid_below_adjacent.height(); - if adjacent_fluid_height > 0. { - adjacent_height_difference = - fluid.height() - (adjacent_fluid_height - 0.8888889); - } + let adjacent_fluid_state = FluidState::from(adjacent_block_state); + + if !fluid.affects_flow(&adjacent_fluid_state) { + continue; + }; + let mut adjacent_fluid_height = adjacent_fluid_state.height(); + let mut adjacent_height_difference: f32 = 0.; + + if adjacent_fluid_height == 0. { + if !legacy_blocks_motion(adjacent_block_state) { + let block_pos_below_adjacent = adjacent_block_pos.down(1); + let fluid_below_adjacent = world + .get_fluid_state(&block_pos_below_adjacent) + .unwrap_or_default(); + + if fluid.affects_flow(&fluid_below_adjacent) { + adjacent_fluid_height = fluid_below_adjacent.height(); + if adjacent_fluid_height > 0. { + adjacent_height_difference = + cur_fluid_height - (adjacent_fluid_height - 0.8888889); } } - } else if adjacent_fluid_height > 0. { - adjacent_height_difference = fluid.height() - adjacent_fluid_height; } + } else if adjacent_fluid_height > 0. { + adjacent_height_difference = cur_fluid_height - adjacent_fluid_height; + } - if adjacent_height_difference != 0. { - x_flow += (direction.x() as f32 * adjacent_height_difference) as f64; - z_flow += (direction.z() as f32 * adjacent_height_difference) as f64; - } + if adjacent_height_difference != 0. { + x_flow += (direction.x() as f32 * adjacent_height_difference) as f64; + z_flow += (direction.z() as f32 * adjacent_height_difference) as f64; } } diff --git a/azalea-physics/src/lib.rs b/azalea-physics/src/lib.rs index 0541042c..2e8132c8 100644 --- a/azalea-physics/src/lib.rs +++ b/azalea-physics/src/lib.rs @@ -90,44 +90,40 @@ pub fn ai_step( physics.velocity.z = 0.; } - if let Some(jumping) = jumping { - if **jumping { - // TODO: jumping in liquids and jump delay - - let fluid_height = if physics.is_in_lava() { - physics.lava_fluid_height - } else if physics.is_in_water() { - physics.water_fluid_height - } else { - 0. - }; - - let in_water = physics.is_in_water() && fluid_height > 0.; - let fluid_jump_threshold = travel::fluid_jump_threshold(); - - if !in_water || physics.on_ground() && fluid_height <= fluid_jump_threshold { - if !physics.is_in_lava() - || physics.on_ground() && fluid_height <= fluid_jump_threshold + if jumping == Some(&Jumping(true)) { + let fluid_height = if physics.is_in_lava() { + physics.lava_fluid_height + } else if physics.is_in_water() { + physics.water_fluid_height + } else { + 0. + }; + + let in_water = physics.is_in_water() && fluid_height > 0.; + let fluid_jump_threshold = travel::fluid_jump_threshold(); + + if !in_water || physics.on_ground() && fluid_height <= fluid_jump_threshold { + if !physics.is_in_lava() + || physics.on_ground() && fluid_height <= fluid_jump_threshold + { + if (physics.on_ground() || in_water && fluid_height <= fluid_jump_threshold) + && physics.no_jump_delay == 0 { - if (physics.on_ground() || in_water && fluid_height <= fluid_jump_threshold) - && physics.no_jump_delay == 0 - { - jump_from_ground( - &mut physics, - position, - look_direction, - sprinting, - instance_name, - &instance_container, - ); - physics.no_jump_delay = 10; - } - } else { - jump_in_liquid(&mut physics); + jump_from_ground( + &mut physics, + position, + look_direction, + sprinting, + instance_name, + &instance_container, + ); + physics.no_jump_delay = 10; } } else { jump_in_liquid(&mut physics); } + } else { + jump_in_liquid(&mut physics); } } else { physics.no_jump_delay = 0; @@ -417,7 +413,6 @@ fn handle_relative_friction_and_calculate_movement( .unwrap_or_default() .into(); - // TODO: powdered snow if **on_climbable || block_at_feet == azalea_registry::Block::PowderSnow { physics.velocity.y = 0.2; } diff --git a/azalea-physics/src/travel.rs b/azalea-physics/src/travel.rs index e6145c18..0ee39f73 100644 --- a/azalea-physics/src/travel.rs +++ b/azalea-physics/src/travel.rs @@ -1,5 +1,8 @@ -use azalea_block::{Block, BlockState}; -use azalea_core::{aabb::AABB, position::Vec3}; +use azalea_block::{Block, BlockState, fluid_state::FluidState}; +use azalea_core::{ + aabb::AABB, + position::{BlockPos, Vec3}, +}; use azalea_entity::{ Attributes, InLoadedChunk, Jumping, LocalEntity, LookDirection, OnClimbable, Physics, Pose, Position, metadata::Sprinting, move_relative, @@ -9,7 +12,11 @@ use bevy_ecs::prelude::*; use crate::{ HandleRelativeFrictionAndCalculateMovementOpts, - collision::{MoverType, move_colliding}, + collision::{ + MoverType, Shapes, + entity_collisions::{CollidableEntityQuery, PhysicsQuery, get_entity_collisions}, + move_colliding, + }, get_block_pos_below_that_affects_movement, handle_relative_friction_and_calculate_movement, }; @@ -19,6 +26,7 @@ use crate::{ pub fn travel( mut query: Query< ( + Entity, &mut Physics, &mut LookDirection, &mut Position, @@ -32,8 +40,11 @@ pub fn travel( (With, With), >, instance_container: Res, + physics_query: PhysicsQuery, + collidable_entity_query: CollidableEntityQuery, ) { for ( + entity, mut physics, direction, position, @@ -59,13 +70,16 @@ pub fn travel( // !this.canStandOnFluid(fluidAtBlock)` here but it doesn't matter // for players travel_in_fluid( + &world, + entity, &mut physics, &direction, position, attributes, sprinting, on_climbable, - &world, + &physics_query, + &collidable_entity_query, ); } else { travel_in_air( @@ -149,14 +163,18 @@ fn travel_in_air( } } +#[allow(clippy::too_many_arguments)] fn travel_in_fluid( + world: &Instance, + entity: Entity, physics: &mut Physics, direction: &LookDirection, mut position: Mut, attributes: &Attributes, sprinting: Sprinting, on_climbable: &OnClimbable, - world: &Instance, + physics_query: &PhysicsQuery, + collidable_entity_query: &CollidableEntityQuery, ) { let moving_down = physics.velocity.y <= 0.; let y = position.y; @@ -239,11 +257,13 @@ fn travel_in_fluid( let velocity = physics.velocity; if physics.horizontal_collision && is_free( - physics.bounding_box, world, - velocity.x, - velocity.y + 0.6 - position.y + y, - velocity.z, + entity, + physics_query, + collidable_entity_query, + physics, + physics.bounding_box, + velocity.up(0.6).down(position.y).up(y), ) { physics.velocity.y = 0.3; @@ -276,14 +296,97 @@ fn get_fluid_falling_adjusted_movement( } } -fn is_free(bounding_box: AABB, world: &Instance, x: f64, y: f64, z: f64) -> bool { - // let bounding_box = bounding_box.move_relative(Vec3::new(x, y, z)); +fn is_free( + world: &Instance, + source_entity: Entity, + physics_query: &PhysicsQuery, + collidable_entity_query: &CollidableEntityQuery, + entity_physics: &mut Physics, + bounding_box: AABB, + delta: Vec3, +) -> bool { + let bounding_box = bounding_box.move_relative(delta); + + no_collision( + world, + Some(source_entity), + physics_query, + collidable_entity_query, + entity_physics, + &bounding_box, + false, + ) && !contains_any_liquid(world, bounding_box) +} - let _ = (bounding_box, world, x, y, z); +fn no_collision( + world: &Instance, + source_entity: Option, + physics_query: &PhysicsQuery, + collidable_entity_query: &CollidableEntityQuery, + entity_physics: &mut Physics, + aabb: &AABB, + include_liquid_collisions: bool, +) -> bool { + let collisions = if include_liquid_collisions { + crate::collision::world_collisions::get_block_and_liquid_collisions(world, aabb) + } else { + crate::collision::world_collisions::get_block_collisions(world, aabb) + }; - // TODO: implement this, see Entity.isFree + for collision in collisions { + if !collision.is_empty() { + return false; + } + } + + if !get_entity_collisions( + world, + aabb, + source_entity, + physics_query, + collidable_entity_query, + ) + .is_empty() + { + false + } else if source_entity.is_none() { + true + } else { + let collision = border_collision(entity_physics, aabb); + if let Some(collision) = collision { + // !Shapes.joinIsNotEmpty(collision, Shapes.create(aabb), BooleanOp.AND); + !Shapes::matches_anywhere(&collision.into(), &aabb.into(), |a, b| a && b) + } else { + true + } + } +} + +fn border_collision(_entity_physics: &Physics, _aabb: &AABB) -> Option { + // TODO: implement world border, see CollisionGetter.borderCollision + + None +} + +fn contains_any_liquid(world: &Instance, bounding_box: AABB) -> bool { + let min = bounding_box.min.to_block_pos_floor(); + let max = bounding_box.max.to_block_pos_ceil(); + + for x in min.x..max.x { + for y in min.y..max.y { + for z in min.z..max.z { + let block_state = world + .chunks + .get_block_state(&BlockPos::new(x, y, z)) + .unwrap_or_default(); + if !FluidState::from(block_state).is_empty() { + return true; + } + } + } + } - true + false } fn get_effective_gravity() -> f64 { -- cgit v1.2.3