feat(app): render 3x3 neighbor landblocks with texture atlas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
347a7e92ff
commit
560100e5b6
4 changed files with 109 additions and 79 deletions
|
|
@ -1,6 +1,4 @@
|
|||
using AcDream.Core.Terrain;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Options;
|
||||
using Silk.NET.Input;
|
||||
using Silk.NET.Maths;
|
||||
|
|
@ -98,75 +96,67 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
_dats = new DatCollection(_datDir, DatAccessType.Read);
|
||||
|
||||
// Find ANY landblock ending in 0xFFFF. Holtburg 0xA9B4FFFF is a
|
||||
// good default; fall back to the first one we find. Using Get<T>
|
||||
// (returns null on miss) rather than TryGet to sidestep
|
||||
// [MaybeNullWhen(false)] nullable-generic analysis under
|
||||
// TreatWarningsAsErrors.
|
||||
uint landblockId = 0xA9B4FFFFu;
|
||||
var block = _dats.Get<LandBlock>(landblockId);
|
||||
if (block is null)
|
||||
{
|
||||
foreach (var file in _dats.Cell.Tree)
|
||||
{
|
||||
if ((file.Id & 0xFFFFu) == 0xFFFFu)
|
||||
{
|
||||
landblockId = file.Id;
|
||||
block = _dats.Get<LandBlock>(landblockId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
uint centerLandblockId = 0xA9B4FFFFu;
|
||||
Console.WriteLine($"loading world view centered on 0x{centerLandblockId:X8}");
|
||||
|
||||
if (block is null)
|
||||
throw new InvalidOperationException("no landblock found in cell dat");
|
||||
|
||||
Console.WriteLine($"loaded landblock 0x{landblockId:X8}");
|
||||
|
||||
// Load the non-linear LandHeightTable from the Region dat. AC encodes
|
||||
// per-vertex heights as byte indices into this 256-entry float table,
|
||||
// not as a simple * 2.0 ramp — building placements depend on the real
|
||||
// table, so terrain rendered with the simplified scale would leave
|
||||
// buildings floating or buried.
|
||||
var region = _dats.Get<DatReaderWriter.DBObjs.Region>(0x13000000u);
|
||||
var heightTable = region?.LandDefs.LandHeightTable;
|
||||
if (heightTable is null || heightTable.Length < 256)
|
||||
throw new InvalidOperationException("Region.LandDefs.LandHeightTable missing or truncated");
|
||||
|
||||
var meshData = LandblockMesh.Build(block, heightTable, new Dictionary<uint, uint>());
|
||||
_terrain = new TerrainRenderer(_gl, meshData, _shader);
|
||||
// Build the terrain atlas once from the Region dat.
|
||||
var terrainAtlas = AcDream.App.Rendering.TerrainAtlas.Build(_gl, _dats);
|
||||
|
||||
_terrain = new TerrainRenderer(_gl, _shader, terrainAtlas);
|
||||
|
||||
// Load the 3x3 neighbor grid.
|
||||
var worldView = AcDream.Core.World.WorldView.Load(_dats, centerLandblockId);
|
||||
Console.WriteLine($"loaded {worldView.Landblocks.Count} landblocks in 3x3 grid");
|
||||
|
||||
int centerX = (int)((centerLandblockId >> 24) & 0xFFu);
|
||||
int centerY = (int)((centerLandblockId >> 16) & 0xFFu);
|
||||
|
||||
foreach (var lb in worldView.Landblocks)
|
||||
{
|
||||
var meshData = AcDream.Core.Terrain.LandblockMesh.Build(
|
||||
lb.Heightmap, heightTable, terrainAtlas.TerrainTypeToLayer);
|
||||
|
||||
// Compute world origin for this landblock relative to the center.
|
||||
int lbX = (int)((lb.LandblockId >> 24) & 0xFFu);
|
||||
int lbY = (int)((lb.LandblockId >> 16) & 0xFFu);
|
||||
var origin = new System.Numerics.Vector3(
|
||||
(lbX - centerX) * 192f,
|
||||
(lbY - centerY) * 192f,
|
||||
0f);
|
||||
|
||||
_terrain.AddLandblock(meshData, origin);
|
||||
}
|
||||
|
||||
_textureCache = new TextureCache(_gl, _dats);
|
||||
_staticMesh = new StaticMeshRenderer(_gl, _meshShader, _textureCache);
|
||||
|
||||
// Load LandBlockInfo for Holtburg, hydrate entities.
|
||||
var info = _dats.Get<DatReaderWriter.DBObjs.LandBlockInfo>((landblockId & 0xFFFF0000u) | 0xFFFEu);
|
||||
var entities = info is not null
|
||||
? AcDream.Core.World.LandblockLoader.BuildEntitiesFromInfo(info)
|
||||
: Array.Empty<AcDream.Core.World.WorldEntity>();
|
||||
// Hydrate entities from ALL loaded landblocks, not just the center.
|
||||
var allEntities = worldView.AllEntities.ToList();
|
||||
Console.WriteLine($"hydrating {allEntities.Count} entities across {worldView.Landblocks.Count} landblocks");
|
||||
|
||||
// Populate MeshRefs for each entity by resolving its source id to GfxObj or Setup
|
||||
// and extracting sub-meshes. Store back onto the entity. Since WorldEntity is
|
||||
// `required init`, we rebuild the entity here.
|
||||
var hydratedEntities = new List<AcDream.Core.World.WorldEntity>(entities.Count);
|
||||
foreach (var e in entities)
|
||||
var hydratedEntities = new List<AcDream.Core.World.WorldEntity>(allEntities.Count);
|
||||
foreach (var e in allEntities)
|
||||
{
|
||||
var meshRefs = new List<AcDream.Core.World.MeshRef>();
|
||||
|
||||
if ((e.SourceGfxObjOrSetupId & 0xFF000000u) == 0x01000000u)
|
||||
{
|
||||
// GfxObj: one mesh ref with identity transform.
|
||||
var gfx = _dats.Get<DatReaderWriter.DBObjs.GfxObj>(e.SourceGfxObjOrSetupId);
|
||||
if (gfx is not null)
|
||||
{
|
||||
var subMeshes = AcDream.Core.Meshing.GfxObjMesh.Build(gfx);
|
||||
_staticMesh.EnsureUploaded(e.SourceGfxObjOrSetupId, subMeshes);
|
||||
meshRefs.Add(new AcDream.Core.World.MeshRef(e.SourceGfxObjOrSetupId, System.Numerics.Matrix4x4.Identity));
|
||||
meshRefs.Add(new AcDream.Core.World.MeshRef(
|
||||
e.SourceGfxObjOrSetupId, System.Numerics.Matrix4x4.Identity));
|
||||
}
|
||||
}
|
||||
else if ((e.SourceGfxObjOrSetupId & 0xFF000000u) == 0x02000000u)
|
||||
{
|
||||
// Setup: flatten into parts, upload each part's GfxObj.
|
||||
var setup = _dats.Get<DatReaderWriter.DBObjs.Setup>(e.SourceGfxObjOrSetupId);
|
||||
if (setup is not null)
|
||||
{
|
||||
|
|
@ -184,11 +174,21 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
if (meshRefs.Count > 0)
|
||||
{
|
||||
// Add the landblock origin to the entity's position so the static
|
||||
// mesh renderer draws it at the correct world location.
|
||||
var sourceLandblock = worldView.Landblocks.First(lb => lb.Entities.Contains(e));
|
||||
int lbX = (int)((sourceLandblock.LandblockId >> 24) & 0xFFu);
|
||||
int lbY = (int)((sourceLandblock.LandblockId >> 16) & 0xFFu);
|
||||
var worldOffset = new System.Numerics.Vector3(
|
||||
(lbX - centerX) * 192f,
|
||||
(lbY - centerY) * 192f,
|
||||
0f);
|
||||
|
||||
hydratedEntities.Add(new AcDream.Core.World.WorldEntity
|
||||
{
|
||||
Id = e.Id,
|
||||
SourceGfxObjOrSetupId = e.SourceGfxObjOrSetupId,
|
||||
Position = e.Position,
|
||||
Position = e.Position + worldOffset,
|
||||
Rotation = e.Rotation,
|
||||
MeshRefs = meshRefs,
|
||||
});
|
||||
|
|
@ -196,7 +196,7 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
|
||||
_entities = hydratedEntities;
|
||||
Console.WriteLine($"hydrated {_entities.Count} entities on landblock 0x{landblockId:X8}");
|
||||
Console.WriteLine($"hydrated {_entities.Count} entities");
|
||||
}
|
||||
|
||||
private void OnRender(double deltaSeconds)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue