fix(core): ACME cross-check fixes — normals, placement, scenery

Four fixes from the ACME StaticObjectManager cross-reference:

1. GfxObjMesh: normalize vertex normals (1d). Dat normals may not be
   unit-length; without normalization, lighting is wrong per-vertex.

2. SetupMesh: add third-fallback placement frame (2a). If neither
   Resting nor Default exists, use the first available frame from
   PlacementFrames. Matches ACME's GetDefaultPlacementFrame.

3. SceneryGenerator: building cell exclusion (4d). Compute which
   terrain vertices have buildings (from LandBlockInfo.Objects +
   Buildings), skip scenery spawns in those cells. Prevents trees
   from spawning inside building footprints.

4. SceneryGenerator: slope filter (4e). Compute terrain normal Z at
   each displaced position and check against ObjectDesc.MinSlope /
   MaxSlope bounds. Prevents trees from spawning on cliff faces.

Also confirmed 4f (scenery Z=0) is NOT a bug — GameWindow's hydrator
lifts scenery to terrain Z at line 1213. The Z=0 in SceneryGenerator
is a placeholder correctly overridden at render time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 22:52:08 +02:00
parent 05749f52e0
commit 9d4967a461
4 changed files with 61 additions and 7 deletions

View file

@ -37,6 +37,17 @@ public static class SetupMesh
defaultAnim = resting;
if (defaultAnim is null && setup.PlacementFrames.TryGetValue(Placement.Default, out var af))
defaultAnim = af;
// Last resort: use the first available placement frame (matches ACME's
// StaticObjectManager.GetDefaultPlacementFrame third fallback). Handles
// rare Setups that define only an unusual placement frame key.
if (defaultAnim is null)
{
foreach (var kvp in setup.PlacementFrames)
{
defaultAnim = kvp.Value;
break;
}
}
var result = new List<MeshRef>(setup.Parts.Count);
for (int i = 0; i < setup.Parts.Count; i++)