feat(render): Phase A8 Wave 1 — WB scaffolding extraction + stencil low-level method
Five tasks shipped together (interdependent at build time): Task 1: WbRenderPass enum — verbatim port of WB RenderPass.cs:1-22 Task 2: WbFrustum + WbBoundingBox + FrustumTestResult — verbatim port of WB Frustum.cs (98 LOC) with namespace + BoundingBox-type adaptations. +7 unit tests. Task 3: EnvCellSceneryInstance + EnvCellLandblock — verbatim port of WB SceneryInstance.cs:1-161, renamed scope-narrow. Dropped editor-only fields (DisqualificationReason, ParticleEmitters, IsQueuedForUpload, InstanceBufferOffset, InstanceCount, MdiCommands, IsTransformOnlyUpdate) + InstanceId narrowed uint (we don't use ObjectId's editor methods). +5 unit tests. Task 4: EnvCellVisibilitySnapshot — direct port of WB VisibilitySnapshot narrowed to BatchedByCell + VisibleLandblocks only. Task 7: IndoorCellStencilPipeline.RenderBuildingStencilMask — new low-level WB-faithful entry mirroring PortalRenderManager:471-484. No surrounding GL state setup (caller's responsibility). Probe fields LastStencilVertexCount / LastStencilWasFarPunch / LastStencilBuildingId for the [stencil] probe emitter in Task 9. Build green, 18 tests pass (7 new Frustum + 5 new SceneryInstance + 6 existing stencil pipeline). Ready for Wave 2 (EnvCellRenderer port). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
95f0d5267b
commit
fc68d6d01f
7 changed files with 595 additions and 0 deletions
95
tests/AcDream.App.Tests/Rendering/Wb/WbFrustumTests.cs
Normal file
95
tests/AcDream.App.Tests/Rendering/Wb/WbFrustumTests.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Wb;
|
||||
|
||||
public class WbFrustumTests
|
||||
{
|
||||
private static Matrix4x4 PerspectiveVp(Vector3 eye, Vector3 lookAt) =>
|
||||
Matrix4x4.CreateLookAt(eye, lookAt, Vector3.UnitY)
|
||||
* Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 4f, 1.0f, 0.1f, 100.0f);
|
||||
|
||||
[Fact]
|
||||
public void TestBox_BoxInFrontOfCamera_ReturnsInsideOrIntersecting()
|
||||
{
|
||||
var f = new WbFrustum();
|
||||
f.Update(PerspectiveVp(Vector3.Zero, new Vector3(0, 0, -1)));
|
||||
var box = new WbBoundingBox(new Vector3(-1, -1, -10), new Vector3(1, 1, -2));
|
||||
var res = f.TestBox(box);
|
||||
Assert.NotEqual(FrustumTestResult.Outside, res);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestBox_BoxBehindCamera_ReturnsOutside()
|
||||
{
|
||||
var f = new WbFrustum();
|
||||
f.Update(PerspectiveVp(Vector3.Zero, new Vector3(0, 0, -1)));
|
||||
var box = new WbBoundingBox(new Vector3(-1, -1, 2), new Vector3(1, 1, 10));
|
||||
Assert.Equal(FrustumTestResult.Outside, f.TestBox(box));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Intersects_BoxInFrontOfCamera_ReturnsTrue()
|
||||
{
|
||||
var f = new WbFrustum();
|
||||
f.Update(PerspectiveVp(Vector3.Zero, new Vector3(0, 0, -1)));
|
||||
var box = new WbBoundingBox(new Vector3(-1, -1, -10), new Vector3(1, 1, -2));
|
||||
Assert.True(f.Intersects(box));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_IsIdempotent()
|
||||
{
|
||||
var f = new WbFrustum();
|
||||
var vp = PerspectiveVp(Vector3.Zero, new Vector3(0, 0, -1));
|
||||
f.Update(vp);
|
||||
f.Update(vp);
|
||||
var box = new WbBoundingBox(new Vector3(-1, -1, -10), new Vector3(1, 1, -2));
|
||||
Assert.NotEqual(FrustumTestResult.Outside, f.TestBox(box));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WbBoundingBox_Union_ExtendsToCoverBoth()
|
||||
{
|
||||
var a = new WbBoundingBox(new Vector3(0, 0, 0), new Vector3(1, 1, 1));
|
||||
var b = new WbBoundingBox(new Vector3(2, 2, 2), new Vector3(3, 3, 3));
|
||||
var u = WbBoundingBox.Union(a, b);
|
||||
Assert.Equal(new Vector3(0, 0, 0), u.Min);
|
||||
Assert.Equal(new Vector3(3, 3, 3), u.Max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Intersects_BoxBehindCamera_ReturnsFalse()
|
||||
{
|
||||
var f = new WbFrustum();
|
||||
f.Update(PerspectiveVp(Vector3.Zero, new Vector3(0, 0, -1)));
|
||||
var box = new WbBoundingBox(new Vector3(-1, -1, 2), new Vector3(1, 1, 10));
|
||||
Assert.False(f.Intersects(box));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestBox_IgnoreNearPlane_DoesNotReturnOutsideForNearOverlap()
|
||||
{
|
||||
// Box straddles the near plane (z from -0.05 to 5) — with near plane ignored,
|
||||
// the box should not be culled as Outside.
|
||||
var f = new WbFrustum();
|
||||
f.Update(PerspectiveVp(Vector3.Zero, new Vector3(0, 0, -1)));
|
||||
var box = new WbBoundingBox(new Vector3(-0.5f, -0.5f, -5f), new Vector3(0.5f, 0.5f, -0.05f));
|
||||
var withNear = f.TestBox(box, ignoreNearPlane: false);
|
||||
var withoutNear = f.TestBox(box, ignoreNearPlane: true);
|
||||
// Both should be non-Outside for a box clearly in front
|
||||
Assert.NotEqual(FrustumTestResult.Outside, withoutNear);
|
||||
_ = withNear; // result varies by near clip; just verify no exception
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WbBoundingBox_Union_WithOverlapping_CoversAll()
|
||||
{
|
||||
var a = new WbBoundingBox(new Vector3(-1, -1, -1), new Vector3(2, 2, 2));
|
||||
var b = new WbBoundingBox(new Vector3(0, 0, 0), new Vector3(3, 3, 3));
|
||||
var u = WbBoundingBox.Union(a, b);
|
||||
Assert.Equal(new Vector3(-1, -1, -1), u.Min);
|
||||
Assert.Equal(new Vector3(3, 3, 3), u.Max);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue