Carry TerrainTex.TexTiling through the terrain atlas and upload a layer-indexed table to the modern bindless shader so base, overlay, and road textures repeat at retail scale. Keep alpha masks cell-scaled and preserve retail's verified source-level-zero high-detail selection. Add the named-retail pseudocode, WorldBuilder/ACE/ACME cross-reference, adapter conformance tests, and inventory documentation so a future renderer migration keeps the contract. Co-Authored-By: Codex <noreply@openai.com>
40 lines
1.4 KiB
C#
40 lines
1.4 KiB
C#
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// Builds the layer-indexed adapter table consumed by
|
|
/// <c>terrain_modern.frag</c>. Retail passes <c>TerrainTex::tex_tiling</c>
|
|
/// directly to <c>ImgTex::TileCSI</c> / <c>ImgTex::MergeTexture</c>
|
|
/// (`TexMerge::CopyAndTile` 0x00503580, `TexMerge::Merge` 0x005038C0).
|
|
/// The table is the modern texture-array equivalent of that per-texture
|
|
/// argument.
|
|
/// </summary>
|
|
internal static class TerrainTextureTilingTable
|
|
{
|
|
// WorldBuilder's extracted terrain-array path uses the same 36-layer
|
|
// contract. Packed terrain layer indices are validated against this
|
|
// capacity before the shader table is built.
|
|
internal const int LayerCapacity = 36;
|
|
|
|
internal static float[] Build(IEnumerable<(uint Layer, uint RepeatCount)> entries)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(entries);
|
|
|
|
var table = new float[LayerCapacity];
|
|
Array.Fill(table, 1f);
|
|
|
|
foreach (var (layer, repeatCount) in entries)
|
|
{
|
|
if (layer >= LayerCapacity)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Terrain atlas layer {layer} exceeds the shader capacity of {LayerCapacity} layers.");
|
|
}
|
|
|
|
// Preserve the dat value exactly. Retail forwards the unsigned
|
|
// field without normalizing it in both CopyAndTile and Merge.
|
|
table[layer] = repeatCount;
|
|
}
|
|
|
|
return table;
|
|
}
|
|
}
|