From c22636a4b8b2832a248802a3ff4d8e00f2b228ee Mon Sep 17 00:00:00 2001
From: Imbris <imbrisf@gmail.com>
Date: Thu, 25 Mar 2021 00:38:02 -0400
Subject: [PATCH] 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<V, S: RectVolSize, M: Clone> ReadVol for Chonk<V, S, M> {
                 .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<i32>, mut f: impl FnMut(Vec3<i32>, 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<V: Clone + PartialEq, S: RectVolSize, M: Clone> WriteVol for Chonk<V, S, M> {
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<i32>, mut f: impl FnMut(Vec3<i32>, 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<V: RectRasterableVol + ReadVol + Debug> ReadVol for VolGrid2d<V> {
                 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<i32>, mut f: impl FnMut(Vec3<i32>, 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<V: RectRasterableVol> VolGrid2d<V> {
         }
     }
 
+    //#[inline]
     pub fn chunk_size() -> Vec2<u32> { V::RECT_SIZE }
 
+    //#[inline]
     pub fn insert(&mut self, key: Vec2<i32>, chunk: Arc<V>) -> Option<Arc<V>> {
         self.chunks.insert(key, chunk)
     }
 
+    //#[inline]
     pub fn get_key(&self, key: Vec2<i32>) -> Option<&V> {
         self.chunks.get(&key).map(|arc_chunk| arc_chunk.as_ref())
     }
 
+    //#[inline]
     pub fn get_key_arc(&self, key: Vec2<i32>) -> Option<&Arc<V>> { self.chunks.get(&key) }
 
     pub fn clear(&mut self) { self.chunks.clear(); }
 
     pub fn drain(&mut self) -> hash_map::Drain<Vec2<i32>, Arc<V>> { self.chunks.drain() }
 
+    //#[inline]
     pub fn remove(&mut self, key: Vec2<i32>) -> Option<Arc<V>> { self.chunks.remove(&key) }
 
+    //#[inline]
     pub fn key_pos(&self, key: Vec2<i32>) -> Vec2<i32> { key * V::RECT_SIZE.map(|e| e as i32) }
 
+    //#[inline]
     pub fn pos_key(&self, pos: Vec3<i32>) -> Vec2<i32> { Self::chunk_key(pos) }
 
+    //#[inline]
     pub fn iter(&self) -> ChunkIter<V> {
         ChunkIter {
             iter: self.chunks.iter(),
         }
     }
 
+    //#[inline]
     pub fn cached(&self) -> CachedVolGrid2d<V> { 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<Vox = Block> + 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<Vox = Block> + ReadVol>(
+    /*fn collision_iter<'a, T: BaseVol<Vox = Block> + ReadVol>(
         pos: Vec3<f32>,
         terrain: &'a T,
         hit: &'a impl Fn(&Block) -> bool,
@@ -1386,7 +1387,7 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + 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<Vox = Block> + ReadVol>(
         pos: Vec3<f32>,
         terrain: &'a T,
         hit: impl Fn(&Block) -> bool,
-        near_iter: impl Iterator<Item = (i32, i32, i32)> + 'a,
+        near_aabb: Aabb<i32>,
         radius: f32,
         z_range: Range<f32>,
     ) -> 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<Vox = Block> + 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::<Block>;
     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<Vox = Block> + 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<Vox = Block> + 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<f32>| {
+                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<Vox = Block> + 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<Vox = Block> + 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<Vox = Block> + 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<Vox = Block> + 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<Vox = Block> + 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<Vox = Block> + ReadVol>(
             }
         }
     });
+    drop(guard);
 
     // Use wall collision results to determine if we are against a wall
     let mut on_wall = None;