feat(core+app): alpha atlas loading (Phase 3c.2)
Loads AC's terrain blending alpha masks into a second GL_TEXTURE_2D_ARRAY
alongside the existing terrain atlas. The alpha atlas is built but not
yet sampled by any shader — that wiring lands in Phase 3c.4.
SurfaceDecoder additions:
- Handles PFID_A8 (generic single-byte-alpha) by replicating each
alpha byte into all four RGBA channels
- Same branch handles PFID_CUSTOM_LSCAPE_ALPHA (0xF4), AC's landscape-
specific alpha format — the bit layout is identical, just a different
format ID to distinguish the asset class in the dats. I only found
this by adding a diagnostic in the first iteration (initial attempt
returned Magenta for every alpha map because I only wired PFID_A8)
- 3 new tests: 2x2 A8 round-trip, short-source fallback, and a
CUSTOM_LSCAPE_ALPHA test verifying it's routed through the same path
TerrainAtlas additions:
- New GlAlphaTexture property plus CornerAlphaLayers / SideAlphaLayers
/ RoadAlphaLayers index lists so the coming BuildSurface port can
cite atlas layers by source category
- BuildAlphaAtlas walks TexMerge.CornerTerrainMaps, SideTerrainMaps,
RoadMaps and uploads each decoded mask as a layer in insertion
order; categories carry their atlas-layer index in the respective
list
- Fallback handling (single-layer white) when TexMerge is missing or
every map fails to decode
- Alpha atlas uses ClampToEdge wrap so repeating tile sampling at
mask boundaries doesn't produce seams
- Dispose() now cleans up both textures
On Holtburg's region the log prints:
TerrainAtlas: 33 terrain layers at 512x512
AlphaAtlas: 8 layers at 512x512 (corners=4, sides=1, roads=3)
Tests: 61/61 passing. No visual change expected this commit (shader
still ignores Data0..3 and the alpha sampler).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8b940bd038
commit
a8459eecbb
3 changed files with 324 additions and 19 deletions
|
|
@ -55,6 +55,81 @@ public class SurfaceDecoderTests
|
|||
Assert.Same(DecodedTexture.Magenta, decoded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Decode_A8_ExpandsSingleByteToRgbaWithAlphaInAllChannels()
|
||||
{
|
||||
// PFID_A8 is single-byte-per-pixel alpha. AC terrain blending alpha maps
|
||||
// are stored this way. WorldBuilder's GetExpandedAlphaTexture replicates
|
||||
// the byte into all four RGBA channels so fragment shaders can read the
|
||||
// blend value from any channel (convention: the alpha channel).
|
||||
var src = new byte[] { 0x00, 0x40, 0x80, 0xFF }; // 2x2 image
|
||||
var rs = new RenderSurface
|
||||
{
|
||||
Width = 2,
|
||||
Height = 2,
|
||||
Format = PixelFormat.PFID_A8,
|
||||
SourceData = src,
|
||||
};
|
||||
|
||||
var decoded = SurfaceDecoder.DecodeRenderSurface(rs);
|
||||
|
||||
Assert.Equal(2, decoded.Width);
|
||||
Assert.Equal(2, decoded.Height);
|
||||
Assert.Equal(16, decoded.Rgba8.Length);
|
||||
// Each input byte expands to (b, b, b, b) in RGBA output
|
||||
Assert.Equal(new byte[]
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x40, 0x40, 0x40,
|
||||
0x80, 0x80, 0x80, 0x80,
|
||||
0xFF, 0xFF, 0xFF, 0xFF,
|
||||
}, decoded.Rgba8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Decode_CustomLscapeAlpha_TreatedIdenticallyToA8()
|
||||
{
|
||||
// PFID_CUSTOM_LSCAPE_ALPHA (0xF4) is AC's custom format for terrain
|
||||
// blending alpha maps. Pixel layout is identical to PFID_A8 — one
|
||||
// byte of alpha per pixel — so the decoder routes both through the
|
||||
// same DecodeA8 implementation.
|
||||
var src = new byte[] { 0x10, 0x20, 0x30, 0x40 }; // 2x2
|
||||
var rs = new RenderSurface
|
||||
{
|
||||
Width = 2,
|
||||
Height = 2,
|
||||
Format = PixelFormat.PFID_CUSTOM_LSCAPE_ALPHA,
|
||||
SourceData = src,
|
||||
};
|
||||
|
||||
var decoded = SurfaceDecoder.DecodeRenderSurface(rs);
|
||||
|
||||
Assert.Equal(16, decoded.Rgba8.Length);
|
||||
Assert.Equal(new byte[]
|
||||
{
|
||||
0x10, 0x10, 0x10, 0x10,
|
||||
0x20, 0x20, 0x20, 0x20,
|
||||
0x30, 0x30, 0x30, 0x30,
|
||||
0x40, 0x40, 0x40, 0x40,
|
||||
}, decoded.Rgba8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Decode_A8_WithShortSourceData_ReturnsMagenta()
|
||||
{
|
||||
var rs = new RenderSurface
|
||||
{
|
||||
Width = 4,
|
||||
Height = 4,
|
||||
Format = PixelFormat.PFID_A8,
|
||||
SourceData = new byte[8], // expects 16
|
||||
};
|
||||
|
||||
var decoded = SurfaceDecoder.DecodeRenderSurface(rs);
|
||||
|
||||
Assert.Same(DecodedTexture.Magenta, decoded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Decode_NullSourceData_ReturnsMagenta()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue