using System.Numerics; using AcDream.App.Rendering; using Xunit; namespace AcDream.Core.Tests.Rendering; public class FrustumCullerTests { [Fact] public void IdentityVP_EverythingVisible() { // Identity VP: all planes at infinity, every box is visible. // Actually identity VP has degenerate planes — instead use a // simple ortho that covers [-1,1]³. var vp = Matrix4x4.CreateOrthographic(2f, 2f, 0.1f, 100f); var planes = FrustumPlanes.FromViewProjection(vp); // Box at origin, well within the frustum. Assert.True(FrustumCuller.IsAabbVisible(planes, new Vector3(-0.5f, -0.5f, -50f), new Vector3(0.5f, 0.5f, -1f))); } [Fact] public void PerspectiveCamera_BoxInFront_Visible() { var view = Matrix4x4.CreateLookAt( new Vector3(0, 0, 0), new Vector3(0, 0, -1), Vector3.UnitY); var proj = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3f, 1f, 1f, 1000f); var planes = FrustumPlanes.FromViewProjection(view * proj); // Box directly in front of the camera. Assert.True(FrustumCuller.IsAabbVisible(planes, new Vector3(-10f, -10f, -100f), new Vector3(10f, 10f, -10f))); } [Fact] public void PerspectiveCamera_BoxBehind_NotVisible() { var view = Matrix4x4.CreateLookAt( new Vector3(0, 0, 0), new Vector3(0, 0, -1), Vector3.UnitY); var proj = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3f, 1f, 1f, 1000f); var planes = FrustumPlanes.FromViewProjection(view * proj); // Box entirely behind the camera (positive Z). Assert.False(FrustumCuller.IsAabbVisible(planes, new Vector3(-10f, -10f, 10f), new Vector3(10f, 10f, 100f))); } [Fact] public void PerspectiveCamera_BoxFarLeft_NotVisible() { var view = Matrix4x4.CreateLookAt( new Vector3(0, 0, 0), new Vector3(0, 0, -1), Vector3.UnitY); var proj = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3f, 1f, 1f, 1000f); var planes = FrustumPlanes.FromViewProjection(view * proj); // Box way off to the left. Assert.False(FrustumCuller.IsAabbVisible(planes, new Vector3(-1000f, -10f, -50f), new Vector3(-500f, 10f, -10f))); } [Fact] public void PerspectiveCamera_BoxStraddlingNearPlane_Visible() { var view = Matrix4x4.CreateLookAt( new Vector3(0, 0, 0), new Vector3(0, 0, -1), Vector3.UnitY); var proj = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3f, 1f, 1f, 1000f); var planes = FrustumPlanes.FromViewProjection(view * proj); // Box that straddles the near plane (conservative: visible). Assert.True(FrustumCuller.IsAabbVisible(planes, new Vector3(-1f, -1f, -2f), new Vector3(1f, 1f, 0.5f))); } [Theory] [InlineData(0.1f, 1000f)] [InlineData(1f, 1000f)] [InlineData(1f, 100f)] [InlineData(2.5f, 5000f)] public void PerspectiveCamera_ExtractedNearPlane_SitsAtTheCameraNearDistance( float nearDistance, float farDistance) { // #248: the extracted near plane must sit at exactly the camera's near // value. The camera sits at the origin looking down -Z, so the near // plane's signed distance from the eye IS `nearDistance`, and for a // plane stored as (normal, d) with the eye at the origin that signed // distance is just `-d`. // // This is what distinguishes the [0,1] extraction from the [-1,1] one: // the GL form `col4 + col3` would report -n·f/(2f-n) here — about 0.5 // for a 1.0 near — so any regression back to it fails this outright. var view = Matrix4x4.CreateLookAt( new Vector3(0, 0, 0), new Vector3(0, 0, -1), Vector3.UnitY); var proj = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3f, 16f / 9f, nearDistance, farDistance); var planes = FrustumPlanes.FromViewProjection(view * proj); var near = planes.Near; // The plane is normalized and faces down -Z, into the frustum. Assert.Equal(1f, new Vector3(near.X, near.Y, near.Z).Length(), 4); Assert.Equal(0f, near.X, 4); Assert.Equal(0f, near.Y, 4); Assert.Equal(-1f, near.Z, 4); // And it stands off the eye by exactly the camera's near distance. Assert.Equal(nearDistance, -near.W, 3); // A point just inside the near plane is kept; one just outside is not. float eps = nearDistance * 0.01f; Assert.True(FrustumCuller.IsAabbVisible(planes, new Vector3(-0.01f, -0.01f, -(nearDistance + eps)), new Vector3(0.01f, 0.01f, -(nearDistance + eps)))); Assert.False(FrustumCuller.IsAabbVisible(planes, new Vector3(-0.01f, -0.01f, -(nearDistance - eps)), new Vector3(0.01f, 0.01f, -(nearDistance - eps)))); } [Fact] public void PerspectiveCamera_FarPlane_IsUnchangedByTheNearFix() { // #248 touches NEAR only. `col4 - col3` is the far plane under both // clip-space conventions, so pin it alongside to prove the fix did not // drift it. const float FarDistance = 100f; var view = Matrix4x4.CreateLookAt( new Vector3(0, 0, 0), new Vector3(0, 0, -1), Vector3.UnitY); var proj = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3f, 16f / 9f, 1f, FarDistance); var planes = FrustumPlanes.FromViewProjection(view * proj); var far = planes.Far; Assert.Equal(1f, new Vector3(far.X, far.Y, far.Z).Length(), 4); Assert.Equal(1f, far.Z, 4); Assert.Equal(FarDistance, far.W, 3); } [Fact] public void PerspectiveCamera_BoxBeyondFarPlane_NotVisible() { var view = Matrix4x4.CreateLookAt( new Vector3(0, 0, 0), new Vector3(0, 0, -1), Vector3.UnitY); var proj = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3f, 1f, 1f, 100f); var planes = FrustumPlanes.FromViewProjection(view * proj); // Box far beyond the far plane. Assert.False(FrustumCuller.IsAabbVisible(planes, new Vector3(-10f, -10f, -500f), new Vector3(10f, 10f, -200f))); } [Fact] public void AcdreamCamera_LandblockInFront_Visible() { // Simulate acdream's actual camera setup (Z-up, looking +Y, // positioned at Holtburg). A landblock-sized AABB at the // camera's forward should be visible. var view = Matrix4x4.CreateLookAt( new Vector3(96f, 96f, 150f), // camera pos (center of a landblock, elevated) new Vector3(96f, 288f, 100f), // looking roughly +Y (forward toward next landblock) Vector3.UnitZ); // Z-up var proj = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3f, 16f / 9f, 1f, 5000f); var planes = FrustumPlanes.FromViewProjection(view * proj); // Landblock-sized AABB 192 units ahead in Y. Assert.True(FrustumCuller.IsAabbVisible(planes, new Vector3(0f, 192f, 50f), new Vector3(192f, 384f, 200f))); // Landblock behind the camera (negative Y relative to camera). Assert.False(FrustumCuller.IsAabbVisible(planes, new Vector3(0f, -384f, 50f), new Vector3(192f, -192f, 200f))); } }