diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 1a6a5cae3f..711c846b13 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1344,50 +1344,21 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( )] prof_span!("box_voxel_collision"); - // Function for iterating over the blocks the player at a specific position - // collides with - /*fn collision_iter<'a, T: BaseVol + ReadVol>( - pos: Vec3, - terrain: &'a T, - hit: &'a impl Fn(&Block) -> bool, - height: &'a impl Fn(&Block) -> f32, - near_iter: impl Iterator + 'a, - radius: f32, - z_range: Range, - ) -> impl Iterator> + 'a { - near_iter.filter_map(move |(i, j, k)| { - let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k); + // Convience function to compute the player aabb + fn player_aabb(pos: Vec3, radius: f32, z_range: Range) -> Aabb { + Aabb { + min: pos + Vec3::new(-radius, -radius, z_range.start), + max: pos + Vec3::new(radius, radius, z_range.end), + } + } - // `near_iter` could be a few blocks too large due to being integer - // aligned and rounding up, so skip points outside of the tighter - // bounds before looking them up in the terrain - // (which incurs a hashmap cost for volgrids) - let player_aabb = Aabb { - min: pos + Vec3::new(-radius, -radius, z_range.start), - max: pos + Vec3::new(radius, radius, z_range.end), - }; - let block_approx = Aabb { - min: block_pos.as_(), - max: block_pos.as_() + Vec3::new(1.0, 1.0, Block::MAX_HEIGHT), - }; - if !player_aabb.collides_with_aabb(block_approx) { - return None; - } - - if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) { - let block_aabb = Aabb { - min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, height(&block)), - }; - - if player_aabb.collides_with_aabb(block_aabb) { - return Some(block_aabb); - } - } - - None - }) - }*/ + // Convience function to translate the near_aabb into the world space + fn move_aabb(aabb: Aabb, pos: Vec3) -> Aabb { + Aabb { + min: aabb.min + pos.map(|e| e.floor() as i32), + max: aabb.max + pos.map(|e| e.floor() as i32), + } + } // Function for determining whether the player at a specific position collides // with blocks with the given criteria @@ -1399,16 +1370,10 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( radius: f32, z_range: Range, ) -> bool { - let player_aabb = Aabb { - min: pos + Vec3::new(-radius, -radius, z_range.start), - max: pos + Vec3::new(radius, radius, z_range.end), - }; + let player_aabb = player_aabb(pos, radius, z_range); // Calculate the world space near aabb - let near_aabb = Aabb { - min: near_aabb.min + pos.map(|e| e.floor() as i32), - max: near_aabb.max + pos.map(|e| e.floor() as i32), - }; + let near_aabb = move_aabb(near_aabb, pos); let mut collision = false; // TODO: could short-circuit here @@ -1431,25 +1396,84 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( #[allow(clippy::trivially_copy_pass_by_ref)] fn always_hits(_: &Block) -> bool { true } + // Stuff for liquid immersion and wall contact detections + let dirs = [ + Vec3::unit_x(), + Vec3::unit_y(), + -Vec3::unit_x(), + -Vec3::unit_y(), + ]; + + struct LiquidAndWalls { + liquid: Option<(LiquidKind, f32)>, + wall_dir_collisions: [bool; 4], + } + + impl LiquidAndWalls { + pub fn new() -> Self { + Self { + liquid: None, + wall_dir_collisions: [false; 4], + } + } + + pub fn update_max_liquid( + &mut self, + block_pos: Vec3, + block: Block, + player_aabb: Aabb, + ) { + // Check for liquid blocks + if let Some(block_liquid) = block.liquid_kind() { + let liquid_aabb = Aabb { + min: block_pos.map(|e| e as f32), + // The liquid part of a liquid block always extends 1 block high. + max: block_pos.map(|e| e as f32) + Vec3::one(), + }; + if player_aabb.collides_with_aabb(liquid_aabb) { + self.liquid = match self.liquid { + Some((kind, max_liquid_z)) => Some(( + // TODO: merging of liquid kinds and max_liquid_z are done + // independently which allows mix and + // matching them + kind.merge(block_liquid), + max_liquid_z.max(liquid_aabb.max.z), + )), + None => Some((block_liquid, liquid_aabb.max.z)), + }; + } + } + } + + pub fn update_walls( + &mut self, + block_pos: Vec3, + block: Block, + player_aabbs: [Aabb; 4], + ) { + // Check for walls + if block.is_solid() { + let block_aabb = Aabb { + min: block_pos.map(|e| e as f32), + max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.solid_height()), + }; + + for dir in 0..4 { + if player_aabbs[dir].collides_with_aabb(block_aabb) { + self.wall_dir_collisions[dir] = true; + } + } + } + } + } + + // Setup values for the loop below + let (radius, z_min, z_max) = cylinder; // Probe distances let hdist = radius.ceil() as i32; - // Neighbouring blocks iterator - /*let near_iter = (-hdist..=hdist) - .flat_map(move |i| { - (-hdist..=hdist).map(move |j| { - let max_block_height = Block::MAX_HEIGHT.ceil() as i32; - let box_floor = z_min.floor() as i32; - let floor = 1 - max_block_height + box_floor; - let ceil = z_max.ceil() as i32; - - (floor..=ceil).map(move |k| (i, j, k)) - }) - }) - .flatten();*/ - // Neighbouring blocks Aabb let near_aabb = Aabb { min: Vec3::new( @@ -1462,6 +1486,17 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( let z_range = z_min..z_max; + // Compute a list of Aabbs to check for collision with nearby walls + let player_wall_aabbs = |pos| { + dirs.map(|dir| { + let pos = pos + dir * 0.01; + Aabb { + min: pos + Vec3::new(-radius, -radius, z_range.start), + max: pos + Vec3::new(radius, radius, z_range.end), + } + }) + }; + physics_state.on_ground = None; physics_state.on_ceiling = false; @@ -1485,20 +1520,14 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( let try_colliding_block = |pos: &Pos| { prof_span!("most colliding check"); // Calculate the player's AABB - let player_aabb = Aabb { - min: pos.0 + Vec3::new(-radius, -radius, z_min), - max: pos.0 + Vec3::new(radius, radius, z_max), - }; + let player_aabb = player_aabb(pos.0, radius, z_range.clone()); // Determine the block that we are colliding with most // (based on minimum collision axis) // (if we are colliding with one) let mut most_colliding = None; // Calculate the world space near aabb - let near_aabb = Aabb { - min: near_aabb.min + pos.0.map(|e| e.floor() as i32), - max: near_aabb.max + pos.0.map(|e| e.floor() as i32), - }; + let near_aabb = move_aabb(near_aabb, pos.0); let player_overlap = |block_aabb: Aabb| { ordered_float::OrderedFloat( (block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5) @@ -1546,10 +1575,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( .flatten() { // Calculate the player's AABB - let player_aabb = Aabb { - min: pos.0 + Vec3::new(-radius, -radius, z_min), - max: pos.0 + Vec3::new(radius, radius, z_max), - }; + let player_aabb = player_aabb(pos.0, radius, z_range.clone()); // Find the intrusion vector of the collision let dir = player_aabb.collision_vector_with_aabb(block_aabb); @@ -1644,6 +1670,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( } } + // Report on_ceiling state if on_ceiling { physics_state.on_ceiling = true; } @@ -1676,76 +1703,26 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( .copied(); } - let player_aabb = Aabb { - min: pos.0 + Vec3::new(-radius, -radius, z_range.start), - max: pos.0 + Vec3::new(radius, radius, z_range.end), - }; - let player_voxel_pos = pos.0.map(|e| e.floor() as i32); + // Find liquid immersion and wall collision + prof_span!(guard, "liquid/walls"); + let mut liquid_and_walls = LiquidAndWalls::new(); - let dirs = [ - Vec3::unit_x(), - Vec3::unit_y(), - -Vec3::unit_x(), - -Vec3::unit_y(), - ]; - let player_wall_aabbs = dirs.map(|dir| { - let pos = pos.0 + dir * 0.01; - Aabb { - min: pos + Vec3::new(-radius, -radius, z_range.start), - max: pos + Vec3::new(radius, radius, z_range.end), - } - }); + let player_aabb = player_aabb(pos.0, radius, z_range.clone()); + let player_wall_aabbs = player_wall_aabbs(pos.0); // Calculate the world space near aabb - let near_aabb = Aabb { - min: near_aabb.min + player_voxel_pos, - max: near_aabb.max + player_voxel_pos, - }; - // Find liquid immersion and wall collision all in one round of iteration - let mut liquid = None::<(LiquidKind, f32)>; // (kind, max_liquid_z) - let mut wall_dir_collisions = [false; 4]; - prof_span!(guard, "liquid/walls"); + let near_aabb = move_aabb(near_aabb, pos.0); + terrain.for_each_in(near_aabb, |block_pos, block| { - // Check for liquid blocks - if let Some(block_liquid) = block.liquid_kind() { - let liquid_aabb = Aabb { - min: block_pos.map(|e| e as f32), - // The liquid part of a liquid block always extends 1 block high. - max: block_pos.map(|e| e as f32) + Vec3::one(), - }; - if player_aabb.collides_with_aabb(liquid_aabb) { - liquid = match liquid { - Some((kind, max_liquid_z)) => Some(( - // TODO: merging of liquid kinds and max_liquid_z are done independently - // which allows mix and matching them - kind.merge(block_liquid), - max_liquid_z.max(liquid_aabb.max.z), - )), - None => Some((block_liquid, liquid_aabb.max.z)), - }; - } - } - - // Check for walls - if block.is_solid() { - let block_aabb = Aabb { - min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.solid_height()), - }; - - for dir in 0..4 { - if player_wall_aabbs[dir].collides_with_aabb(block_aabb) { - wall_dir_collisions[dir] = true; - } - } - } + liquid_and_walls.update_max_liquid(block_pos, block, player_aabb); + liquid_and_walls.update_walls(block_pos, block, player_wall_aabbs); }); drop(guard); // Use wall collision results to determine if we are against a wall let mut on_wall = None; for dir in 0..4 { - if wall_dir_collisions[dir] { + if liquid_and_walls.wall_dir_collisions[dir] { on_wall = Some(match on_wall { Some(acc) => acc + dirs[dir], None => dirs[dir], @@ -1761,7 +1738,8 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( physics_state.ground_vel = ground_vel; } - physics_state.in_fluid = liquid + physics_state.in_fluid = liquid_and_walls + .liquid .map(|(kind, max_z)| { // NOTE: assumes min_z == 0.0 let depth = max_z - pos.0.z;