acdream/src/AcDream.App/Rendering/Wb/Building.cs
Erik f125fdb220 feat(render): Phase A8 RR3 — Building + BuildingRegistry + BuildingLoader
New per-landblock data model for WB-style per-building cell scoping:

  Building            — BuildingId, EnvCellIds, ExitPortalPolygons,
                        occlusion-query state (Step 5 lifecycle)
  BuildingRegistry    — two-way indexed (by cellId + by buildingId);
                        single source of truth per landblock
  BuildingLoader      — static factory from LandBlockInfo.Buildings;
                        walks interior portals to expand cell sets;
                        collects exit portal polygons in world space

10 new unit tests cover data invariants + registry indexing + loader
mapping per the algorithm resolved in RR2 findings.

LoadedCell.BuildingId stamping wired in RR4. Render-time consumption
arrives in RR7 (Steps 1-4) + RR9 (Step 5) + RR11 (RenderOutsideIn).

Design: docs/superpowers/specs/2026-05-26-phase-a8-wb-full-port-design.md
Spike: docs/research/2026-05-26-a8-buildings-data-shape.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:08:43 +02:00

57 lines
2.9 KiB
C#

using System.Collections.Generic;
using System.Numerics;
namespace AcDream.App.Rendering.Wb;
/// <summary>
/// Phase A8 (2026-05-26): a logical building — one or more EnvCells linked
/// via the dat-level <c>LandBlockInfo.Buildings</c> entry. Building shells (cottage
/// walls, inn walls — <c>IsBuildingShell=true</c> entities) render unconditionally
/// when the camera is inside this building's cells. The exit portal polygons
/// are stencil-marked so outdoor visibility leaks through portal silhouettes
/// only.
///
/// <para>Step 5 (cross-building visibility via 3-stencil-bit pipeline) uses
/// the occlusion-query state to skip rendering when the building's portals
/// weren't visible last frame.</para>
///
/// <para>WB reference: <c>WorldBuilder.Shared/Services/PortalService.cs</c>
/// (<c>BuildingPortalGroup</c>) and <c>PortalRenderManager.cs</c> step-5 lifecycle.
/// Retail reference: <c>docs/research/named-retail/acclient.h:32035</c>
/// (<c>BuildInfo</c>) + <c>32094</c> (<c>CBldPortal</c>).</para>
/// </summary>
public sealed class Building
{
/// <summary>Unique within a landblock; allocated sequentially by <see cref="BuildingLoader"/>
/// starting at 1 (0 is reserved for "no building" semantics on <c>LoadedCell</c>).</summary>
public required uint BuildingId { get; init; }
/// <summary>The EnvCells this building owns. Includes all cells reachable
/// from the building's entry portals via interior portals (no exit portals).
/// Populated by <see cref="BuildingLoader.Build"/> via BFS over
/// <see cref="AcDream.App.Rendering.LoadedCell.Portals"/>.</summary>
public required HashSet<uint> EnvCellIds { get; init; }
/// <summary>Exit portal polygons in world space (each polygon is a triangle
/// fan from vertex 0). Stencil-marked + far-depth-punched at Steps 1+2 of
/// WB's RenderInsideOut pipeline (RR7). Collected during
/// <see cref="BuildingLoader.Build"/> Step C by transforming cell-local portal
/// polygon vertices via <see cref="AcDream.App.Rendering.LoadedCell.WorldTransform"/>.</summary>
public required IReadOnlyList<Vector3[]> ExitPortalPolygons { get; init; }
// -------------------------------------------------------------------------
// Step 5 occlusion-query state (mutable, per-frame, RR9 scope).
// -------------------------------------------------------------------------
/// <summary>GL query object handle; lazily created on first use by the
/// Step 5 occlusion-query pass (RR9). 0 = not yet created.</summary>
public uint QueryId;
/// <summary>True after the first <c>BeginQuery</c> call; controls whether the
/// read-back path is safe to execute on the next frame.</summary>
public bool QueryStarted;
/// <summary>Previous-frame query result. When false, the building's interior
/// render is skipped (Step 5 early-out in RR9 + RR11).</summary>
public bool WasVisible;
}