From c22636a4b8b2832a248802a3ff4d8e00f2b228ee Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 25 Mar 2021 00:38:02 -0400 Subject: [PATCH 01/12] Initial iterator optimization by using internal iteration at the vol_grid level, water/walls not switched over yet --- common/src/terrain/chonk.rs | 24 ++++ common/src/vol.rs | 21 +++ common/src/volumes/vol_grid_2d.rs | 43 ++++++ common/systems/src/phys.rs | 211 ++++++++++++++++++------------ 4 files changed, 215 insertions(+), 84 deletions(-) diff --git a/common/src/terrain/chonk.rs b/common/src/terrain/chonk.rs index 29240560de..1d6cc89e74 100644 --- a/common/src/terrain/chonk.rs +++ b/common/src/terrain/chonk.rs @@ -163,6 +163,30 @@ impl ReadVol for Chonk { .map_err(Self::Error::SubChunkError) } } + + /// Call provided closure with each block in the supplied Aabb + /// Portions of the Aabb outside this chonk are ignored + //#[inline] + fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, V)) + where + V: Copy, + { + for x in aabb.min.x..aabb.max.x + 1 { + for y in aabb.min.y..aabb.max.y + 1 { + for z in aabb.min.z..aabb.max.z + 1 { + if let Ok(block) = self.get(Vec3::new(x, y, z)) { + f(Vec3::new(x, y, z), *block); + } + } + } + } + // TODO + //let min_z = self.get_min_z(); + //let max_z = self.get_max_z(); + // Iterate through blocks in above terrain + // Iterate through blocks in subchunks + // Iterate through bloks in below terrain + } } impl WriteVol for Chonk { diff --git a/common/src/vol.rs b/common/src/vol.rs index 34f66ab804..2a261659ef 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -111,6 +111,27 @@ pub trait ReadVol: BaseVol { { Ray::new(self, from, to, |_| true) } + + /// Call provided closure with each block in the supplied Aabb + /// Portions of the Aabb outside the volume are ignored + //#[inline] + fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, Self::Vox)) + where + Self::Vox: Copy, + { + (aabb.min.x..=aabb.max.x) + .map(|x| { + (aabb.min.y..=aabb.max.y) + .map(move |y| (aabb.min.z..=aabb.max.z).map(move |z| Vec3::new(x, y, z))) + }) + .flatten() + .flatten() + .for_each(|pos| { + if let Ok(vox) = self.get(pos) { + f(pos, *vox); + } + }); + } } /// A volume that provides the ability to sample (i.e., clone a section of) its diff --git a/common/src/volumes/vol_grid_2d.rs b/common/src/volumes/vol_grid_2d.rs index fc558c9235..ddb4167a18 100644 --- a/common/src/volumes/vol_grid_2d.rs +++ b/common/src/volumes/vol_grid_2d.rs @@ -53,6 +53,40 @@ impl ReadVol for VolGrid2d { chunk.get(co).map_err(VolGrid2dError::ChunkError) }) } + + // /// Call provided closure with each block in the supplied Aabb + // /// Areas outside loaded chunks are ignored + fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, Self::Vox)) + where + Self::Vox: Copy, + { + let min_chunk_key = self.pos_key(aabb.min); + let max_chunk_key = self.pos_key(aabb.max); + for key_x in min_chunk_key.x..max_chunk_key.x + 1 { + for key_y in min_chunk_key.y..max_chunk_key.y + 1 { + let key = Vec2::new(key_x, key_y); + let pos = self.key_pos(key); + // Calculate intersection of Aabb and this chunk + // TODO: should we do this more implicitly as part of the loop + // TODO: this probably has to be computed in the chunk.for_each_in() as well + // maybe remove here? + let intersection = aabb.intersection(Aabb { + min: pos.with_z(i32::MIN), + // -1 here since the Aabb is inclusive and chunk_offs below will wrap it if + // it's outside the range of the chunk + max: (pos + Self::chunk_size().map(|e| e as i32) - 1).with_z(i32::MAX), + }); + // Map intersection into chunk coordinates + let intersection = Aabb { + min: Self::chunk_offs(intersection.min), + max: Self::chunk_offs(intersection.max), + }; + if let Some(chonk) = self.get_key(key) { + chonk.for_each_in(intersection, |pos_offset, block| f(pos_offset + pos, block)); + } + } + } + } } // TODO: This actually breaks the API: samples are supposed to have an offset of @@ -117,34 +151,43 @@ impl VolGrid2d { } } + //#[inline] pub fn chunk_size() -> Vec2 { V::RECT_SIZE } + //#[inline] pub fn insert(&mut self, key: Vec2, chunk: Arc) -> Option> { self.chunks.insert(key, chunk) } + //#[inline] pub fn get_key(&self, key: Vec2) -> Option<&V> { self.chunks.get(&key).map(|arc_chunk| arc_chunk.as_ref()) } + //#[inline] pub fn get_key_arc(&self, key: Vec2) -> Option<&Arc> { self.chunks.get(&key) } pub fn clear(&mut self) { self.chunks.clear(); } pub fn drain(&mut self) -> hash_map::Drain, Arc> { self.chunks.drain() } + //#[inline] pub fn remove(&mut self, key: Vec2) -> Option> { self.chunks.remove(&key) } + //#[inline] pub fn key_pos(&self, key: Vec2) -> Vec2 { key * V::RECT_SIZE.map(|e| e as i32) } + //#[inline] pub fn pos_key(&self, pos: Vec3) -> Vec2 { Self::chunk_key(pos) } + //#[inline] pub fn iter(&self) -> ChunkIter { ChunkIter { iter: self.chunks.iter(), } } + //#[inline] pub fn cached(&self) -> CachedVolGrid2d { CachedVolGrid2d::new(self) } } diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 861dddc13e..f049e60b0f 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1342,10 +1342,11 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( clippy::cast_possible_truncation, clippy::cast_sign_loss )] + 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>( + /*fn collision_iter<'a, T: BaseVol + ReadVol>( pos: Vec3, terrain: &'a T, hit: &'a impl Fn(&Block) -> bool, @@ -1386,7 +1387,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( None }) - } + }*/ // Function for determining whether the player at a specific position collides // with blocks with the given criteria @@ -1394,21 +1395,36 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( pos: Vec3, terrain: &'a T, hit: impl Fn(&Block) -> bool, - near_iter: impl Iterator + 'a, + near_aabb: Aabb, radius: f32, z_range: Range, ) -> bool { - collision_iter( - pos, - terrain, - &|block| block.is_solid() && hit(block), - &Block::solid_height, - near_iter, - radius, - z_range, - ) - .next() - .is_some() + let player_aabb = Aabb { + min: pos + Vec3::new(-radius, -radius, z_range.start), + max: pos + Vec3::new(radius, radius, z_range.end), + }; + + // 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 mut collision = false; + // TODO: could short-circuit here + terrain.for_each_in(near_aabb, |block_pos, block| { + if block.is_solid() && hit(&block) { + 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()), + }; + if player_aabb.collides_with_aabb(block_aabb) { + collision = true; + } + } + }); + + collision } // Should be easy to just make clippy happy if we want? @@ -1434,12 +1450,21 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( }) .flatten(); + let near_aabb = Aabb { + min: Vec3::new( + -hdist, + -hdist, + 1 - Block::MAX_HEIGHT.ceil() as i32 + z_min.floor() as i32, + ), + max: Vec3::new(hdist, hdist, z_max.ceil() as i32), + }; + let z_range = z_min..z_max; physics_state.on_ground = None; physics_state.on_ceiling = false; - let mut on_ground = None; + let mut on_ground = None::; let mut on_ceiling = false; // Don't loop infinitely here let mut attempts = 0; @@ -1452,10 +1477,12 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( .clamped(1, MAX_INCREMENTS); let old_pos = pos.0; for _ in 0..increments { + prof_span!("increment"); const MAX_ATTEMPTS: usize = 16; pos.0 += pos_delta / increments as f32; 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), @@ -1465,41 +1492,51 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( // Determine the block that we are colliding with most // (based on minimum collision axis) // (if we are colliding with one) - // - // 1) Calculate the block's positions in world space - // 2) Make sure the block is actually solid - // 3) Calculate block AABB - // 4) Find the maximum of the minimum collision axes - // (this bit is weird, trust me that it works) - near_iter - .clone() - .map(|(i, j, k)| pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k)) - .filter_map(|block_pos| { - terrain - .get(block_pos) - .ok() - .filter(|block| block.is_solid()) - .map(|block| (block_pos, block)) - }) - .map(|(block_pos, block)| { - ( - block_pos, - 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()), - }, - block, - ) - }) - .filter(|(_, block_aabb, _)| block_aabb.collides_with_aabb(player_aabb)) - .min_by_key(|(_, block_aabb, _)| { - ordered_float::OrderedFloat( - (block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5) - .map(f32::abs) - .sum(), - ) - }) + 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 player_overlap = |block_aabb: Aabb| { + ordered_float::OrderedFloat( + (block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5) + .map(f32::abs) + .sum(), + ) + }; + + terrain.for_each_in(near_aabb, |block_pos, block| { + // Make sure the block is actually solid + if block.is_solid() { + // Calculate block AABB + 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()), + }; + + // Determine whether the block's AABB collides with the player's AABB + if block_aabb.collides_with_aabb(player_aabb) { + most_colliding = match most_colliding { + // Select the minimum of the value from `player_overlap` + other @ Some((_, other_block_aabb, _)) + if { + // TODO: comment below is outdated (as of ~1 year ago) + // Find the maximum of the minimum collision axes (this bit + // is weird, trust me that it works) + player_overlap(block_aabb) >= player_overlap(other_block_aabb) + } => + { + other + }, + _ => Some((block_pos, block_aabb, block)), + } + } + } + }); + + most_colliding }; // While the player is colliding with the terrain... @@ -1531,7 +1568,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( // ground /* if resolve_dir.z > 0.0 && vel.0.z <= 0.0 { */ if resolve_dir.z > 0.0 { - on_ground = Some(*block); + on_ground = Some(block); if !was_on_ground { land_on_ground(entity, *vel); @@ -1544,33 +1581,33 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( // with a wall // // If we're being pushed out horizontally... - let pushed_horizontaly = resolve_dir.z == 0.0; + if resolve_dir.z == 0.0 // ...and the vertical resolution direction is sufficiently great... - let vertical_resolution = dir.z < -0.1; + && dir.z < -0.1 // ...and the space above is free... - let space_above_is_free = !collision_with( - Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), - &terrain, - always_hits, - near_iter.clone(), - radius, - z_range.clone(), - ); + && { + prof_span!("space above free"); + !collision_with( + Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), + &terrain, + always_hits, + near_aabb, + radius, + z_range.clone(), + ) + } // ...and there is a collision with a block beneath our current hitbox... - let block_beneath_collides = collision_with( - pos.0 + resolve_dir - Vec3::unit_z() * 1.25, - &terrain, - always_hits, - near_iter.clone(), - radius, - z_range.clone(), - ); - - if pushed_horizontaly - && vertical_resolution - && space_above_is_free - && block_beneath_collides - { + && { + prof_span!("collision beneath"); + collision_with( + pos.0 + resolve_dir - Vec3::unit_z() * 1.25, + &terrain, + always_hits, + near_aabb, + radius, + z_range.clone(), + ) + } { // ...block-hop! pos.0.z = pos.0.z.max(block_aabb.max.z); vel.0.z = vel.0.z.max(0.0); @@ -1579,7 +1616,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( if (vel.0 * resolve_dir).xy().magnitude_squared() < 1.0_f32.powi(2) { pos.0 -= resolve_dir.normalized() * 0.05; } - on_ground = Some(*block); + on_ground = Some(block); break; } @@ -1613,17 +1650,21 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( if on_ground.is_some() { physics_state.on_ground = on_ground; // If the space below us is free, then "snap" to the ground - } else if collision_with( - pos.0 - Vec3::unit_z() * 1.1, - &terrain, - always_hits, - near_iter.clone(), - radius, - z_range.clone(), - ) && vel.0.z <= 0.0 + } else if { + prof_span!("snap check"); + collision_with( + pos.0 - Vec3::unit_z() * 1.1, + &terrain, + always_hits, + near_aabb, + radius, + z_range.clone(), + ) + } && vel.0.z <= 0.0 && was_on_ground && block_snap { + prof_span!("snap!!"); let snap_height = terrain .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.1).map(|e| e.floor() as i32)) .ok() @@ -1660,6 +1701,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( // Find liquid immersion and wall collision all in one round of iteration let mut liquid = None::<(LiquidKind, f32)>; let mut wall_dir_collisions = [false; 4]; + prof_span!(guard, "liquid/walls"); near_iter.for_each(|(i, j, k)| { let block_pos = player_voxel_pos + Vec3::new(i, j, k); @@ -1696,6 +1738,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( } } }); + drop(guard); // Use wall collision results to determine if we are against a wall let mut on_wall = None; From bfece25bba637ba7276b50dc2f173c96db5821d0 Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 25 Mar 2021 00:51:32 -0400 Subject: [PATCH 02/12] Convert water/walls check to the new iteration function --- common/systems/src/phys.rs | 85 ++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index f049e60b0f..3f395f73e1 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1437,19 +1437,20 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( 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; + /*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)) - }) + (floor..=ceil).map(move |k| (i, j, k)) }) - .flatten(); + }) + .flatten();*/ + // Neighbouring blocks Aabb let near_aabb = Aabb { min: Vec3::new( -hdist, @@ -1698,42 +1699,46 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( } }); + // 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)>; + let mut liquid = None::<(LiquidKind, f32)>; // (kind, max_liquid_z) let mut wall_dir_collisions = [false; 4]; prof_span!(guard, "liquid/walls"); - near_iter.for_each(|(i, j, k)| { - let block_pos = player_voxel_pos + Vec3::new(i, j, k); - - if let Some(block) = terrain.get(block_pos).ok().copied() { - // 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(), + 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)), }; - if player_aabb.collides_with_aabb(liquid_aabb) { - liquid = match liquid { - Some((kind, max_liquid_z)) => Some(( - 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; - } + // 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; } } } From 3f3eb637c4cc6dce9ecfbf032b5bf549703a4a79 Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 25 Mar 2021 00:52:42 -0400 Subject: [PATCH 03/12] Do iteration check for snapping after checking the other conditions --- common/systems/src/phys.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 3f395f73e1..1a6a5cae3f 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1651,7 +1651,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( if on_ground.is_some() { physics_state.on_ground = on_ground; // If the space below us is free, then "snap" to the ground - } else if { + } else if vel.0.z <= 0.0 && was_on_ground && block_snap && { prof_span!("snap check"); collision_with( pos.0 - Vec3::unit_z() * 1.1, @@ -1661,10 +1661,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( radius, z_range.clone(), ) - } && vel.0.z <= 0.0 - && was_on_ground - && block_snap - { + } { prof_span!("snap!!"); let snap_height = terrain .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.1).map(|e| e.floor() as i32)) From 77a1fc26bdbe7406ae5cb28fcb171599bfcb72e7 Mon Sep 17 00:00:00 2001 From: Imbris Date: Thu, 25 Mar 2021 02:27:52 -0400 Subject: [PATCH 04/12] Refactor some of the terrain collision code, failed pre-emptive liquid check experiment --- common/systems/src/phys.rs | 254 +++++++++++++++++-------------------- 1 file changed, 116 insertions(+), 138 deletions(-) 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; From 685bcb5c62fce6afd4f1c38bccde1c4d82588560 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 28 Mar 2021 13:30:38 -0400 Subject: [PATCH 05/12] simplify water/walls check code, interally iterate in chonks (but not chunks) --- common/src/terrain/chonk.rs | 45 ++++++++--- common/systems/src/phys.rs | 154 ++++++++++++++---------------------- 2 files changed, 94 insertions(+), 105 deletions(-) diff --git a/common/src/terrain/chonk.rs b/common/src/terrain/chonk.rs index 1d6cc89e74..3ec18617e7 100644 --- a/common/src/terrain/chonk.rs +++ b/common/src/terrain/chonk.rs @@ -171,21 +171,48 @@ impl ReadVol for Chonk { where V: Copy, { - for x in aabb.min.x..aabb.max.x + 1 { - for y in aabb.min.y..aabb.max.y + 1 { - for z in aabb.min.z..aabb.max.z + 1 { - if let Ok(block) = self.get(Vec3::new(x, y, z)) { - f(Vec3::new(x, y, z), *block); + // Iterate through blocks in above terrain + if aabb.max.z >= self.get_max_z() { + let min_z = aabb.min.z.max(self.get_max_z()); + for x in aabb.min.x..aabb.max.x + 1 { + for y in aabb.min.y..aabb.max.y + 1 { + for z in min_z..aabb.max.z + 1 { + f(Vec3::new(x, y, z), self.above); } } } } - // TODO - //let min_z = self.get_min_z(); - //let max_z = self.get_max_z(); - // Iterate through blocks in above terrain // Iterate through blocks in subchunks + // Compute lowest & highest subchunks + { + let min_z = self.get_min_z().max(aabb.min.z); + let max_z = (self.get_max_z() - 1).min(aabb.max.z); + let min_sub_chunk_idx = self.sub_chunk_idx(min_z); + let max_sub_chunk_idx = self.sub_chunk_idx(max_z); + for sub_chunk_idx in min_sub_chunk_idx..max_sub_chunk_idx + 1 { + let z_offset = -Vec3::unit_z() + * (self.z_offset + sub_chunk_idx * SubChunkSize::::SIZE.z as i32); + let relative_aabb = Aabb { + min: aabb.min + z_offset, + max: aabb.max + z_offset, + }; + self.sub_chunks[sub_chunk_idx as usize] + .for_each_in(relative_aabb, |relative_pos, block| { + f(relative_pos - z_offset, block) + }); + } + } // Iterate through bloks in below terrain + if aabb.min.z > self.get_min_z() { + let max_z = aabb.max.z.min(self.get_min_z() - 1); + for x in aabb.min.x..aabb.max.x + 1 { + for y in aabb.min.y..aabb.max.y + 1 { + for z in aabb.min.z..max_z + 1 { + f(Vec3::new(x, y, z), self.below); + } + } + } + } } } diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 711c846b13..f40846b582 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1396,79 +1396,6 @@ 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 @@ -1486,17 +1413,7 @@ 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), - } - }) - }; - + // Setup values for the loop below physics_state.on_ground = None; physics_state.on_ceiling = false; @@ -1703,26 +1620,72 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( .copied(); } - // Find liquid immersion and wall collision - prof_span!(guard, "liquid/walls"); - let mut liquid_and_walls = LiquidAndWalls::new(); - + // Find liquid immersion and wall collision all in one round of iteration 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 + // Calculate the world space near_aabb let near_aabb = move_aabb(near_aabb, pos.0); + let dirs = [ + Vec3::unit_x(), + Vec3::unit_y(), + -Vec3::unit_x(), + -Vec3::unit_y(), + ]; + + // Compute a list of aabbs to check for collision with nearby walls + 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 mut liquid = None::<(LiquidKind, f32)>; + let mut wall_dir_collisions = [false; 4]; + prof_span!(guard, "liquid/walls"); terrain.for_each_in(near_aabb, |block_pos, block| { - liquid_and_walls.update_max_liquid(block_pos, block, player_aabb); - liquid_and_walls.update_walls(block_pos, block, player_wall_aabbs); + // 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; + } + } + } }); 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 liquid_and_walls.wall_dir_collisions[dir] { + if wall_dir_collisions[dir] { on_wall = Some(match on_wall { Some(acc) => acc + dirs[dir], None => dirs[dir], @@ -1738,8 +1701,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( physics_state.ground_vel = ground_vel; } - physics_state.in_fluid = liquid_and_walls - .liquid + physics_state.in_fluid = liquid .map(|(kind, max_z)| { // NOTE: assumes min_z == 0.0 let depth = max_z - pos.0.z; From 81d426f7a093e7a04788e8b218dd83dae30b0c06 Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 8 Oct 2021 10:56:02 -0400 Subject: [PATCH 06/12] Try marking Chonk::for_each_in and related functions as inlineable --- common/src/terrain/chonk.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/src/terrain/chonk.rs b/common/src/terrain/chonk.rs index 3ec18617e7..94f3a3f501 100644 --- a/common/src/terrain/chonk.rs +++ b/common/src/terrain/chonk.rs @@ -57,8 +57,10 @@ impl Chonk { pub fn meta(&self) -> &M { &self.meta } + #[inline] pub fn get_min_z(&self) -> i32 { self.z_offset } + #[inline] pub fn get_max_z(&self) -> i32 { self.z_offset + (self.sub_chunks.len() as u32 * SubChunkSize::::SIZE.z) as i32 } @@ -71,6 +73,7 @@ impl Chonk { // Returns the index (in self.sub_chunks) of the SubChunk that contains // layer z; note that this index changes when more SubChunks are prepended + #[inline] fn sub_chunk_idx(&self, z: i32) -> i32 { let diff = z - self.z_offset; diff >> (SubChunkSize::::SIZE.z - 1).count_ones() @@ -166,7 +169,7 @@ impl ReadVol for Chonk { /// Call provided closure with each block in the supplied Aabb /// Portions of the Aabb outside this chonk are ignored - //#[inline] + #[inline] fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, V)) where V: Copy, From 3e84bb0669767e4bacd40f088cfb9f601fe6a292 Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 8 Oct 2021 11:43:09 -0400 Subject: [PATCH 07/12] Try marking VolGrid2d::for_each_in and friends as inlineable --- common/src/volumes/vol_grid_2d.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/common/src/volumes/vol_grid_2d.rs b/common/src/volumes/vol_grid_2d.rs index ddb4167a18..ae5849fe41 100644 --- a/common/src/volumes/vol_grid_2d.rs +++ b/common/src/volumes/vol_grid_2d.rs @@ -56,6 +56,7 @@ impl ReadVol for VolGrid2d { // /// Call provided closure with each block in the supplied Aabb // /// Areas outside loaded chunks are ignored + #[inline(always)] fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, Self::Vox)) where Self::Vox: Copy, @@ -151,7 +152,7 @@ impl VolGrid2d { } } - //#[inline] + #[inline(always)] pub fn chunk_size() -> Vec2 { V::RECT_SIZE } //#[inline] @@ -159,7 +160,7 @@ impl VolGrid2d { self.chunks.insert(key, chunk) } - //#[inline] + #[inline(always)] pub fn get_key(&self, key: Vec2) -> Option<&V> { self.chunks.get(&key).map(|arc_chunk| arc_chunk.as_ref()) } @@ -174,10 +175,10 @@ impl VolGrid2d { //#[inline] pub fn remove(&mut self, key: Vec2) -> Option> { self.chunks.remove(&key) } - //#[inline] + #[inline(always)] pub fn key_pos(&self, key: Vec2) -> Vec2 { key * V::RECT_SIZE.map(|e| e as i32) } - //#[inline] + #[inline(always)] pub fn pos_key(&self, pos: Vec3) -> Vec2 { Self::chunk_key(pos) } //#[inline] From fb68bb38706c56c6f41dde6804ae60043f8e0409 Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 8 Oct 2021 11:53:40 -0400 Subject: [PATCH 08/12] Make LiquidKind::merge as inlineable, remove always from TerrainGrid::for_each_in inline annotation --- common/src/comp/fluid_dynamics.rs | 1 + common/src/volumes/vol_grid_2d.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/comp/fluid_dynamics.rs b/common/src/comp/fluid_dynamics.rs index 209ccfb79d..1607cb5f2c 100644 --- a/common/src/comp/fluid_dynamics.rs +++ b/common/src/comp/fluid_dynamics.rs @@ -19,6 +19,7 @@ impl LiquidKind { /// If an entity is in multiple overlapping liquid blocks, which one takes /// precedence? (should be a rare edge case, since checkerboard patterns of /// water and lava shouldn't show up in worldgen) + #[inline] pub fn merge(self, other: LiquidKind) -> LiquidKind { use LiquidKind::{Lava, Water}; match (self, other) { diff --git a/common/src/volumes/vol_grid_2d.rs b/common/src/volumes/vol_grid_2d.rs index ae5849fe41..b6fac1630f 100644 --- a/common/src/volumes/vol_grid_2d.rs +++ b/common/src/volumes/vol_grid_2d.rs @@ -56,7 +56,7 @@ impl ReadVol for VolGrid2d { // /// Call provided closure with each block in the supplied Aabb // /// Areas outside loaded chunks are ignored - #[inline(always)] + #[inline] fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, Self::Vox)) where Self::Vox: Copy, From 6fb2e66dc19452701fc971e6008f14e7542ceb65 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 10 Oct 2021 01:29:05 -0400 Subject: [PATCH 09/12] Remove custom for_each_in from Chonk --- common/src/terrain/chonk.rs | 51 ------------------------------------- 1 file changed, 51 deletions(-) diff --git a/common/src/terrain/chonk.rs b/common/src/terrain/chonk.rs index 94f3a3f501..359479e7de 100644 --- a/common/src/terrain/chonk.rs +++ b/common/src/terrain/chonk.rs @@ -166,57 +166,6 @@ impl ReadVol for Chonk { .map_err(Self::Error::SubChunkError) } } - - /// Call provided closure with each block in the supplied Aabb - /// Portions of the Aabb outside this chonk are ignored - #[inline] - fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, V)) - where - V: Copy, - { - // Iterate through blocks in above terrain - if aabb.max.z >= self.get_max_z() { - let min_z = aabb.min.z.max(self.get_max_z()); - for x in aabb.min.x..aabb.max.x + 1 { - for y in aabb.min.y..aabb.max.y + 1 { - for z in min_z..aabb.max.z + 1 { - f(Vec3::new(x, y, z), self.above); - } - } - } - } - // Iterate through blocks in subchunks - // Compute lowest & highest subchunks - { - let min_z = self.get_min_z().max(aabb.min.z); - let max_z = (self.get_max_z() - 1).min(aabb.max.z); - let min_sub_chunk_idx = self.sub_chunk_idx(min_z); - let max_sub_chunk_idx = self.sub_chunk_idx(max_z); - for sub_chunk_idx in min_sub_chunk_idx..max_sub_chunk_idx + 1 { - let z_offset = -Vec3::unit_z() - * (self.z_offset + sub_chunk_idx * SubChunkSize::::SIZE.z as i32); - let relative_aabb = Aabb { - min: aabb.min + z_offset, - max: aabb.max + z_offset, - }; - self.sub_chunks[sub_chunk_idx as usize] - .for_each_in(relative_aabb, |relative_pos, block| { - f(relative_pos - z_offset, block) - }); - } - } - // Iterate through bloks in below terrain - if aabb.min.z > self.get_min_z() { - let max_z = aabb.max.z.min(self.get_min_z() - 1); - for x in aabb.min.x..aabb.max.x + 1 { - for y in aabb.min.y..aabb.max.y + 1 { - for z in aabb.min.z..max_z + 1 { - f(Vec3::new(x, y, z), self.below); - } - } - } - } - } } impl WriteVol for Chonk { From 34beb1155706bcc1ce227ace678ca8a225f36c1e Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 10 Oct 2021 02:21:39 -0400 Subject: [PATCH 10/12] Switch to for loops in default for_each_in (small perf increase), add inline to sprite methods (no perf change) --- common/src/terrain/sprite.rs | 7 +++++++ common/src/vol.rs | 19 ++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/common/src/terrain/sprite.rs b/common/src/terrain/sprite.rs index cf67732a01..05f046f092 100644 --- a/common/src/terrain/sprite.rs +++ b/common/src/terrain/sprite.rs @@ -181,6 +181,7 @@ make_case_elim!( ); impl SpriteKind { + #[inline] pub fn solid_height(&self) -> Option { // Beware: the height *must* be <= `MAX_HEIGHT` or the collision system will not // properly detect it! @@ -268,6 +269,7 @@ impl SpriteKind { } /// What loot table does collecting this sprite draw from? + #[inline] pub fn collectible_id(&self) -> Option> { let item = |id: &'static str| LootSpec::Item(id); let table = |id: &'static str| LootSpec::LootTable(id); @@ -336,16 +338,19 @@ impl SpriteKind { } /// Can this sprite be picked up to yield an item without a tool? + #[inline] pub fn is_collectible(&self) -> bool { self.collectible_id().is_some() && self.mine_tool().is_none() } /// Is the sprite a container that will emit a mystery item? + #[inline] pub fn is_container(&self) -> bool { matches!(self.collectible_id(), Some(LootSpec::LootTable(_))) } /// Which tool (if any) is needed to collect this sprite? + #[inline] pub fn mine_tool(&self) -> Option { match self { SpriteKind::Velorite @@ -375,6 +380,7 @@ impl SpriteKind { } } + #[inline] pub fn has_ori(&self) -> bool { matches!( self, @@ -446,5 +452,6 @@ lazy_static! { impl<'a> TryFrom<&'a str> for SpriteKind { type Error = (); + #[inline] fn try_from(s: &'a str) -> Result { SPRITE_KINDS.get(s).copied().ok_or(()) } } diff --git a/common/src/vol.rs b/common/src/vol.rs index 2a261659ef..4d1d4a305e 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -119,18 +119,15 @@ pub trait ReadVol: BaseVol { where Self::Vox: Copy, { - (aabb.min.x..=aabb.max.x) - .map(|x| { - (aabb.min.y..=aabb.max.y) - .map(move |y| (aabb.min.z..=aabb.max.z).map(move |z| Vec3::new(x, y, z))) - }) - .flatten() - .flatten() - .for_each(|pos| { - if let Ok(vox) = self.get(pos) { - f(pos, *vox); + for x in aabb.min.x..aabb.max.x + 1 { + for y in aabb.min.y..aabb.max.y + 1 { + for z in aabb.min.z..aabb.max.z + 1 { + if let Ok(block) = self.get(Vec3::new(x, y, z)) { + f(Vec3::new(x, y, z), *block); + } } - }); + } + } } } From 8837dc81d512d3ee478b51923805bd908ff516bd Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 10 Oct 2021 02:31:53 -0400 Subject: [PATCH 11/12] Rearrange iteration pattern so that x is in the inner loop in the default for_each_in (no or little perf change) --- common/src/vol.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/vol.rs b/common/src/vol.rs index 4d1d4a305e..1e008f4e71 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -119,9 +119,9 @@ pub trait ReadVol: BaseVol { where Self::Vox: Copy, { - for x in aabb.min.x..aabb.max.x + 1 { + for z in aabb.min.z..aabb.max.z + 1 { for y in aabb.min.y..aabb.max.y + 1 { - for z in aabb.min.z..aabb.max.z + 1 { + for x in aabb.min.x..aabb.max.x + 1 { if let Ok(block) = self.get(Vec3::new(x, y, z)) { f(Vec3::new(x, y, z), *block); } From f96a8a11abc74395c6dd3ba33bde2f35d5eff23d Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 10 Oct 2021 02:50:21 -0400 Subject: [PATCH 12/12] Remove dead commented code, updated changelog, commented profiling spans (but left in for easy reuse later), fix clippy complaint --- CHANGELOG.md | 1 + common/src/vol.rs | 1 - common/src/volumes/vol_grid_2d.rs | 10 ++-------- common/systems/src/phys.rs | 22 +++++++++++----------- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6fc0d1ffe..21a7fd415c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Sprite spawn rates - The Interact button can be used on campfires to sit - Made map icons fade out when near the edge of the map display +- Roughly doubled the speed of entity vs terrain physics checks ### Removed diff --git a/common/src/vol.rs b/common/src/vol.rs index 1e008f4e71..42aeecf0be 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -114,7 +114,6 @@ pub trait ReadVol: BaseVol { /// Call provided closure with each block in the supplied Aabb /// Portions of the Aabb outside the volume are ignored - //#[inline] fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, Self::Vox)) where Self::Vox: Copy, diff --git a/common/src/volumes/vol_grid_2d.rs b/common/src/volumes/vol_grid_2d.rs index b6fac1630f..49c91f00fc 100644 --- a/common/src/volumes/vol_grid_2d.rs +++ b/common/src/volumes/vol_grid_2d.rs @@ -54,9 +54,8 @@ impl ReadVol for VolGrid2d { }) } - // /// Call provided closure with each block in the supplied Aabb - // /// Areas outside loaded chunks are ignored - #[inline] + /// Call provided closure with each block in the supplied Aabb + /// Areas outside loaded chunks are ignored fn for_each_in(&self, aabb: Aabb, mut f: impl FnMut(Vec3, Self::Vox)) where Self::Vox: Copy, @@ -155,7 +154,6 @@ impl VolGrid2d { #[inline(always)] pub fn chunk_size() -> Vec2 { V::RECT_SIZE } - //#[inline] pub fn insert(&mut self, key: Vec2, chunk: Arc) -> Option> { self.chunks.insert(key, chunk) } @@ -165,14 +163,12 @@ impl VolGrid2d { self.chunks.get(&key).map(|arc_chunk| arc_chunk.as_ref()) } - //#[inline] pub fn get_key_arc(&self, key: Vec2) -> Option<&Arc> { self.chunks.get(&key) } pub fn clear(&mut self) { self.chunks.clear(); } pub fn drain(&mut self) -> hash_map::Drain, Arc> { self.chunks.drain() } - //#[inline] pub fn remove(&mut self, key: Vec2) -> Option> { self.chunks.remove(&key) } #[inline(always)] @@ -181,14 +177,12 @@ impl VolGrid2d { #[inline(always)] pub fn pos_key(&self, pos: Vec3) -> Vec2 { Self::chunk_key(pos) } - //#[inline] pub fn iter(&self) -> ChunkIter { ChunkIter { iter: self.chunks.iter(), } } - //#[inline] pub fn cached(&self) -> CachedVolGrid2d { CachedVolGrid2d::new(self) } } diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index f40846b582..77c9a509b5 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -1342,7 +1342,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( clippy::cast_possible_truncation, clippy::cast_sign_loss )] - prof_span!("box_voxel_collision"); + //prof_span!("box_voxel_collision"); // Convience function to compute the player aabb fn player_aabb(pos: Vec3, radius: f32, z_range: Range) -> Aabb { @@ -1362,9 +1362,9 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( // Function for determining whether the player at a specific position collides // with blocks with the given criteria - fn collision_with<'a, T: BaseVol + ReadVol>( + fn collision_with + ReadVol>( pos: Vec3, - terrain: &'a T, + terrain: &T, hit: impl Fn(&Block) -> bool, near_aabb: Aabb, radius: f32, @@ -1430,12 +1430,12 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( .clamped(1, MAX_INCREMENTS); let old_pos = pos.0; for _ in 0..increments { - prof_span!("increment"); + //prof_span!("increment"); const MAX_ATTEMPTS: usize = 16; pos.0 += pos_delta / increments as f32; let try_colliding_block = |pos: &Pos| { - prof_span!("most colliding check"); + //prof_span!("most colliding check"); // Calculate the player's AABB let player_aabb = player_aabb(pos.0, radius, z_range.clone()); @@ -1530,7 +1530,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( && dir.z < -0.1 // ...and the space above is free... && { - prof_span!("space above free"); + //prof_span!("space above free"); !collision_with( Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &terrain, @@ -1542,7 +1542,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( } // ...and there is a collision with a block beneath our current hitbox... && { - prof_span!("collision beneath"); + //prof_span!("collision beneath"); collision_with( pos.0 + resolve_dir - Vec3::unit_z() * 1.25, &terrain, @@ -1596,7 +1596,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( physics_state.on_ground = on_ground; // If the space below us is free, then "snap" to the ground } else if vel.0.z <= 0.0 && was_on_ground && block_snap && { - prof_span!("snap check"); + //prof_span!("snap check"); collision_with( pos.0 - Vec3::unit_z() * 1.1, &terrain, @@ -1606,7 +1606,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( z_range.clone(), ) } { - prof_span!("snap!!"); + //prof_span!("snap!!"); let snap_height = terrain .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.1).map(|e| e.floor() as i32)) .ok() @@ -1643,7 +1643,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( let mut liquid = None::<(LiquidKind, f32)>; let mut wall_dir_collisions = [false; 4]; - prof_span!(guard, "liquid/walls"); + //prof_span!(guard, "liquid/walls"); terrain.for_each_in(near_aabb, |block_pos, block| { // Check for liquid blocks if let Some(block_liquid) = block.liquid_kind() { @@ -1680,7 +1680,7 @@ fn box_voxel_collision<'a, T: BaseVol + ReadVol>( } } }); - drop(guard); + //drop(guard); // Use wall collision results to determine if we are against a wall let mut on_wall = None;