// CellVisibilityFromRootTests.cs — UCG W2 Task 2: tests for // CellVisibility.ComputeVisibilityFromRoot. // // Two acceptance criteria (from the W2 Task 2 spec): // (a) ComputeVisibilityFromRoot(null, pos) is fallback-equivalent to // ComputeVisibility(pos) — same CameraCell answer. // (b) ComputeVisibilityFromRoot(root, pos) with a registered root returns // a result whose CameraCell is that root, regardless of whether 'pos' // is geometrically inside it. // // CellVisibility is intentionally free of GL types — it can be unit-tested // without a GPU context (confirmed: only System.Numerics dependency). using System.Numerics; using AcDream.App.Rendering; using Xunit; namespace AcDream.App.Tests.Rendering; public class CellVisibilityFromRootTests { // ------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------ /// /// Build a minimal LoadedCell with an axis-aligned bounding box and identity /// transform so PointInCell works for a position inside the box. /// private static LoadedCell MakeCell(uint cellId, Vector3 boundsMin, Vector3 boundsMax) { return new LoadedCell { CellId = cellId, WorldTransform = Matrix4x4.Identity, InverseWorldTransform = Matrix4x4.Identity, LocalBoundsMin = boundsMin, LocalBoundsMax = boundsMax, Portals = new(), ClipPlanes = new(), PortalPolygons = new(), }; } // ------------------------------------------------------------------ // (a) Fallback equivalence: null root → same answer as ComputeVisibility // ------------------------------------------------------------------ [Fact] public void ComputeVisibilityFromRoot_NullRoot_FallsBackToPositionBased() { // Arrange: one cell covering [0,10]^3, position inside it. var cv = new CellVisibility(); var cell = MakeCell(0xA9B40101u, Vector3.Zero, new Vector3(10, 10, 10)); cv.AddCell(cell); var pos = new Vector3(5, 5, 5); // inside the cell // Act: null-root and direct-position paths must agree on CameraCell. var fromPos = cv.ComputeVisibility(pos); var fromNull = cv.ComputeVisibilityFromRoot(null, pos); // Assert Assert.NotNull(fromPos); Assert.NotNull(fromNull); Assert.Equal(fromPos!.CameraCell?.CellId, fromNull!.CameraCell?.CellId); Assert.Equal(cell.CellId, fromNull.CameraCell?.CellId); } [Fact] public void ComputeVisibilityFromRoot_NullRoot_NoCells_ReturnsNull() { // With no cells registered both paths return null. var cv = new CellVisibility(); var posOutdoors = new Vector3(100, 100, 100); var fromPos = cv.ComputeVisibility(posOutdoors); var fromNull = cv.ComputeVisibilityFromRoot(null, posOutdoors); Assert.Null(fromPos); Assert.Null(fromNull); } [Fact] public void ComputeVisibilityFromRoot_NullRoot_PositionOutsideAllCells_ReturnsNull() { // Cell exists but position is outside it — both paths produce null CameraCell. var cv = new CellVisibility(); var cell = MakeCell(0xA9B40102u, Vector3.Zero, new Vector3(5, 5, 5)); cv.AddCell(cell); var posOutside = new Vector3(100, 100, 100); var fromPos = cv.ComputeVisibility(posOutside); var fromNull = cv.ComputeVisibilityFromRoot(null, posOutside); // Both should return null (no grace frames built up yet) Assert.Null(fromPos?.CameraCell); Assert.Null(fromNull?.CameraCell); } // ------------------------------------------------------------------ // (b) Supplied root is used as BFS root → CameraCell == root // ------------------------------------------------------------------ [Fact] public void ComputeVisibilityFromRoot_RegisteredRoot_CameraCellIsSuppliedRoot() { // Arrange: cell registered in CellVisibility. var cv = new CellVisibility(); var cell = MakeCell(0xA9B40103u, Vector3.Zero, new Vector3(10, 10, 10)); cv.AddCell(cell); // The position can be OUTSIDE the cell — physics already determined membership // via BSP, we just trust that answer. var posAnywhere = new Vector3(999, 999, 999); // Act var result = cv.ComputeVisibilityFromRoot(cell, posAnywhere); // Assert: CameraCell is the supplied root. Assert.NotNull(result); Assert.Same(cell, result!.CameraCell); } [Fact] public void ComputeVisibilityFromRoot_RegisteredRoot_IncludesRootInVisibleCells() { var cv = new CellVisibility(); var cell = MakeCell(0xA9B40104u, Vector3.Zero, new Vector3(10, 10, 10)); cv.AddCell(cell); var result = cv.ComputeVisibilityFromRoot(cell, Vector3.Zero); Assert.NotNull(result); Assert.Contains(cell.CellId, result!.VisibleCellIds); } [Fact] public void ComputeVisibilityFromRoot_RegisteredRoot_LastVisibilityResultUpdated() { var cv = new CellVisibility(); var cell = MakeCell(0xA9B40105u, Vector3.Zero, new Vector3(10, 10, 10)); cv.AddCell(cell); var result = cv.ComputeVisibilityFromRoot(cell, Vector3.Zero); Assert.Same(result, cv.LastVisibilityResult); } }