Implement Frustum Unit Tests (Currently failing)
This commit is contained in:
327
tests/Geometry/FrustumTests.hpp
Normal file
327
tests/Geometry/FrustumTests.hpp
Normal file
@@ -0,0 +1,327 @@
|
||||
/// Josh's 3D Math Library
|
||||
/// A C++20 Library for 3D Math, Computer Graphics, and Scientific Computing.
|
||||
/// Developed and Maintained by Josh O'Leary @ Redacted Software.
|
||||
/// Special Thanks to William Tomasine II and Maxine Hayes.
|
||||
/// (c) 2024 Redacted Software
|
||||
/// This work is dedicated to the public domain.
|
||||
|
||||
/// @file FrustumTests.hpp
|
||||
/// @desc Unit tests for the Frustum class.
|
||||
/// @edit 2024-07-07
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jtest/jtest.hpp>
|
||||
|
||||
#include <J3ML/Geometry/Frustum.h>
|
||||
#include <J3ML/Geometry/PBVolume.hpp>
|
||||
|
||||
|
||||
Frustum GenIdFrustum(FrustumType t, FrustumHandedness h, FrustumProjectiveSpace p) {
|
||||
Frustum f;
|
||||
f.SetKind(p, h);
|
||||
f.SetViewPlaneDistances(1.f, 100.f);
|
||||
f.SetFrame({0,0,0}, {0,0,-1}, {0,1,0});
|
||||
if (t == FrustumType::Perspective)
|
||||
f.SetPerspective(Math::Pi / 2.f, Math::Pi / 2.f);
|
||||
else
|
||||
f.SetOrthographic(100.f, 100.f);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
#define FOR_EACH_FRUSTUM_CONVENTION(f) \
|
||||
for (int ii = 0; ii < 8; ++ii) { \
|
||||
switch (ii) { \
|
||||
case 0 : f = GenIdFrustum(FrustumType::Perspective, FrustumHandedness::Left, FrustumProjectiveSpace::GL); break; \
|
||||
case 1 : f = GenIdFrustum(FrustumType::Perspective, FrustumHandedness::Right, FrustumProjectiveSpace::GL); break; \
|
||||
case 2 : f = GenIdFrustum(FrustumType::Perspective, FrustumHandedness::Left, FrustumProjectiveSpace::D3D); break; \
|
||||
case 3 : f = GenIdFrustum(FrustumType::Perspective, FrustumHandedness::Right, FrustumProjectiveSpace::D3D); break; \
|
||||
case 4 : f = GenIdFrustum(FrustumType::Orthographic, FrustumHandedness::Left, FrustumProjectiveSpace::GL); break; \
|
||||
case 5 : f = GenIdFrustum(FrustumType::Orthographic, FrustumHandedness::Right, FrustumProjectiveSpace::GL); break; \
|
||||
case 6 : f = GenIdFrustum(FrustumType::Orthographic, FrustumHandedness::Left, FrustumProjectiveSpace::D3D); break; \
|
||||
case 7 : f = GenIdFrustum(FrustumType::Orthographic, FrustumHandedness::Right, FrustumProjectiveSpace::D3D); break; \
|
||||
} \
|
||||
|
||||
|
||||
void FrustumTests() {
|
||||
using namespace jtest;
|
||||
using namespace J3ML::Geometry;
|
||||
using namespace J3ML::Math;
|
||||
|
||||
RNG rng;
|
||||
|
||||
TEST("Frustum::AspectRatio", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
check(EqualAbs(f.AspectRatio(), 1.f));
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
TEST("Frustum::WorldRight", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
if (f.Handedness() == FrustumHandedness::Right) {
|
||||
check(f.WorldRight().Equals({1, 0, 0}));
|
||||
} else { // In the test func, all cameras lock down to -Z, so left-handed cameras need to point their right towards -X then.
|
||||
check(f.WorldRight().Equals({-1, 0, 0}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::Chirality", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
check(f.WorldMatrix().Determinant() > 0.f);
|
||||
check(f.ViewMatrix().Determinant() > 0.f);
|
||||
if (f.Handedness() == FrustumHandedness::Left)
|
||||
check(f.ProjectionMatrix().Determinant4() > 0.f); // left-handed view -> projection space transform does not change handedness.
|
||||
else
|
||||
check(f.ProjectionMatrix().Determinant4() < 0.f); // but right-handed transform should.
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::Planes", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
check(f.NearPlane().Normal.Equals({0,0,1}));
|
||||
check(Math::EqualAbs(f.NearPlane().distance, -1.f));
|
||||
|
||||
check(f.FarPlane().Normal.Equals({0,0,-1}));
|
||||
check(Math::EqualAbs(f.FarPlane().distance, 100.f));
|
||||
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
Vector3 pt;
|
||||
if (i == 8)
|
||||
pt = f.CenterPoint();
|
||||
else
|
||||
pt = f.CornerPoint(i);
|
||||
check(f.NearPlane().SignedDistance(pt) < 1e-3f);
|
||||
check(f.FarPlane().SignedDistance(pt) < 1e-3f);
|
||||
check(f.LeftPlane().SignedDistance(pt) < 1e-3f);
|
||||
check(f.RightPlane().SignedDistance(pt) < 1e-3f);
|
||||
check(f.TopPlane().SignedDistance(pt) < 1e-3f);
|
||||
check(f.BottomPlane().SignedDistance(pt) < 1e-3f);
|
||||
check(f.Contains(pt));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
TEST("Frustum::Corners", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
|
||||
// Corner points are returned in XYZ order: 0: ---, 1: --+, 2: -+-, 3: -++, 4: +--, 5: +-+, 6: ++-, 7: +++
|
||||
if (f.Type() == FrustumType::Perspective && f.Handedness() == FrustumHandedness::Left) {
|
||||
check(f.CornerPoint(0).Equals({1.f, -1.f, -1.f}));
|
||||
check(f.CornerPoint(1).Equals({100.f, -100.f, -100.f}));
|
||||
check(f.CornerPoint(2).Equals({1.f, 1, -1.f}));
|
||||
check(f.CornerPoint(3).Equals({100.f, 100.f, 100.f}));
|
||||
check(f.CornerPoint(4).Equals({-1.f, -1.f, -1.f}));
|
||||
check(f.CornerPoint(5).Equals({-100.f, -100.f, -100.f}));
|
||||
check(f.CornerPoint(6).Equals({-1.f, 1.f, -1.f}));
|
||||
check(f.CornerPoint(7).Equals({-100.f, 100.f, -100.f}));
|
||||
} else if (f.Type() == FrustumType::Perspective && f.Handedness() == FrustumHandedness::Right) {
|
||||
check(f.CornerPoint(0).Equals({-1.f, -1.f, -1.f}));
|
||||
check(f.CornerPoint(1).Equals({-100.f, -100.f, -100.f}));
|
||||
check(f.CornerPoint(2).Equals({-1.f, 1.f, -1.f}));
|
||||
check(f.CornerPoint(3).Equals({-100.f, 100.f, -100.f}));
|
||||
check(f.CornerPoint(4).Equals({1.f, -1.f, -1.f}));
|
||||
check(f.CornerPoint(5).Equals({100.f, -100.f, -100.f}));
|
||||
check(f.CornerPoint(6).Equals({1.f, 1.f, -1.f}));
|
||||
check(f.CornerPoint(7).Equals({100.f, 100.f, -100.f}));
|
||||
} else if (f.Type() == FrustumType::Orthographic && f.Handedness() == FrustumHandedness::Left) {
|
||||
check(f.CornerPoint(0).Equals({50.f, -50.f, -1.f}));
|
||||
check(f.CornerPoint(1).Equals({50.f, -50.f, -100.f}));
|
||||
check(f.CornerPoint(2).Equals({50.f, 50.f, -1.f}));
|
||||
check(f.CornerPoint(3).Equals({50.f, 50.f, -100.f}));
|
||||
check(f.CornerPoint(4).Equals({-50.f, -50.f, -1.f}));
|
||||
check(f.CornerPoint(5).Equals({-50.f, -50.f, -100.f}));
|
||||
check(f.CornerPoint(6).Equals({-50.f, 50.f, -1.f}));
|
||||
check(f.CornerPoint(7).Equals({-50.f, 50.f, -100.f}));
|
||||
} else if (f.Type() == FrustumType::Orthographic && f.Handedness() == FrustumHandedness::Right) {
|
||||
check(f.CornerPoint(0).Equals({-50.f, -50.f, -1.f}));
|
||||
check(f.CornerPoint(1).Equals({-50.f, -50.f, -100.f}));
|
||||
check(f.CornerPoint(2).Equals({-50.f, 50.f, -1.f}));
|
||||
check(f.CornerPoint(3).Equals({-50.f, 50.f, -100.f}));
|
||||
check(f.CornerPoint(4).Equals({50.f, -50.f, -1.f}));
|
||||
check(f.CornerPoint(5).Equals({50.f, -50.f, -100.f}));
|
||||
check(f.CornerPoint(6).Equals({50.f, 50.f, -1.f}));
|
||||
check(f.CornerPoint(7).Equals({50.f, 50.f, -100.f}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::ProjectUnprojectSymmetry", [&rng] {
|
||||
|
||||
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// Orient and locate the Frustum randomly
|
||||
Matrix3x3 rot = Matrix3x3::RandomRotation(rng);
|
||||
f.Transform(rot);
|
||||
f.SetPos(f.Pos() + Vector3::RandomDir(rng, rng.Float(1.f, 100.f)));
|
||||
|
||||
for (int j = 0; j < 100; ++j) {
|
||||
Vector2 pt = Vector2::RandomBox(rng, -1.f, 1.f);
|
||||
Vector3 pos = f.NearPlanePos(pt);
|
||||
Vector3 pt2 = f.Project(pos);
|
||||
check(pt.Equals(pt2.XY()));
|
||||
|
||||
pos = f.FarPlanePos(pt);
|
||||
pt2 = f.Project(pos);
|
||||
check(pt.Equals(pt2.XY()));
|
||||
|
||||
pos = f.PointInside(pt.x, pt.y, rng.Float());
|
||||
pt2 = f.Project(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::PlaneNormalsAreCorrect", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
auto pb = f.ToPBVolume();
|
||||
Vector3 corners[8];
|
||||
Plane planes[6];
|
||||
f.GetCornerPoints(corners);
|
||||
f.GetPlanes(planes);
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
check(pb.Contains(corners[i]));
|
||||
|
||||
for(int i = 0; i < 6; ++i)
|
||||
for(int j = 0; j < 8; ++j)
|
||||
check(planes[i].SignedDistance(corners[j]) <= 0.f);
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::IsConvex", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
Polyhedron p = f.ToPolyhedron();
|
||||
|
||||
for(int i = 0; i < 6; ++i)
|
||||
{
|
||||
Plane p1 = f.GetPlane(i);
|
||||
Plane p2 = p.FacePlane(i);
|
||||
check(p1.Equals(p2));
|
||||
}
|
||||
check(p.EulerFormulaHolds());
|
||||
check(p.IsClosed());
|
||||
check(p.IsConvex());
|
||||
check(!p.IsNull());
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Plane::ProjectToNegativeHalf", [] {
|
||||
Plane p(Vector3(0,1,0), 50.f);
|
||||
|
||||
Vector3 neg = Vector3(0,-100.f, 0);
|
||||
Vector3 pos = Vector3(0, 100.f, 0);
|
||||
check(neg.Equals(p.ProjectToNegativeHalf(neg)));
|
||||
check(!neg.Equals(p.ProjectToPositiveHalf(neg)));
|
||||
|
||||
check(pos.Equals(p.ProjectToPositiveHalf(pos)));
|
||||
check(!pos.Equals(p.ProjectToNegativeHalf(pos)));
|
||||
});
|
||||
|
||||
TEST("Frustum::Contains", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
for(int i = 0; i < 8; ++i)
|
||||
{
|
||||
Vector3 corner = f.CornerPoint(i);
|
||||
Vector3 closestPoint = f.ClosestPoint(corner);
|
||||
float distance = f.Distance(corner);
|
||||
if (!f.Contains(corner) || distance > 1e-4f)
|
||||
//LOGE("Closest point to %s: %s", corner.ToString().c_str(), closestPoint.ToString().c_str());
|
||||
check(f.Contains(corner));
|
||||
check(distance < 10.f);
|
||||
}
|
||||
check(f.Contains(f.CenterPoint()));
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::ContainsCorners", [&rng] {
|
||||
constexpr float SCALE = 1e2f;
|
||||
|
||||
Vector3 pt = Vector3::RandomBox(rng, Vector3::FromScalar(-SCALE), Vector3::FromScalar(SCALE));
|
||||
Frustum b;// = RandomFrustumContainingPoint(rng, pt);
|
||||
|
||||
for(int i = 0; i < 9; ++i)
|
||||
{
|
||||
Vector3 point = (i == 8) ? b.CenterPoint() : b.CornerPoint(i);
|
||||
|
||||
check(b.NearPlane().SignedDistance(point) < 1e-3f);
|
||||
check(b.FarPlane().SignedDistance(point) < 1e-3f);
|
||||
check(b.LeftPlane().SignedDistance(point) < 1e-3f);
|
||||
check(b.RightPlane().SignedDistance(point) < 1e-3f);
|
||||
check(b.TopPlane().SignedDistance(point) < 1e-3f);
|
||||
check(b.BottomPlane().SignedDistance(point) < 1e-3f);
|
||||
check(b.Contains(point));
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::Matrices", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
if (f.Handedness() == FrustumHandedness::Right) {
|
||||
Matrix4x4 wm = f.WorldMatrix();
|
||||
check(wm.IsIdentity());
|
||||
|
||||
Matrix4x4 vm = f.ViewMatrix();
|
||||
check(vm.IsIdentity());
|
||||
} else {
|
||||
Matrix4x4 wm = f.WorldMatrix() * Matrix4x4::RotateY(Math::Pi);
|
||||
check(wm.IsIdentity());
|
||||
|
||||
Matrix4x4 vm = f.ViewMatrix() * Matrix4x4::RotateY(Math::Pi);
|
||||
check(vm.IsIdentity());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::Projection", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
const float nearD = (f.ProjectiveSpace() == FrustumProjectiveSpace::D3D) ? 0.f : -1.f;
|
||||
|
||||
// Corner points are returned in XYZ order: 0: ---, 1: --+, 2: -+-, 3: -++, 4: +--, 5: +-+, 6: ++-, 7: +++
|
||||
check(f.Project(f.CornerPoint(0)).Equals({-1,-1, nearD}));
|
||||
check(f.Project(f.CornerPoint(1)).Equals({-1,-1, 1}));
|
||||
check(f.Project(f.CornerPoint(2)).Equals({-1,1, nearD}));
|
||||
check(f.Project(f.CornerPoint(3)).Equals({-1,1, 1}));
|
||||
check(f.Project(f.CornerPoint(4)).Equals({1,-1, nearD}));
|
||||
check(f.Project(f.CornerPoint(5)).Equals({1,-1, 1}));
|
||||
check(f.Project(f.CornerPoint(6)).Equals({1,1, nearD}));
|
||||
check(f.Project(f.CornerPoint(7)).Equals({1,1, 1}));
|
||||
}
|
||||
});
|
||||
|
||||
TEST("Frustum::UnProject", [] {
|
||||
Frustum f;
|
||||
FOR_EACH_FRUSTUM_CONVENTION(f)
|
||||
if (f.Type() == FrustumType::Perspective) {
|
||||
Ray r = f.UnProject(0, 0);
|
||||
check(r.Origin.Equals(f.Pos()));
|
||||
check(r.Origin.Equals({0,0,0}));
|
||||
check(r.Direction.Equals({0,0,-1}));
|
||||
|
||||
|
||||
r = f.UnProject(-1, -1);
|
||||
check(r.Origin.Equals(f.Pos()));
|
||||
check(r.Origin.Equals({0,0,0}));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
TEST("Frustum::UnProjectFromNearPlane", []{});
|
||||
|
||||
TEST("Frustum::UnProjectFromNearPlane", []{});
|
||||
}
|
Reference in New Issue
Block a user