feat(render): Phase A8 Wave 1 — WB scaffolding extraction + stencil low-level method

Five tasks shipped together (interdependent at build time):

Task 1: WbRenderPass enum — verbatim port of WB RenderPass.cs:1-22
Task 2: WbFrustum + WbBoundingBox + FrustumTestResult — verbatim port
  of WB Frustum.cs (98 LOC) with namespace + BoundingBox-type adaptations.
  +7 unit tests.
Task 3: EnvCellSceneryInstance + EnvCellLandblock — verbatim port of WB
  SceneryInstance.cs:1-161, renamed scope-narrow. Dropped editor-only
  fields (DisqualificationReason, ParticleEmitters, IsQueuedForUpload,
  InstanceBufferOffset, InstanceCount, MdiCommands, IsTransformOnlyUpdate)
  + InstanceId narrowed uint (we don't use ObjectId's editor methods).
  +5 unit tests.
Task 4: EnvCellVisibilitySnapshot — direct port of WB VisibilitySnapshot
  narrowed to BatchedByCell + VisibleLandblocks only.
Task 7: IndoorCellStencilPipeline.RenderBuildingStencilMask — new
  low-level WB-faithful entry mirroring PortalRenderManager:471-484.
  No surrounding GL state setup (caller's responsibility). Probe fields
  LastStencilVertexCount / LastStencilWasFarPunch / LastStencilBuildingId
  for the [stencil] probe emitter in Task 9.

Build green, 18 tests pass (7 new Frustum + 5 new SceneryInstance + 6
existing stencil pipeline). Ready for Wave 2 (EnvCellRenderer port).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-27 14:46:07 +02:00
parent 95f0d5267b
commit fc68d6d01f
7 changed files with 595 additions and 0 deletions

View file

@ -0,0 +1,35 @@
using System.Collections.Generic;
namespace AcDream.App.Rendering.Wb;
/// <summary>
/// Phase A8 (2026-05-28): EnvCell-scoped visibility snapshot. Direct port of
/// WB's <c>VisibilitySnapshot</c> at
/// <c>references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilitySnapshot.cs:1-36</c>,
/// narrowed to the fields <see cref="EnvCellRenderer"/> actually consumes
/// (<c>BatchedByCell</c> + <c>VisibleLandblocks</c>). The scenery-side
/// <c>VisibleGroups</c> / <c>VisibleGfxObjIds</c> / <c>IntersectingLandblocks</c>
/// / <c>PostPreparePoolIndex</c> are dropped — we render scenery through
/// <see cref="WbDrawDispatcher"/>, not through this snapshot.
///
/// <para>Used as an immutable snapshot atomically swapped under the
/// renderer's render lock so PrepareRenderBatches (worker-driven) and
/// Render (render-thread-driven) can't race on a half-populated dict.</para>
/// </summary>
public sealed class EnvCellVisibilitySnapshot
{
/// <summary>Landblocks fully or partially inside the frustum at prepare time.</summary>
public List<EnvCellLandblock> VisibleLandblocks { get; init; } = new();
/// <summary>
/// Grouped instance data by full 32-bit cell id.
/// Outer key: <c>CellId</c>. Inner key: <c>GfxObjId</c> (ulong; bit 33 set for
/// deduplicated cell geometry per <see cref="EnvCellRenderer.GetEnvCellGeomId"/>).
/// Value: list of per-instance transforms (one per cell or per static object
/// inside that cell).
/// </summary>
public Dictionary<uint, Dictionary<ulong, List<InstanceData>>> BatchedByCell { get; init; } = new();
/// <summary>True when no visible cells were produced this prepare cycle.</summary>
public bool IsEmpty => VisibleLandblocks.Count == 0 && BatchedByCell.Count == 0;
}