Verbatim copy of 5 WorldBuilder files into src/AcDream.Core/Rendering/Wb/: - TextureHelpers.cs (pixel-format decoders, Chorizite Lib) - SceneryHelpers.cs (scenery transforms, Chorizite Lib) - TerrainUtils.cs, TerrainEntry.cs, CellSplitDirection.cs (WB.Shared Landscape) Namespace migrated from WorldBuilder.* / Chorizite.OpenGLSDLBackend.Lib to AcDream.Core.Rendering.Wb per O-D11. [MemoryPackable] stripped from TerrainEntry per O-D10 (we don't serialize the struct). Updated 3 source files + 1 test file to import from the new namespace. Verbatim discipline (O-D1): only namespace + MemoryPack attribute changed. All algorithm bodies byte-identical to upstream. Note: TextureHelpers omits IsAlphaFormat() and GetCompressedLayerSize() because those reference Chorizite.Core.Render.Enums.TextureFormat, a type that has no path into AcDream.Core without adding an unwanted NuGet dep. Neither method is called from Core or the test suite; the omission is safe. Verified on main checkout: dotnet build green (0 errors), dotnet test green — Failed: 8, Passed: 1147, Skipped: 0, Total: 1155 (baseline maintained). TextureDecodeConformanceTests (9/9) pass byte-for-byte after namespace swap. AcDream.Core project alone builds green in this worktree (App-layer failures are pre-existing, blocked by empty WB submodule, addressed in Tasks 3+4). Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
239 lines
10 KiB
C#
239 lines
10 KiB
C#
using System;
|
|
|
|
namespace AcDream.Core.Rendering.Wb {
|
|
/// <summary>
|
|
/// Flags that indicate which fields are present in TerrainEntry
|
|
/// </summary>
|
|
[Flags]
|
|
public enum TerrainEntryFlags : byte {
|
|
None = 0,
|
|
Height = 1 << 0,
|
|
Texture = 1 << 1,
|
|
Scenery = 1 << 2,
|
|
Road = 1 << 3,
|
|
Encounters = 1 << 4,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a terrain entry with optional height, texture, scenery, road, encounters, and flags.
|
|
/// Flags are automatically synchronized with nullable fields.
|
|
/// Packed format: [Height:8][Texture:5][Scenery:5][Encounters:4][Road:3][Unused:2][Flags:5]
|
|
/// </summary>
|
|
public struct TerrainEntry {
|
|
private const int HEIGHT_SHIFT = 24;
|
|
private const uint HEIGHT_MASK = 0xFF000000;
|
|
|
|
private const int TEXTURE_SHIFT = 19;
|
|
private const uint TEXTURE_MASK = 0x00F80000;
|
|
|
|
private const int SCENERY_SHIFT = 14;
|
|
private const uint SCENERY_MASK = 0x0007C000;
|
|
|
|
private const int ENCOUNTERS_SHIFT = 10;
|
|
private const uint ENCOUNTERS_MASK = 0x00003C00;
|
|
|
|
private const int ROAD_SHIFT = 7;
|
|
private const uint ROAD_MASK = 0x00000380;
|
|
|
|
private const int FLAGS_SHIFT = 0;
|
|
private const uint FLAGS_MASK = 0x0000001F;
|
|
|
|
private uint _data;
|
|
|
|
/// <summary>Gets the flags for this terrain entry, indicating which fields are set.</summary>
|
|
public TerrainEntryFlags Flags {
|
|
get => (TerrainEntryFlags)((_data & FLAGS_MASK) >> FLAGS_SHIFT);
|
|
private set => _data = (_data & ~FLAGS_MASK) | ((uint)value << FLAGS_SHIFT);
|
|
}
|
|
|
|
/// <summary>Gets or sets the height of the terrain.</summary>
|
|
public byte? Height {
|
|
get => (_data & (uint)TerrainEntryFlags.Height) != 0
|
|
? (byte)((_data & HEIGHT_MASK) >> HEIGHT_SHIFT)
|
|
: null;
|
|
set {
|
|
if (value.HasValue) {
|
|
_data = (_data & ~HEIGHT_MASK) | ((uint)value.Value << HEIGHT_SHIFT);
|
|
Flags |= TerrainEntryFlags.Height;
|
|
}
|
|
else {
|
|
_data &= ~HEIGHT_MASK;
|
|
Flags &= ~TerrainEntryFlags.Height;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets or sets the texture type of the terrain.</summary>
|
|
public byte? Type {
|
|
get => (_data & (uint)TerrainEntryFlags.Texture) != 0
|
|
? (byte)((_data & TEXTURE_MASK) >> TEXTURE_SHIFT)
|
|
: null;
|
|
set {
|
|
if (value.HasValue) {
|
|
if (value.Value > 31)
|
|
throw new ArgumentOutOfRangeException(nameof(Type), "Texture must be 0-31");
|
|
_data = (_data & ~TEXTURE_MASK) | ((uint)value.Value << TEXTURE_SHIFT);
|
|
Flags |= TerrainEntryFlags.Texture;
|
|
}
|
|
else {
|
|
_data &= ~TEXTURE_MASK;
|
|
Flags &= ~TerrainEntryFlags.Texture;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets or sets the scenery index of the terrain.</summary>
|
|
public byte? Scenery {
|
|
get => (_data & (uint)TerrainEntryFlags.Scenery) != 0
|
|
? (byte)((_data & SCENERY_MASK) >> SCENERY_SHIFT)
|
|
: null;
|
|
set {
|
|
if (value.HasValue) {
|
|
if (value.Value > 31)
|
|
throw new ArgumentOutOfRangeException(nameof(Scenery), "Scenery must be 0-31");
|
|
_data = (_data & ~SCENERY_MASK) | ((uint)value.Value << SCENERY_SHIFT);
|
|
Flags |= TerrainEntryFlags.Scenery;
|
|
}
|
|
else {
|
|
_data &= ~SCENERY_MASK;
|
|
Flags &= ~TerrainEntryFlags.Scenery;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets or sets the road index of the terrain.</summary>
|
|
public byte? Road {
|
|
get => (_data & (uint)TerrainEntryFlags.Road) != 0
|
|
? (byte)((_data & ROAD_MASK) >> ROAD_SHIFT)
|
|
: null;
|
|
set {
|
|
if (value.HasValue) {
|
|
if (value.Value > 7)
|
|
throw new ArgumentOutOfRangeException(nameof(Road), "Road must be 0-7");
|
|
_data = (_data & ~ROAD_MASK) | ((uint)value.Value << ROAD_SHIFT);
|
|
Flags |= TerrainEntryFlags.Road;
|
|
}
|
|
else {
|
|
_data &= ~ROAD_MASK;
|
|
Flags &= ~TerrainEntryFlags.Road;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets or sets the encounters index of the terrain.</summary>
|
|
public byte? Encounters {
|
|
get => (_data & (uint)TerrainEntryFlags.Encounters) != 0
|
|
? (byte)((_data & ENCOUNTERS_MASK) >> ENCOUNTERS_SHIFT)
|
|
: null;
|
|
set {
|
|
if (value.HasValue) {
|
|
if (value.Value > 15)
|
|
throw new ArgumentOutOfRangeException(nameof(Encounters), "Encounters must be 0-15");
|
|
_data = (_data & ~ENCOUNTERS_MASK) | ((uint)value.Value << ENCOUNTERS_SHIFT);
|
|
Flags |= TerrainEntryFlags.Encounters;
|
|
}
|
|
else {
|
|
_data &= ~ENCOUNTERS_MASK;
|
|
Flags &= ~TerrainEntryFlags.Encounters;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="TerrainEntry"/> struct.</summary>
|
|
public TerrainEntry() {
|
|
_data = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="TerrainEntry"/> struct with specified components.
|
|
/// </summary>
|
|
/// <param name="height">The terrain height.</param>
|
|
/// <param name="texture">The texture type.</param>
|
|
/// <param name="scenery">The scenery index.</param>
|
|
/// <param name="road">The road index.</param>
|
|
/// <param name="encounters">The encounters index.</param>
|
|
public TerrainEntry(byte? height, byte? texture, byte? scenery, byte? road, byte? encounters) {
|
|
_data = 0;
|
|
|
|
if (texture.HasValue && texture.Value > 31)
|
|
throw new ArgumentOutOfRangeException(nameof(texture), "Texture must be 0-31");
|
|
if (scenery.HasValue && scenery.Value > 31)
|
|
throw new ArgumentOutOfRangeException(nameof(scenery), "Scenery must be 0-31");
|
|
if (road.HasValue && road.Value > 7)
|
|
throw new ArgumentOutOfRangeException(nameof(road), "Road must be 0-7");
|
|
if (encounters.HasValue && encounters.Value > 15)
|
|
throw new ArgumentOutOfRangeException(nameof(encounters), "Encounters must be 0-15");
|
|
|
|
Height = height;
|
|
Type = texture;
|
|
Scenery = scenery;
|
|
Road = road;
|
|
Encounters = encounters;
|
|
}
|
|
|
|
/// <summary>Creates a <see cref="TerrainEntry"/> with only height data.</summary>
|
|
/// <param name="height">The height.</param>
|
|
/// <returns>A new <see cref="TerrainEntry"/>.</returns>
|
|
public static TerrainEntry FromHeight(byte height) => new(height, null, null, null, null);
|
|
|
|
/// <summary>Creates a <see cref="TerrainEntry"/> with only texture data.</summary>
|
|
/// <param name="texture">The texture type.</param>
|
|
/// <returns>A new <see cref="TerrainEntry"/>.</returns>
|
|
public static TerrainEntry FromTexture(byte texture) => new(null, texture, null, null, null);
|
|
|
|
/// <summary>Creates a <see cref="TerrainEntry"/> with only scenery data.</summary>
|
|
/// <param name="scenery">The scenery index.</param>
|
|
/// <returns>A new <see cref="TerrainEntry"/>.</returns>
|
|
public static TerrainEntry FromScenery(byte scenery) => new(null, null, scenery, null, null);
|
|
|
|
/// <summary>Creates a <see cref="TerrainEntry"/> with only road data.</summary>
|
|
/// <param name="road">The road index.</param>
|
|
/// <returns>A new <see cref="TerrainEntry"/>.</returns>
|
|
public static TerrainEntry FromRoad(byte road) => new(null, null, null, road, null);
|
|
|
|
/// <summary>Creates a <see cref="TerrainEntry"/> with only encounters data.</summary>
|
|
/// <param name="encounters">The encounters index.</param>
|
|
/// <returns>A new <see cref="TerrainEntry"/>.</returns>
|
|
public static TerrainEntry FromEncounters(byte encounters) => new(null, null, null, null, encounters);
|
|
|
|
/// <summary>Creates a <see cref="TerrainEntry"/> with texture and scenery data.</summary>
|
|
/// <param name="texture">The texture type.</param>
|
|
/// <param name="scenery">The scenery index.</param>
|
|
/// <returns>A new <see cref="TerrainEntry"/>.</returns>
|
|
public static TerrainEntry FromTextureScenery(byte texture, byte scenery) => new(null, texture, scenery, null, null);
|
|
|
|
/// <summary>
|
|
/// Returns the packed 32-bit representation
|
|
/// </summary>
|
|
public uint Pack() => _data;
|
|
|
|
/// <summary>
|
|
/// Creates a TerrainEntry from a packed 32-bit value
|
|
/// </summary>
|
|
public static TerrainEntry Unpack(uint packed) {
|
|
return new TerrainEntry { _data = packed };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Merges another entry into this one. Null values are ignored.
|
|
/// </summary>
|
|
public void Merge(TerrainEntry value) {
|
|
if (value.Height.HasValue) Height = value.Height;
|
|
if (value.Type.HasValue) Type = value.Type;
|
|
if (value.Scenery.HasValue) Scenery = value.Scenery;
|
|
if (value.Road.HasValue) Road = value.Road;
|
|
if (value.Encounters.HasValue) Encounters = value.Encounters;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override string ToString() {
|
|
string height = Height?.ToString() ?? "null";
|
|
string texture = Type?.ToString() ?? "null";
|
|
string scenery = Scenery?.ToString() ?? "null";
|
|
string road = Road?.ToString() ?? "null";
|
|
string encounters = Encounters?.ToString() ?? "null";
|
|
|
|
return $"Height:{height}, Texture:{texture}, Scenery:{scenery}, Road:{road}, Encounters:{encounters}, Flags:{Flags}";
|
|
}
|
|
}
|
|
}
|