feat(app): walk interior EnvCells for in-building static objects
Addresses the "foundry statue still missing" user feedback after Phase
2c. Diagnostic spike confirmed the statue is not a Stab on
LandBlockInfo.Objects, not a Building on LandBlockInfo.Buildings, and not
a hierarchical Setup part — the highest entity on Holtburg center was
at Z=104 with no entity above the foundry cluster.
Root cause per LandBlockInfo.NumCells's own doc comment: interior cells
live at dat id 0xAAAA0100 + N where AAAA is the landblock id high word
and N runs from 0 to NumCells-1. Each EnvCell has a StaticObjects list
(List<Stab>) holding in-building decorations — statues, furniture,
lamps, crates, rugs, and the like. We weren't loading any of it.
GameWindow.OnLoad now iterates each landblock's LandBlockInfo.NumCells,
loads each EnvCell at the canonical id, walks its StaticObjects, and
hydrates each as a WorldEntity. Position is composed as:
worldPos = landblockOffset + cellOrigin + (cellRotation * stabLocal)
worldRot = cellRotation * stabRotation
where cellOrigin/cellRotation come from EnvCell.Position (a Frame in
landblock-local space) and stabLocal/stabRotation come from the Stab's
own Frame (cell-local space). Cell rotation is applied to the stab
position because some cells in AC are rotated relative to the landblock
grid.
Entity counts on Holtburg 3x3:
Stabs + Buildings 239
Procedural scenery 419
Interior StaticObjects 475 (NEW)
-------
Total 1133
Phase 2d done. Interior geometry (walls, floors, ceilings) is still not
rendered — cell shells are Phase 3+ work. Only the StaticObjects list is
walked, which is enough to surface the visible decorations the user sees
inside buildings in real AC.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
985cdc0044
commit
abcfb55418
1 changed files with 96 additions and 1 deletions
|
|
@ -320,8 +320,103 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
Console.WriteLine($"scenery: spawned {scenerySpawned} entities across {worldView.Landblocks.Count} landblocks");
|
||||
|
||||
// Phase 2d: walk interior EnvCells and add their StaticObjects. Buildings'
|
||||
// rooftop statues, doors, interior decorations, and other in-building static
|
||||
// objects live here rather than in LandBlockInfo.Objects. EnvCell ids for a
|
||||
// landblock are packed at 0xAAAABBBB where AAAA is the landblock id high word
|
||||
// and BBBB starts at 0x0100 — documented on LandBlockInfo.NumCells.
|
||||
int interiorSpawned = 0;
|
||||
uint interiorIdCounter = 0x40000000u; // distinct from scenery (0x80000000+) and stabs
|
||||
foreach (var lb in worldView.Landblocks)
|
||||
{
|
||||
// Re-fetch LandBlockInfo to get NumCells. WorldView.LoadedLandblock exposes
|
||||
// Heightmap + Entities but not the raw info record.
|
||||
var lbInfo = _dats.Get<DatReaderWriter.DBObjs.LandBlockInfo>((lb.LandblockId & 0xFFFF0000u) | 0xFFFEu);
|
||||
if (lbInfo is null || lbInfo.NumCells == 0) continue;
|
||||
|
||||
int lbX = (int)((lb.LandblockId >> 24) & 0xFFu);
|
||||
int lbY = (int)((lb.LandblockId >> 16) & 0xFFu);
|
||||
var lbOffset = new System.Numerics.Vector3(
|
||||
(lbX - centerX) * 192f,
|
||||
(lbY - centerY) * 192f,
|
||||
0f);
|
||||
|
||||
// Interior cells start at 0xAAAA0100 and run for NumCells.
|
||||
uint firstCellId = (lb.LandblockId & 0xFFFF0000u) | 0x0100u;
|
||||
for (uint offset = 0; offset < lbInfo.NumCells; offset++)
|
||||
{
|
||||
var envCell = _dats.Get<DatReaderWriter.DBObjs.EnvCell>(firstCellId + offset);
|
||||
if (envCell is null) continue;
|
||||
|
||||
foreach (var stab in envCell.StaticObjects)
|
||||
{
|
||||
// Resolve stab id to mesh (same as LandBlockInfo.Objects).
|
||||
var meshRefs = new List<AcDream.Core.World.MeshRef>();
|
||||
if ((stab.Id & 0xFF000000u) == 0x01000000u)
|
||||
{
|
||||
var gfx = _dats.Get<DatReaderWriter.DBObjs.GfxObj>(stab.Id);
|
||||
if (gfx is not null)
|
||||
{
|
||||
var subMeshes = AcDream.Core.Meshing.GfxObjMesh.Build(gfx);
|
||||
_staticMesh.EnsureUploaded(stab.Id, subMeshes);
|
||||
meshRefs.Add(new AcDream.Core.World.MeshRef(stab.Id, System.Numerics.Matrix4x4.Identity));
|
||||
}
|
||||
}
|
||||
else if ((stab.Id & 0xFF000000u) == 0x02000000u)
|
||||
{
|
||||
var setup = _dats.Get<DatReaderWriter.DBObjs.Setup>(stab.Id);
|
||||
if (setup is not null)
|
||||
{
|
||||
var flat = AcDream.Core.Meshing.SetupMesh.Flatten(setup);
|
||||
foreach (var mr in flat)
|
||||
{
|
||||
var gfx = _dats.Get<DatReaderWriter.DBObjs.GfxObj>(mr.GfxObjId);
|
||||
if (gfx is null) continue;
|
||||
var subMeshes = AcDream.Core.Meshing.GfxObjMesh.Build(gfx);
|
||||
_staticMesh.EnsureUploaded(mr.GfxObjId, subMeshes);
|
||||
meshRefs.Add(mr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (meshRefs.Count == 0) continue;
|
||||
|
||||
// Compose: the stab's position is cell-local, and the cell's Position
|
||||
// is landblock-local. Rotate the stab-local position by the cell's
|
||||
// orientation before adding the cell origin.
|
||||
var stabLocalPos = stab.Frame.Origin;
|
||||
var cellOrigin = envCell.Position.Origin;
|
||||
var cellRot = envCell.Position.Orientation;
|
||||
var rotatedStab = System.Numerics.Vector3.Transform(stabLocalPos, cellRot);
|
||||
var landblockLocalPos = cellOrigin + rotatedStab;
|
||||
var worldPos = landblockLocalPos + lbOffset;
|
||||
var worldRot = cellRot * stab.Frame.Orientation;
|
||||
|
||||
var hydrated = new AcDream.Core.World.WorldEntity
|
||||
{
|
||||
Id = interiorIdCounter++,
|
||||
SourceGfxObjOrSetupId = stab.Id,
|
||||
Position = worldPos,
|
||||
Rotation = worldRot,
|
||||
MeshRefs = meshRefs,
|
||||
};
|
||||
hydratedEntities.Add(hydrated);
|
||||
|
||||
var snapshot = new AcDream.Plugin.Abstractions.WorldEntitySnapshot(
|
||||
Id: hydrated.Id,
|
||||
SourceId: hydrated.SourceGfxObjOrSetupId,
|
||||
Position: hydrated.Position,
|
||||
Rotation: hydrated.Rotation);
|
||||
_worldGameState.Add(snapshot);
|
||||
_worldEvents.FireEntitySpawned(snapshot);
|
||||
interiorSpawned++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"interior: spawned {interiorSpawned} static objects from EnvCells");
|
||||
|
||||
_entities = hydratedEntities;
|
||||
Console.WriteLine($"hydrated {_entities.Count} entities total (stabs + buildings + scenery)");
|
||||
Console.WriteLine($"hydrated {_entities.Count} entities total (stabs + buildings + scenery + interior)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue