feat(streaming): load full column bedrock->surface to fix terrain holes
This commit is contained in:
@@ -18,10 +18,11 @@ public sealed class ChunkStreamer : IDisposable
|
||||
private readonly IWorld _world;
|
||||
private readonly Func<Vector3i, VoxelChunk> _generate;
|
||||
private readonly Func<int, int, int> _surfaceHeightAt;
|
||||
// Per chunk-column (cx,cz) surface (min,max) height range, memoized so each
|
||||
// column's samples are taken once and reused across ticks. Pruned to the
|
||||
// horizontal window each recompute so it stays bounded.
|
||||
private readonly Dictionary<(int cx, int cz), (int min, int max)> _surfaceRangeCache = new();
|
||||
// Per chunk-column (cx,cz) surface-TOP height, memoized so each column's
|
||||
// samples are taken once and reused across ticks. Pruned to the horizontal
|
||||
// window each recompute so it stays bounded. Full-column loading only needs
|
||||
// the top; the bottom is always bedrock (cy 0).
|
||||
private readonly Dictionary<(int cx, int cz), int> _surfaceTopCache = new();
|
||||
private readonly IChunkMesher _mesher;
|
||||
private readonly BlockRegistry _registry;
|
||||
private readonly IJobExecutor _executor;
|
||||
@@ -156,42 +157,43 @@ public sealed class ChunkStreamer : IDisposable
|
||||
this.PruneSurfaceCache(p);
|
||||
}
|
||||
|
||||
// Drop cached surface ranges for columns outside the current horizontal
|
||||
// Drop cached surface tops for columns outside the current horizontal
|
||||
// window so the cache stays bounded as the player explores.
|
||||
private void PruneSurfaceCache(Vector3i p)
|
||||
{
|
||||
int keep = this._cfg.RenderDistanceHorizontal + 1;
|
||||
List<(int cx, int cz)>? stale = null;
|
||||
foreach (var key in this._surfaceRangeCache.Keys)
|
||||
foreach (var key in this._surfaceTopCache.Keys)
|
||||
{
|
||||
if (Math.Abs(key.cx - p.X) > keep || Math.Abs(key.cz - p.Z) > keep)
|
||||
(stale ??= new()).Add(key);
|
||||
}
|
||||
if (stale != null)
|
||||
foreach (var key in stale) this._surfaceRangeCache.Remove(key);
|
||||
foreach (var key in stale) this._surfaceTopCache.Remove(key);
|
||||
}
|
||||
|
||||
// Fills `into` with the chunk positions to load for a viewer at chunk
|
||||
// `center`, over a square horizontal region of Chebyshev radius `radiusH`.
|
||||
// Per column the loaded Y-range is the union of the surface shell and the
|
||||
// player slab, clamped to the world's vertical bounds.
|
||||
// Per column the full vertical stack from bedrock (cy 0) up to the
|
||||
// surface-top chunk is loaded, plus any section above the surface holding
|
||||
// player edits (e.g. a built tower).
|
||||
private void CollectTargets(Vector3i center, int radiusH, HashSet<Vector3i> into)
|
||||
{
|
||||
int v = this._cfg.RenderDistanceVertical;
|
||||
int skirt = this._cfg.SurfaceShellSkirtBelow;
|
||||
|
||||
for (int dx = -radiusH; dx <= radiusH; dx++)
|
||||
for (int dz = -radiusH; dz <= radiusH; dz++)
|
||||
{
|
||||
int cx = center.X + dx;
|
||||
int cz = center.Z + dz;
|
||||
|
||||
(int minH, int maxH) = this.SurfaceHeightRange(cx, cz);
|
||||
int shellBot = ChunkMath.FloorDiv(minH, VoxelChunk.Size) - skirt;
|
||||
int shellTop = ChunkMath.FloorDiv(maxH, VoxelChunk.Size);
|
||||
ChunkStreamer.AddColumn(into, cx, cz, shellBot, shellTop);
|
||||
int surfaceTop = ChunkMath.FloorDiv(this.SurfaceTopHeight(cx, cz), VoxelChunk.Size);
|
||||
ChunkStreamer.AddColumn(into, cx, cz, 0, surfaceTop);
|
||||
|
||||
ChunkStreamer.AddColumn(into, cx, cz, center.Y - v, center.Y + v);
|
||||
int clampedTop = Math.Min(WorldConstants.WorldHeightChunks - 1, surfaceTop);
|
||||
for (int cy = clampedTop + 1; cy < WorldConstants.WorldHeightChunks; cy++)
|
||||
{
|
||||
var pos = new Vector3i(cx, cy, cz);
|
||||
if (this._world.ModificationStore.HasDiff(pos)) into.Add(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,11 +205,13 @@ public sealed class ChunkStreamer : IDisposable
|
||||
into.Add(new Vector3i(cx, cy, cz));
|
||||
}
|
||||
|
||||
// Min/max terrain surface height across a chunk column's 16×16 footprint,
|
||||
// sampled at the four corners and the centre (catches slope spread cheaply).
|
||||
private (int min, int max) SurfaceHeightRange(int cx, int cz)
|
||||
// Max terrain surface height across a chunk column's 16x16 footprint,
|
||||
// sampled at the four corners and the centre (cheap bound on the column's
|
||||
// top). Cached per column. The bottom of the loaded stack is always bedrock,
|
||||
// so only the top is needed.
|
||||
private int SurfaceTopHeight(int cx, int cz)
|
||||
{
|
||||
if (this._surfaceRangeCache.TryGetValue((cx, cz), out (int min, int max) cached))
|
||||
if (this._surfaceTopCache.TryGetValue((cx, cz), out int cached))
|
||||
return cached;
|
||||
|
||||
int x0 = cx * VoxelChunk.Size;
|
||||
@@ -217,18 +221,13 @@ public sealed class ChunkStreamer : IDisposable
|
||||
int xm = x0 + VoxelChunk.Size / 2;
|
||||
int zm = z0 + VoxelChunk.Size / 2;
|
||||
|
||||
int h0 = this._surfaceHeightAt(x0, z0);
|
||||
int h1 = this._surfaceHeightAt(x1, z0);
|
||||
int h2 = this._surfaceHeightAt(x0, z1);
|
||||
int h3 = this._surfaceHeightAt(x1, z1);
|
||||
int h4 = this._surfaceHeightAt(xm, zm);
|
||||
int max = Math.Max(
|
||||
Math.Max(this._surfaceHeightAt(x0, z0), this._surfaceHeightAt(x1, z0)),
|
||||
Math.Max(Math.Max(this._surfaceHeightAt(x0, z1), this._surfaceHeightAt(x1, z1)),
|
||||
this._surfaceHeightAt(xm, zm)));
|
||||
|
||||
int min = Math.Min(Math.Min(h0, h1), Math.Min(Math.Min(h2, h3), h4));
|
||||
int max = Math.Max(Math.Max(h0, h1), Math.Max(Math.Max(h2, h3), h4));
|
||||
|
||||
(int min, int max) range = (min, max);
|
||||
this._surfaceRangeCache[(cx, cz)] = range;
|
||||
return range;
|
||||
this._surfaceTopCache[(cx, cz)] = max;
|
||||
return max;
|
||||
}
|
||||
|
||||
private void DrainCompleted()
|
||||
|
||||
Reference in New Issue
Block a user