feat(app): Phase A.2 — wire frustum culling into terrain + static-mesh renderers
Per-landblock AABB culling against the view frustum. Each loaded landblock has a 192×192 XY footprint + a Z range derived from the terrain vertex min/max (padded +50 above / -10 below for entities on top and basements). One AABB test per landblock per frame; landblocks fully outside the frustum skip ALL their terrain draws and entity draws (both opaque and translucent passes). GpuWorldState gains SetLandblockAabb + LandblockEntries (per-landblock iteration with AABB data). TerrainRenderer.Draw and StaticMeshRenderer.Draw both accept an optional FrustumPlanes and skip culled landblocks. GameWindow.OnRender extracts FrustumPlanes from camera.View * camera.Projection and passes to both. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
07fde88534
commit
3c9fc63af7
4 changed files with 156 additions and 64 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Streaming;
|
||||
|
|
@ -38,6 +39,7 @@ namespace AcDream.App.Streaming;
|
|||
public sealed class GpuWorldState
|
||||
{
|
||||
private readonly Dictionary<uint, LoadedLandblock> _loaded = new();
|
||||
private readonly Dictionary<uint, (Vector3 Min, Vector3 Max)> _aabbs = new();
|
||||
|
||||
/// <summary>
|
||||
/// Per-landblock buffer of live entities awaiting their landblock's
|
||||
|
|
@ -56,6 +58,34 @@ public sealed class GpuWorldState
|
|||
|
||||
public bool IsLoaded(uint landblockId) => _loaded.ContainsKey(landblockId);
|
||||
|
||||
/// <summary>
|
||||
/// Store the axis-aligned bounding box for a loaded landblock. Called from
|
||||
/// the render thread after the terrain mesh is built and uploaded.
|
||||
/// </summary>
|
||||
public void SetLandblockAabb(uint landblockId, Vector3 min, Vector3 max)
|
||||
{
|
||||
_aabbs[landblockId] = (min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-landblock iteration with AABB data for use by the frustum-culling
|
||||
/// draw path. Landblocks without a stored AABB yield <see cref="Vector3.Zero"/>
|
||||
/// for both corners, which the culler will conservatively treat as visible.
|
||||
/// </summary>
|
||||
public IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax, IReadOnlyList<WorldEntity> Entities)> LandblockEntries
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var kvp in _loaded)
|
||||
{
|
||||
if (_aabbs.TryGetValue(kvp.Key, out var aabb))
|
||||
yield return (kvp.Key, aabb.Min, aabb.Max, kvp.Value.Entities);
|
||||
else
|
||||
yield return (kvp.Key, Vector3.Zero, Vector3.Zero, kvp.Value.Entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Total live entities currently parked in the pending bucket waiting
|
||||
/// for their landblock to arrive. Useful diagnostic for verifying the
|
||||
|
|
@ -89,6 +119,7 @@ public sealed class GpuWorldState
|
|||
// pending spawns that arrived for it are no longer relevant. The
|
||||
// server will resend them via CreateObject when the player returns.
|
||||
_pendingByLandblock.Remove(landblockId);
|
||||
_aabbs.Remove(landblockId);
|
||||
|
||||
if (_loaded.Remove(landblockId))
|
||||
RebuildFlatView();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue