feat(streaming): load surface shell + player slab instead of vertical box

This commit is contained in:
MDietermann
2026-06-01 18:51:38 +02:00
parent 9daa9f8267
commit 43527e617b
4 changed files with 99 additions and 28 deletions

View File

@@ -17,6 +17,7 @@ public sealed class ChunkStreamer : IDisposable
private readonly Configuration _cfg;
private readonly IWorld _world;
private readonly Func<Vector3i, VoxelChunk> _generate;
private readonly Func<int, int, int> _surfaceHeightAt;
private readonly IChunkMesher _mesher;
private readonly BlockRegistry _registry;
private readonly IJobExecutor _executor;
@@ -51,6 +52,7 @@ public sealed class ChunkStreamer : IDisposable
Configuration cfg,
IWorld world,
Func<Vector3i, VoxelChunk> generate,
Func<int, int, int> surfaceHeightAt,
IChunkMesher mesher,
BlockRegistry registry,
IJobExecutor executor,
@@ -59,6 +61,7 @@ public sealed class ChunkStreamer : IDisposable
this._cfg = cfg;
this._world = world;
this._generate = generate;
this._surfaceHeightAt = surfaceHeightAt;
this._mesher = mesher;
this._registry = registry;
this._executor = executor;
@@ -104,18 +107,8 @@ public sealed class ChunkStreamer : IDisposable
this._spawnAnchorSet = true;
}
int h = this._cfg.RenderDistanceHorizontal;
int v = this._cfg.RenderDistanceVertical;
var target = new HashSet<Vector3i>();
for (int dx = -h; dx <= h; dx++)
for (int dy = -v; dy <= v; dy++)
for (int dz = -h; dz <= h; dz++)
{
int cy = p.Y + dy;
if (cy < 0 || cy >= WorldConstants.WorldHeightChunks) continue;
target.Add(new Vector3i(p.X + dx, cy, p.Z + dz));
}
this.CollectTargets(p, this._cfg.RenderDistanceHorizontal, target);
foreach (var pos in target)
{
@@ -145,6 +138,60 @@ public sealed class ChunkStreamer : IDisposable
}
}
// 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.
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);
ChunkStreamer.AddColumn(into, cx, cz, center.Y - v, center.Y + v);
}
}
private static void AddColumn(HashSet<Vector3i> into, int cx, int cz, int loCy, int hiCy)
{
int lo = Math.Max(0, loCy);
int hi = Math.Min(WorldConstants.WorldHeightChunks - 1, hiCy);
for (int cy = lo; cy <= hi; cy++)
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)
{
int x0 = cx * VoxelChunk.Size;
int z0 = cz * VoxelChunk.Size;
int x1 = x0 + VoxelChunk.Size - 1;
int z1 = z0 + VoxelChunk.Size - 1;
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 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));
return (min, max);
}
private void DrainCompleted()
{
int budget = this._cfg.MaxMeshUploadsPerFrame;
@@ -176,20 +223,17 @@ public sealed class ChunkStreamer : IDisposable
{
if (this._spawnReady || !this._spawnAnchorSet) return;
int hs = this._cfg.SpawnReadyRadiusHorizontal;
int v = this._cfg.RenderDistanceVertical;
Vector3i a = this._spawnAnchor;
var spawnTargets = new HashSet<Vector3i>();
this.CollectTargets(this._spawnAnchor, this._cfg.SpawnReadyRadiusHorizontal, spawnTargets);
for (int dx = -hs; dx <= hs; dx++)
for (int dy = -v; dy <= v; dy++)
for (int dz = -hs; dz <= hs; dz++)
foreach (var pos in spawnTargets)
{
int cy = a.Y + dy;
if (cy < 0 || cy >= WorldConstants.WorldHeightChunks) continue;
if (!this._ready.Contains(new Vector3i(a.X + dx, cy, a.Z + dz))) return;
if (this._failed.Contains(pos)) continue;
if (!this._ready.Contains(pos)) return;
}
this._spawnReady = true;
Log.Information("Spawn area ready at anchor {Anchor}", a);
Log.Information("Spawn area ready at anchor {Anchor}", this._spawnAnchor);
}
private void InstallResult(ChunkJobResult result)