phase(N.1): implement GenerateViaWb alternative path
Phase N.1 step 4: parallel implementation of Generate() that calls WB's SceneryHelpers (Displace/CheckSlope/RotateObj/ObjAlign/ScaleObj) and TerrainUtils (OnRoad/GetNormal) instead of the inline ports. Not yet wired in — Generate() still runs the legacy path. Step 5 adds the dispatch. Per-helper conformance tests in step 3 prove the Displace/OnRoad/ GetNormalZ/ScaleObj substitutions are behavior-equivalent. Rotation is intentionally NOT conformance-tested because our existing port diverges from retail by ~180°; WB's RotateObj fixes that bug. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4bfcb2b190
commit
804bfbb819
1 changed files with 132 additions and 0 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase N.1 alternative implementation that delegates the
|
||||
/// algorithm calls to WorldBuilder's <c>SceneryHelpers</c> +
|
||||
/// <c>TerrainUtils</c>. Structurally identical to <see cref="Generate"/>
|
||||
/// but with WB's tested ports doing the work. Selected by
|
||||
/// <see cref="UseWbScenery"/>.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<ScenerySpawn> GenerateViaWb(
|
||||
DatCollection dats,
|
||||
Region region,
|
||||
LandBlock block,
|
||||
uint landblockId,
|
||||
HashSet<int>? buildingCells)
|
||||
{
|
||||
var result = new List<ScenerySpawn>();
|
||||
|
||||
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<Scene>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue