feat(content): S1 exact CellStruct surface-index construction, recipe 8
Campaign OVERHAUL S1 chunk A. Retail's D3DPolyRender::ConstructMesh @0x0059DFA0 is ported as one pure Core descriptor plus the Content extraction that consumes it: - side candidates come only from sides_type (0/1/2); NoPos/NoNeg mean UV-array absence only and never suppress a side (CPolygon::UnPack @0x00538650); - ST_DOUBLE's second copy is reversed with a negative normal; ST_BOTH's negative side has a negative normal and forward fan order (reverse is on the copy ordinal, not the side ordinal); - an absent UV-index array is UV index 0 (ConstructMesh @0x0059E691 xor ebx,ebx, arbitrated on the PDB-paired binary); copyVert @0x0059C080 zeroes coordinates only for a negative or out-of-range index or a vertex without UVs, never by clamping to slot 0; - the subset owner is the source surface-array index, emitted in ascending slot order with retail's per-slot mask (2 > 8 > 4 precedence, positive surface OR on signed stippling > 0); - built-EnvCell admission is (Surface.Type & (BASE1_IMAGE|BASE1_CLIPMAP)) != 0 after surface resolution (DrawEnvCell @0x0059F170 -> DrawMesh @0x0059D4A0 arg4=1); untextured slots are constructed but not emitted; - cell batches carry SourceSurfaceIndex, RetailSurfaceMask, RawSurfaceType, IsCellShell, and fixed clockwise raster cull (RenderMeshSubset @0x0059CA10); authored sides_type is no longer stored as GPU cull. Prepared-mesh serializer gains the four fields; bake recipe 7 -> 8 with a FullRebuild migration; pak format stays 2 (pinned). Ordinary GfxObj extraction is unchanged. AP-234's register row and CellMesh unification land in chunk B. Core: 32 descriptor tests. Content: 170/170. Bake: 18/18. Launcher.Core: 365/365 (Lane!=Linux). Solution Release build 0 warnings / 0 errors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
14d8fe6478
commit
acf172469e
15 changed files with 1746 additions and 296 deletions
|
|
@ -5,6 +5,7 @@ using DatReaderWriter.DBObjs;
|
|||
using DatReaderWriter.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using BoundingBox = Chorizite.Core.Lib.BoundingBox;
|
||||
|
|
@ -143,6 +144,23 @@ public class MeshBatchData {
|
|||
/// <summary>
|
||||
/// CPU-side texture info for deduplication during background preparation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// OH2/S1 (docs/research/2026-09-01-overhaul/oh2-cellstruct-surface-contract.md):
|
||||
/// for a CellStruct/EnvCell shell batch (<see cref="IsCellShell"/> true),
|
||||
/// <see cref="CullMode"/> is FIXED retail raster state applied AFTER
|
||||
/// geometry has already been fan-expanded per side/copy candidate —
|
||||
/// <c>RenderMeshSubset</c> @0x0059CA10 always draws a constructed cell
|
||||
/// shell subset <c>D3DCULL_CW</c> — it is NOT the authored
|
||||
/// <c>Polygon.SidesType</c> any more. The subset/material OWNER for a cell
|
||||
/// batch is instead <see cref="SourceSurfaceIndex"/>, the retail source
|
||||
/// surface-array index (contract §3.6): two cell batches can share a
|
||||
/// resolved Surface DID/<see cref="TextureKey"/> and still be two distinct
|
||||
/// subsets, or vice versa. For an ordinary GfxObj batch,
|
||||
/// <see cref="CullMode"/> keeps its historical meaning and the four
|
||||
/// cell-only fields below stay at their neutral (non-cell) defaults —
|
||||
/// <see cref="AcDream.Content.MeshExtractor.PrepareGfxObjMeshData"/> never
|
||||
/// sets them.
|
||||
/// </remarks>
|
||||
public class TextureBatchData {
|
||||
public TextureKey Key { get; set; }
|
||||
public byte[] TextureData { get; set; } = Array.Empty<byte>();
|
||||
|
|
@ -155,4 +173,73 @@ public class TextureBatchData {
|
|||
public bool IsTransparent { get; set; }
|
||||
public bool IsAdditive { get; set; }
|
||||
public bool HasWrappingUVs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retail source surface-array index this subset was constructed from
|
||||
/// (contract §3.6) — the CellStruct subset/material OWNER, not the
|
||||
/// resolved Surface DID or texture identity. -1 for a non-cell (GfxObj)
|
||||
/// batch, where this concept does not apply.
|
||||
/// </summary>
|
||||
public int SourceSurfaceIndex { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Retail's <c>isStippledOrAlphaedMask</c> byte for this surface slot
|
||||
/// (contract §3.2): <c>D3DPolyRender::ConstructMesh</c> @0x0059DFA0's
|
||||
/// per-surface initial mask
|
||||
/// (<see cref="CellStructSideCandidates.InitialSurfaceMask"/>) plus
|
||||
/// every polygon's positive-surface stippling OR
|
||||
/// (<see cref="CellStructSideCandidates.ApplyStipplingMaskBit"/>). 0
|
||||
/// (unused) for a non-cell (GfxObj) batch.
|
||||
/// </summary>
|
||||
public byte RetailSurfaceMask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The resolved cell surface's raw <c>Surface.Type</c> bits (contract
|
||||
/// §2.2) — evidence for the built-EnvCell
|
||||
/// <c>(Type & (BASE1_IMAGE|BASE1_CLIPMAP)) != 0</c> admission
|
||||
/// decision (<c>RenderDeviceD3D::DrawEnvCell</c> @0x0059F170 →
|
||||
/// <c>D3DPolyRender::DrawMesh</c> @0x0059D4A0, contract §4) that
|
||||
/// already happened before this batch was ever emitted into
|
||||
/// <see cref="ObjectMeshData.TextureBatches"/>. 0 (unused) for a
|
||||
/// non-cell (GfxObj) batch.
|
||||
/// </summary>
|
||||
public uint RawSurfaceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True for a CellStruct/EnvCell shell batch built by
|
||||
/// <see cref="AcDream.Content.MeshExtractor.PrepareCellStructMeshData"/>.
|
||||
/// Ordinary GfxObj batches leave this false.
|
||||
/// </summary>
|
||||
public bool IsCellShell { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OH2/S1 chunk-2 (contract §9 item 6): the one place that recovers a
|
||||
/// prepared CellStruct mesh's surface-array-index subset order.
|
||||
/// <see cref="ObjectMeshData.TextureBatches"/> groups
|
||||
/// <see cref="TextureBatchData"/> by (Width, Height, Format) for
|
||||
/// atlas/texture-dedup STORAGE only (contract §3.6 point 5) — the
|
||||
/// SEMANTIC subset order is always ascending
|
||||
/// <see cref="TextureBatchData.SourceSurfaceIndex"/>, recoverable
|
||||
/// independent of that storage grouping or of dictionary/list iteration
|
||||
/// order. Downstream consumers (App draw dispatch, later OVERHAUL slices
|
||||
/// such as OH7's ordered draw stream) must read cell subset order through
|
||||
/// this helper rather than iterating <c>TextureBatches</c> directly.
|
||||
/// </summary>
|
||||
public static class CellSurfaceSubsets {
|
||||
/// <summary>
|
||||
/// Every <see cref="TextureBatchData"/> in <paramref name="mesh"/> with
|
||||
/// <see cref="TextureBatchData.IsCellShell"/> set, ordered ascending by
|
||||
/// <see cref="TextureBatchData.SourceSurfaceIndex"/>. An untextured
|
||||
/// slot that retail's built-EnvCell admission skipped
|
||||
/// (<see cref="AcDream.Core.Meshing.RetailUntexturedSurfacePolicy"/>)
|
||||
/// was never emitted into <see cref="ObjectMeshData.TextureBatches"/>
|
||||
/// in the first place, so it is absent here too — this enumerates
|
||||
/// DRAWABLE subsets, not every constructed slot.
|
||||
/// </summary>
|
||||
public static IEnumerable<TextureBatchData> InAscendingSurfaceOrder(ObjectMeshData mesh) =>
|
||||
mesh.TextureBatches.Values
|
||||
.SelectMany(batches => batches)
|
||||
.Where(batch => batch.IsCellShell)
|
||||
.OrderBy(batch => batch.SourceSurfaceIndex);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue