fix(scenery): add retail obj_within_block check for edge-boundary spawns
Retail's CLandBlock::get_land_scenes creates a PhysicsObj for each scenery spawn, then calls CPhysicsObj::obj_within_block (0x00461c30) which verifies the model's sorting sphere stays within [r, 192-r] on both axes. Edge-vertex spawns displaced close to the boundary (e.g., a tree at Y=190.97 from vertex y=8) get rejected because their sorting sphere extends past the landblock edge. We were missing this check, which caused a tree near a road at ~(85, 191) in landblock 0xA9B1 to appear in acdream but not retail. The tree legitimately passed all other filters (road, building, slope) but its Setup sorting sphere radius (~2-5m) meant it overflowed the boundary. Fix: look up each unique Setup's SortingSphere.Radius from the dat (cached per objectId) and apply the within-block bounds check after the slope filter, matching retail's order. GfxObj objects (0x01) use radius 0 (permissive) since they're typically small single-mesh items. Also: remove temporary ACDREAM_SCENERY_DIAG logging, fix duplicate xmldoc on IsOnRoad, update road check reference to named-retail PDB symbol (CLandBlock::on_road at 0x0052FFF0). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
833d167ebc
commit
e8aa1c82f4
1 changed files with 59 additions and 11 deletions
|
|
@ -59,6 +59,7 @@ public static class SceneryGenerator
|
|||
float[]? heightTable = null)
|
||||
{
|
||||
var result = new List<ScenerySpawn>();
|
||||
var sortingRadiusCache = new Dictionary<uint, float>();
|
||||
|
||||
if (region.TerrainInfo?.TerrainTypes is null || region.SceneInfo?.SceneTypes is null)
|
||||
return result;
|
||||
|
|
@ -182,6 +183,15 @@ public static class SceneryGenerator
|
|||
if (nz < obj.MinSlope || nz > obj.MaxSlope) continue;
|
||||
}
|
||||
|
||||
// Retail obj_within_block check: the model's sorting sphere must
|
||||
// fit entirely within the landblock bounds. This rejects edge-vertex
|
||||
// spawns whose bounding sphere would extend past the boundary.
|
||||
// Named-retail: CPhysicsObj::obj_within_block (0x00461c30), called
|
||||
// inside CLandBlock::get_land_scenes after find_terrain_poly/CheckSlope.
|
||||
float sortRadius = GetSortingSphereRadius(dats, obj.ObjectId, sortingRadiusCache);
|
||||
if (!IsWithinBlock(lx, ly, sortRadius))
|
||||
continue;
|
||||
|
||||
// BaseLoc.Z offset: scenery-specific vertical offset from
|
||||
// the ground (e.g., flowers planted at -0.1m so they
|
||||
// don't float above grass). The renderer adds groundZ
|
||||
|
|
@ -262,20 +272,10 @@ public static class SceneryGenerator
|
|||
/// </summary>
|
||||
private const float RoadHalfWidth = 5.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Retail-faithful post-displacement road test. Ported from ACViewer
|
||||
/// Landblock.OnRoad (Physics/Common/Landblock.cs lines 300-398), which is
|
||||
/// a direct port of FUN_00530d30 in the retail client.
|
||||
///
|
||||
/// Examines the 4 corners of the cell containing (lx, ly) and, depending
|
||||
/// on how many are road vertices (0, 1, 2, 3, or 4), applies a polygonal
|
||||
/// test using the 5-unit road half-width to check if (lx, ly) lies on the
|
||||
/// road ribbon. Returns true if the point is on a road.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Retail-faithful road ribbon test — direct port of ACViewer's
|
||||
/// Landblock.OnRoad (Physics/Common/Landblock.cs lines 300-398), which
|
||||
/// itself is a port of FUN_00530d30 in acclient.exe.
|
||||
/// itself is a port of CLandBlock::on_road (named-retail 0x0052FFF0).
|
||||
///
|
||||
/// Classifies the 4 corners of the cell containing (lx, ly) by road type
|
||||
/// (bits 0-1 of the terrain word) and applies a different geometric test
|
||||
|
|
@ -370,6 +370,54 @@ public static class SceneryGenerator
|
|||
|
||||
private const int CellsPerSide = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Retail CPhysicsObj::obj_within_block check — verifies the object's sorting
|
||||
/// sphere stays entirely within the landblock bounds. Returns true if the object
|
||||
/// is within bounds.
|
||||
///
|
||||
/// Named-retail: CPhysicsObj::obj_within_block (0x00461c30).
|
||||
/// ACViewer: PhysicsObj.obj_within_block.
|
||||
///
|
||||
/// Retail loads the full PhysicsObj, transforms the sorting sphere center via
|
||||
/// LocalToGlobal, then checks center ± radius against [0, BlockLength].
|
||||
/// We approximate by ignoring the sorting sphere center offset (typically small
|
||||
/// for scenery) and checking the spawn position directly against [radius, 192-radius].
|
||||
/// </summary>
|
||||
private static bool IsWithinBlock(float lx, float ly, float sortingSphereRadius)
|
||||
{
|
||||
if (sortingSphereRadius <= 0f) return true;
|
||||
return lx >= sortingSphereRadius && ly >= sortingSphereRadius
|
||||
&& lx < LandblockSize - sortingSphereRadius
|
||||
&& ly < LandblockSize - sortingSphereRadius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sorting sphere radius for a scenery object, with caching.
|
||||
/// Setup objects (0x02xxxxxx) use Setup.SortingSphere.Radius.
|
||||
/// GfxObj objects (0x01xxxxxx) fall back to 0 (allows placement; these are
|
||||
/// typically small items like grass patches whose BSP sphere is tiny).
|
||||
/// </summary>
|
||||
private static float GetSortingSphereRadius(
|
||||
DatCollection dats, uint objectId, Dictionary<uint, float> cache)
|
||||
{
|
||||
if (cache.TryGetValue(objectId, out float cached))
|
||||
return cached;
|
||||
|
||||
float radius = 0f;
|
||||
if ((objectId >> 24) == 0x02)
|
||||
{
|
||||
// Setup — has an explicit SortingSphere
|
||||
var setup = dats.Get<Setup>(objectId);
|
||||
if (setup?.SortingSphere is { } sphere)
|
||||
radius = sphere.Radius;
|
||||
}
|
||||
// GfxObj (0x01) — no Setup.SortingSphere; use 0 (permissive).
|
||||
// These are typically small single-mesh scenery items.
|
||||
|
||||
cache[objectId] = radius;
|
||||
return radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pseudo-random displacement within a cell for a scenery object. Returns a
|
||||
/// Vector3 in local cell-offset space (the caller adds it to the cell corner
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue