diff --git a/src/AcDream.Core/World/SceneryGenerator.cs b/src/AcDream.Core/World/SceneryGenerator.cs index 7571a6c..c75666d 100644 --- a/src/AcDream.Core/World/SceneryGenerator.cs +++ b/src/AcDream.Core/World/SceneryGenerator.cs @@ -1,7 +1,9 @@ using System.Numerics; +using Chorizite.OpenGLSDLBackend.Lib; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Types; +using WorldBuilder.Shared.Modules.Landscape.Lib; namespace AcDream.Core.World; @@ -258,6 +260,136 @@ public static class SceneryGenerator return result; } + /// + /// Phase N.1 alternative implementation that delegates the + /// algorithm calls to WorldBuilder's SceneryHelpers + + /// TerrainUtils. Structurally identical to + /// but with WB's tested ports doing the work. Selected by + /// . + /// + private static IReadOnlyList GenerateViaWb( + DatCollection dats, + Region region, + LandBlock block, + uint landblockId, + HashSet? buildingCells) + { + var result = new List(); + + if (region.TerrainInfo?.TerrainTypes is null || region.SceneInfo?.SceneTypes is null) + return result; + + // Build the TerrainEntry[] WB's helpers consume — once per landblock. + var terrainEntries = WbSceneryAdapter.BuildTerrainEntries(block); + + uint blockX = (landblockId >> 24) * 8; + uint blockY = ((landblockId >> 16) & 0xFFu) * 8; + uint lbX = landblockId >> 24; + uint lbY = (landblockId >> 16) & 0xFFu; + + for (int x = 0; x < VerticesPerSide; x++) + { + for (int y = 0; y < VerticesPerSide; y++) + { + int i = x * VerticesPerSide + y; + ushort raw = block.Terrain[i]; + + uint terrainType = (uint)((raw >> 2) & 0x1F); + uint sceneType = (uint)((raw >> 11) & 0x1F); + + if (terrainType >= region.TerrainInfo.TerrainTypes.Count) continue; + var sceneTypeList = region.TerrainInfo.TerrainTypes[(int)terrainType].SceneTypes; + if (sceneType >= sceneTypeList.Count) continue; + + uint sceneInfo = sceneTypeList[(int)sceneType]; + if (sceneInfo >= region.SceneInfo.SceneTypes.Count) continue; + + var scenes = region.SceneInfo.SceneTypes[(int)sceneInfo].Scenes; + if (scenes.Count == 0) continue; + + uint cellX = (uint)x; + uint cellY = (uint)y; + uint globalCellX = cellX + blockX; + uint globalCellY = cellY + blockY; + + // Scene-selection hash: identical to Generate. + uint cellMat = globalCellY * (712977289u * globalCellX + 1813693831u) + - 1109124029u * globalCellX + 2139937281u; + double offset = cellMat * 2.3283064e-10; + int sceneIdx = (int)(scenes.Count * offset); + if (sceneIdx >= scenes.Count || sceneIdx < 0) sceneIdx = 0; + + uint sceneId = (uint)scenes[sceneIdx]; + var scene = dats.Get(sceneId); + if (scene is null) continue; + + // Per-object frequency setup: identical to Generate. + uint cellXMat = unchecked(0u - 1109124029u * globalCellX); + uint cellYMat = 1813693831u * globalCellY; + uint cellMat2 = 1360117743u * globalCellX * globalCellY + 1888038839u; + + for (uint j = 0; j < scene.Objects.Count; j++) + { + var obj = scene.Objects[(int)j]; + if (obj.WeenieObj != 0) continue; + + double noise = unchecked((uint)(cellXMat + cellYMat - cellMat2 * (23399u + j))) * 2.3283064e-10; + if (noise >= obj.Frequency) continue; + + // ─── WB substitution: displacement ─────────────────── + var localPos = SceneryHelpers.Displace(obj, globalCellX, globalCellY, j); + + float lx = cellX * CellSize + localPos.X; + float ly = cellY * CellSize + localPos.Y; + + if (lx < 0 || ly < 0 || lx >= LandblockSize || ly >= LandblockSize) + continue; + + // ─── WB substitution: road check ────────────────────── + if (TerrainUtils.OnRoad(new Vector3(lx, ly, 0), terrainEntries)) + continue; + + // Building check: identical to Generate. + if (buildingCells is not null) + { + int dcx = Math.Clamp((int)(lx / CellSize), 0, CellsPerSide - 1); + int dcy = Math.Clamp((int)(ly / CellSize), 0, CellsPerSide - 1); + if (buildingCells.Contains(dcx * VerticesPerSide + dcy)) + continue; + } + + // ─── WB substitution: slope check ───────────────────── + Vector3 normal = TerrainUtils.GetNormal( + region, terrainEntries, lbX, lbY, + new Vector3(lx, ly, 0)); + if (!SceneryHelpers.CheckSlope(obj, normal.Z)) + continue; + + float lz = obj.BaseLoc.Origin.Z; + + // ─── WB substitution: rotation ──────────────────────── + Quaternion rotation; + if (obj.Align != 0) + rotation = SceneryHelpers.ObjAlign(obj, normal, lz, localPos); + else + rotation = SceneryHelpers.RotateObj(obj, globalCellX, globalCellY, j, localPos); + + // ─── WB substitution: scale ─────────────────────────── + float scale = SceneryHelpers.ScaleObj(obj, globalCellX, globalCellY, j); + if (scale <= 0) scale = 1f; + + result.Add(new ScenerySpawn( + ObjectId: obj.ObjectId, + LocalPosition: new Vector3(lx, ly, lz), + Rotation: rotation, + Scale: scale)); + } + } + } + + return result; + } + /// /// Returns true if the raw terrain word indicates a road vertex. /// Bits 0-1 of the terrain word encode the road type; any non-zero value