User re-gate after 2163308/987313a: run-from-town stairs FIXED, barrel GONE - but the stairs still vanish by VIEWING ANGLE (visible climbing down, gone climbing up; same at the tower top). The gate3 probe data exonerates everything downstream: the entity always draws with correct batches when it reaches the dispatcher (cache hit:119, restZ correct, zero WALK-REJECTs, never clip-culled) - so the vanish lives in the one gaze-dependent gate the probe cannot see: the bounds-based cullers. WorldEntity.RefreshAabb was a fixed +-5 m box around the entity ANCHOR. The staircase's 43 parts spiral 15 m ABOVE the anchor, and BOTH visibility gates derive from the box: the dispatcher's per-entity frustum cull AND RetailPViewRenderer.EntitySphere (the viewcone sphere = this box's bounding sphere). Looking up the spiral put the anchor's neighborhood out of view -> the whole entity culled while 15 m of it stood in front of the camera; looking down kept the anchor in view -> visible. Exactly the reported asymmetry. Fix: expand the box by the largest MeshRef part-translation magnitude (rotation-invariant, so entity.Rotation needs no handling; identity- part entities get offset 0 - behavior unchanged; scenery scale is already baked into the part transforms). Suites: App 246+1skip / Core 1431+2skip / UI 420 / Net 294. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
2.6 KiB
C#
76 lines
2.6 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.World;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.World;
|
|
|
|
public class WorldEntityAabbTests
|
|
{
|
|
[Fact]
|
|
public void Aabb_DefaultRadius_PositionPlusMinus5()
|
|
{
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 1,
|
|
SourceGfxObjOrSetupId = 0,
|
|
Position = new Vector3(10, 20, 30),
|
|
Rotation = System.Numerics.Quaternion.Identity,
|
|
MeshRefs = System.Array.Empty<MeshRef>(),
|
|
};
|
|
entity.RefreshAabb();
|
|
|
|
Assert.Equal(new Vector3(5, 15, 25), entity.AabbMin);
|
|
Assert.Equal(new Vector3(15, 25, 35), entity.AabbMax);
|
|
}
|
|
|
|
[Fact]
|
|
public void Aabb_MultiPartOffsets_CoverTheParts()
|
|
{
|
|
// #119 follow-up: the AAB3 tower staircase — 43 parts spiralling to
|
|
// (3, 3, 15.15) root-relative. The box (and the viewcone sphere derived
|
|
// from it) must cover the parts, not just the ±5 m anchor neighborhood;
|
|
// the anchor-only box made the staircase vanish whenever the gaze left
|
|
// the base (visible climbing down, gone climbing up).
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 1,
|
|
SourceGfxObjOrSetupId = 0x020003F2,
|
|
Position = new Vector3(300, -132, 112),
|
|
Rotation = System.Numerics.Quaternion.Identity,
|
|
MeshRefs = new[]
|
|
{
|
|
new MeshRef(0x01000E2A, Matrix4x4.CreateTranslation(0f, 3f, 1.55f)),
|
|
new MeshRef(0x01000E2D, Matrix4x4.CreateTranslation(-1.75f, 3f, 15.15f)),
|
|
},
|
|
};
|
|
entity.RefreshAabb();
|
|
|
|
// Top part sits at z = 112 + 15.15 = 127.15 — must be inside the box.
|
|
Assert.True(entity.AabbMax.Z >= 127.15f,
|
|
$"box top {entity.AabbMax.Z} must cover the highest part (z=127.15)");
|
|
// Conservative symmetric expansion still contains the old ±5 box.
|
|
Assert.True(entity.AabbMin.Z <= 107f && entity.AabbMax.X >= 305f);
|
|
}
|
|
|
|
[Fact]
|
|
public void Aabb_DirtyFlag_SetByMutator_ClearedByRefresh()
|
|
{
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 1,
|
|
SourceGfxObjOrSetupId = 0,
|
|
Position = new Vector3(10, 20, 30),
|
|
Rotation = System.Numerics.Quaternion.Identity,
|
|
MeshRefs = System.Array.Empty<MeshRef>(),
|
|
};
|
|
entity.RefreshAabb();
|
|
Assert.False(entity.AabbDirty);
|
|
|
|
entity.SetPosition(new Vector3(100, 200, 300));
|
|
Assert.True(entity.AabbDirty);
|
|
|
|
entity.RefreshAabb();
|
|
Assert.False(entity.AabbDirty);
|
|
Assert.Equal(new Vector3(95, 195, 295), entity.AabbMin);
|
|
}
|
|
}
|