using System;
using System.Collections.Generic;
using AcDream.Core.Content;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Types;
namespace AcDream.Core.Meshing;
///
/// OH2/S1 chunk B (docs/research/2026-09-01-overhaul/oh2-cellstruct-surface-contract.md
/// §9 item 5): retires this type's former render-production role. The old
/// Build method carried its own NoPos-based "skip untextured cell
/// geometry" approximation (AP-234, retired) — a second, divergent
/// interpretation of the retail CellStruct surface/subset construction
/// algorithm alongside MeshExtractor.PrepareCellStructMeshData, which
/// is now the ONE production interpretation (contract §9 item 5: "Core and
/// Content do not retain divergent CellStruct interpretations"). This class
/// now exposes only : the exact retail
/// predicate for whether a CellStruct contributes at least one drawable
/// subset, needed by the streaming build job
/// (AcDream.App.Streaming.LandblockBuildFactory) to decide whether a
/// cell's shell has drawable geometry before it registers the cell — a
/// question answerable without building or retaining any geometry.
///
public static class CellMesh
{
///
/// True iff at least one of this CellStruct's polygon construction
/// candidates (,
/// ported from D3DPolyRender::ConstructMesh @0x0059DFA0, contract
/// §3.4) targets a surface slot whose resolved Surface.Type is
/// TEXTURED (
/// is false) — retail's built-EnvCell draw admission
/// (Surface.Type & (BASE1_IMAGE|BASE1_CLIPMAP)) != 0
/// (RenderDeviceD3D::DrawEnvCell @0x0059F170 →
/// D3DPolyRender::DrawMesh(..., arg4=1) @0x0059D4A0, contract §4),
/// evaluated without resolving the surface's texture dependency chain.
/// It is therefore a conservative superset of what
/// MeshExtractor.PrepareCellStructMeshData actually emits: the
/// extractor additionally drops a textured slot whose SurfaceTexture,
/// RenderSurface, palette, or pixel format cannot be resolved (an
/// extractor-side quarantine outcome, logged there), which this predicate
/// still reports as drawable. Missing texture data is a DAT defect, not a
/// retail admission decision, so the predicate follows the admission rule.
///
///
/// Side candidates come only from CPolygon::sides_type
/// (contract §3.4); NoPos/NoNeg play no role in this
/// predicate — they mean "this side's UV-index array is absent"
/// (contract §2.3/§3.5), which cannot change whether a subset draws,
/// only what its texture coordinates are. This predicate therefore
/// never reads a polygon's UV-index arrays.
///
///
/// The EnvCell whose ordered surface-override array
/// () resolves each candidate's source
/// surface-array slot to a qualified Surface DAT id, exactly like
/// MeshExtractor.PrepareCellStructMeshData's
/// surfaceOverrides parameter.
///
/// The CellStruct containing the polygon geometry.
/// DAT object source used to resolve each candidate's surface Surface.Type.
public static bool HasDrawableGeometry(EnvCell envCell, CellStruct cellStruct, IDatObjectSource dats)
{
// One resolve per distinct surface slot, not per polygon: a slot's
// textured-ness is a per-slot fact (contract §3.2's per-surface
// mask/type), and re-resolving the same Surface DAT record for
// every polygon that references it would be wasted DAT I/O on the
// streaming worker thread that calls this predicate.
var slotIsTextured = new Dictionary();
bool SlotIsTextured(int slot)
{
if (slotIsTextured.TryGetValue(slot, out bool cached))
return cached;
bool textured = false;
if (slot >= 0 && slot < envCell.Surfaces.Count)
{
uint surfaceId = 0x08000000u | envCell.Surfaces[slot];
if (dats.Get(surfaceId) is { } surface)
textured = !RetailUntexturedSurfacePolicy.IsUntextured(surface.Type);
}
slotIsTextured[slot] = textured;
return textured;
}
foreach (var poly in cellStruct.Polygons.Values)
{
// Same degenerate-fan gate as MeshExtractor.PrepareCellStructMeshData.
if (poly.VertexIds.Count < 3) continue;
ReadOnlySpan candidates =
CellStructSideCandidates.GetCandidates((int)poly.SidesType);
foreach (var candidate in candidates)
{
short surfaceIdxRaw = candidate.SurfaceSlot == CellStructPolygonSurfaceSide.Positive
? poly.PosSurface
: poly.NegSurface;
if (surfaceIdxRaw < 0) continue;
if (SlotIsTextured(surfaceIdxRaw))
return true;
}
}
return false;
}
}