608 lines
22 KiB
C++
608 lines
22 KiB
C++
#include "../src/utils/Logger.h"
|
|
#include "../src/core/UsdSceneRenderer.h"
|
|
#include "../src/core/ViewportCamera.h"
|
|
#include "../src/utils/GLExt.h"
|
|
#include <pxr/usd/usd/stage.h>
|
|
#include <pxr/usd/usd/primRange.h>
|
|
#include <pxr/usd/usdGeom/cube.h>
|
|
#include <pxr/usd/usdGeom/sphere.h>
|
|
#include <pxr/usd/usdGeom/xform.h>
|
|
#include <pxr/usd/usdGeom/bboxCache.h>
|
|
#include <pxr/usd/usdGeom/tokens.h>
|
|
#include <Windows.h>
|
|
#include <iostream>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace UsdLayerManager;
|
|
using namespace pxr;
|
|
|
|
static HWND g_hwnd = nullptr;
|
|
static HDC g_hdc = nullptr;
|
|
static HGLRC g_hglrc = nullptr;
|
|
|
|
static int g_testsPassed = 0;
|
|
static int g_testsFailed = 0;
|
|
static int g_testsSkipped = 0;
|
|
|
|
#define TEST_ASSERT(cond, msg) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::cout << "[FAIL] " << (msg) << " (at " << __FILE__ << ":" << __LINE__ << ")" << std::endl; \
|
|
g_testsFailed++; \
|
|
return false; \
|
|
} else { \
|
|
std::cout << "[PASS] " << (msg) << std::endl; \
|
|
g_testsPassed++; \
|
|
} \
|
|
} while(0)
|
|
|
|
#define TEST_SKIP(msg) \
|
|
do { \
|
|
std::cout << "[SKIP] " << (msg) << std::endl; \
|
|
g_testsSkipped++; \
|
|
return true; \
|
|
} while(0)
|
|
|
|
static bool CreateGLContext() {
|
|
WNDCLASSEXW wc = { sizeof(wc), CS_OWNDC, DefWindowProcW, 0L, 0L,
|
|
GetModuleHandle(nullptr), nullptr, nullptr, nullptr, nullptr,
|
|
L"ViewportTest", nullptr };
|
|
RegisterClassExW(&wc);
|
|
|
|
g_hwnd = CreateWindowW(wc.lpszClassName, L"ViewportTest", WS_OVERLAPPEDWINDOW,
|
|
100, 100, 800, 600, nullptr, nullptr, wc.hInstance, nullptr);
|
|
if (!g_hwnd) {
|
|
std::cerr << "[FAIL] CreateWindowW failed" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
g_hdc = GetDC(g_hwnd);
|
|
PIXELFORMATDESCRIPTOR pfd = {};
|
|
pfd.nSize = sizeof(pfd);
|
|
pfd.nVersion = 1;
|
|
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
|
pfd.iPixelType = PFD_TYPE_RGBA;
|
|
pfd.cColorBits = 32;
|
|
pfd.cDepthBits = 24;
|
|
pfd.cStencilBits = 8;
|
|
|
|
int pixelFormat = ChoosePixelFormat(g_hdc, &pfd);
|
|
if (pixelFormat == 0) {
|
|
std::cerr << "[FAIL] ChoosePixelFormat failed" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (!SetPixelFormat(g_hdc, pixelFormat, &pfd)) {
|
|
std::cerr << "[FAIL] SetPixelFormat failed" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
g_hglrc = wglCreateContext(g_hdc);
|
|
if (!g_hglrc) {
|
|
std::cerr << "[FAIL] wglCreateContext failed" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (!wglMakeCurrent(g_hdc, g_hglrc)) {
|
|
std::cerr << "[FAIL] wglMakeCurrent failed" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void DestroyGLContext() {
|
|
if (g_hglrc) { wglMakeCurrent(nullptr, nullptr); wglDeleteContext(g_hglrc); g_hglrc = nullptr; }
|
|
if (g_hdc) { ReleaseDC(g_hwnd, g_hdc); g_hdc = nullptr; }
|
|
if (g_hwnd) { DestroyWindow(g_hwnd); UnregisterClassW(L"ViewportTest", GetModuleHandle(nullptr)); g_hwnd = nullptr; }
|
|
}
|
|
|
|
static UsdStageRefPtr CreateTestStageWithCube() {
|
|
UsdStageRefPtr stage = UsdStage::CreateInMemory();
|
|
if (!stage) return nullptr;
|
|
|
|
UsdGeomXform xform = UsdGeomXform::Define(stage, SdfPath("/TestXform"));
|
|
UsdGeomCube cube = UsdGeomCube::Define(stage, SdfPath("/TestXform/TestCube"));
|
|
cube.GetSizeAttr().Set(50.0);
|
|
cube.GetDisplayColorAttr().Set(VtVec3fArray{{1.0f, 0.0f, 0.0f}});
|
|
cube.GetDisplayOpacityAttr().Set(VtFloatArray{1.0f});
|
|
|
|
UsdGeomSphere sphere = UsdGeomSphere::Define(stage, SdfPath("/TestSphere"));
|
|
sphere.GetRadiusAttr().Set(25.0);
|
|
sphere.GetDisplayColorAttr().Set(VtVec3fArray{{0.0f, 1.0f, 0.0f}});
|
|
sphere.GetDisplayOpacityAttr().Set(VtFloatArray{1.0f});
|
|
|
|
return stage;
|
|
}
|
|
|
|
static GfVec4d MulMatrixVec4(const GfMatrix4d& m, const GfVec4d& v) {
|
|
GfVec4d result;
|
|
for (int j = 0; j < 4; ++j) {
|
|
result[j] = 0;
|
|
for (int i = 0; i < 4; ++i) {
|
|
result[j] += v[i] * m[i][j];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool Test_StageCreation() {
|
|
std::cout << "\n=== Test 1: Stage Creation ===" << std::endl;
|
|
|
|
UsdStageRefPtr stage = CreateTestStageWithCube();
|
|
TEST_ASSERT(stage != nullptr, "In-memory stage created");
|
|
|
|
size_t primCount = 0;
|
|
for ([[maybe_unused]] const auto& prim : stage->Traverse()) { ++primCount; }
|
|
std::cout << " Prim count: " << primCount << std::endl;
|
|
TEST_ASSERT(primCount >= 3, "Stage has at least 3 prims (xform, cube, sphere)");
|
|
|
|
UsdPrim cubePrim = stage->GetPrimAtPath(SdfPath("/TestXform/TestCube"));
|
|
TEST_ASSERT(cubePrim.IsValid(), "Cube prim found at /TestXform/TestCube");
|
|
TEST_ASSERT(cubePrim.IsA<UsdGeomCube>(), "Prim is a UsdGeomCube");
|
|
|
|
UsdPrim spherePrim = stage->GetPrimAtPath(SdfPath("/TestSphere"));
|
|
TEST_ASSERT(spherePrim.IsValid(), "Sphere prim found at /TestSphere");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Test_StageBounds() {
|
|
std::cout << "\n=== Test 2: Stage Bounds Computation ===" << std::endl;
|
|
|
|
UsdStageRefPtr stage = CreateTestStageWithCube();
|
|
TEST_ASSERT(stage != nullptr, "Stage created for bounds test");
|
|
|
|
UsdSceneRenderer renderer;
|
|
renderer.SetStage(stage);
|
|
|
|
GfRange3d bounds = renderer.ComputeStageBounds();
|
|
TEST_ASSERT(!bounds.IsEmpty(), "Stage bounds are not empty");
|
|
|
|
GfVec3d min = bounds.GetMin();
|
|
GfVec3d max = bounds.GetMax();
|
|
std::cout << " Bounds min: (" << min[0] << ", " << min[1] << ", " << min[2] << ")" << std::endl;
|
|
std::cout << " Bounds max: (" << max[0] << ", " << max[1] << ", " << max[2] << ")" << std::endl;
|
|
|
|
GfVec3d size = bounds.GetSize();
|
|
double maxSize = size.GetLength();
|
|
std::cout << " Bounds size length: " << maxSize << std::endl;
|
|
TEST_ASSERT(maxSize > 0.0, "Bounds size is greater than zero");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Test_CameraSetup() {
|
|
std::cout << "\n=== Test 3: Camera Setup ===" << std::endl;
|
|
|
|
ViewportCamera camera;
|
|
TEST_ASSERT(camera.GetMode() == ViewportCamera::CameraMode::Free, "Camera starts in Free mode");
|
|
|
|
GfVec3d eye = camera.GetEye();
|
|
GfVec3d focal = camera.GetFocalPoint();
|
|
std::cout << " Default eye: (" << eye[0] << ", " << eye[1] << ", " << eye[2] << ")" << std::endl;
|
|
std::cout << " Default focal: (" << focal[0] << ", " << focal[1] << ", " << focal[2] << ")" << std::endl;
|
|
std::cout << " Near/Far: " << camera.GetNearClip() << " / " << camera.GetFarClip() << std::endl;
|
|
std::cout << " FOV: " << camera.GetFOV() << ", Aspect: " << camera.GetAspectRatio() << std::endl;
|
|
|
|
UsdStageRefPtr stage = CreateTestStageWithCube();
|
|
camera.SetStage(stage);
|
|
|
|
GfRange3d bounds = GfRange3d(GfVec3d(-25, -25, -25), GfVec3d(25, 25, 25));
|
|
camera.FrameBoundingBox(bounds);
|
|
|
|
eye = camera.GetEye();
|
|
focal = camera.GetFocalPoint();
|
|
std::cout << " After FrameBB eye: (" << eye[0] << ", " << eye[1] << ", " << eye[2] << ")" << std::endl;
|
|
std::cout << " After FrameBB focal: (" << focal[0] << ", " << focal[1] << ", " << focal[2] << ")" << std::endl;
|
|
std::cout << " After FrameBB Near/Far: " << camera.GetNearClip() << " / " << camera.GetFarClip() << std::endl;
|
|
|
|
TEST_ASSERT(camera.GetNearClip() > 0.0f, "Near clip is positive after framing");
|
|
TEST_ASSERT(camera.GetFarClip() > camera.GetNearClip(), "Far clip > near clip after framing");
|
|
|
|
GfMatrix4d viewMatrix = camera.GetViewMatrix();
|
|
GfMatrix4d projMatrix = camera.GetProjectionMatrix();
|
|
|
|
double detView = viewMatrix.GetDeterminant();
|
|
double detProj = projMatrix.GetDeterminant();
|
|
std::cout << " View matrix determinant: " << detView << std::endl;
|
|
std::cout << " Proj matrix determinant: " << detProj << std::endl;
|
|
|
|
TEST_ASSERT(std::abs(detView) > 0.001, "View matrix is non-degenerate");
|
|
TEST_ASSERT(std::abs(detProj) > 0.000001, "Projection matrix is non-degenerate");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Test_CameraAspectRatio() {
|
|
std::cout << "\n=== Test 4: Camera Aspect Ratio Update ===" << std::endl;
|
|
|
|
ViewportCamera camera;
|
|
float defaultAspect = camera.GetAspectRatio();
|
|
std::cout << " Default aspect: " << defaultAspect << std::endl;
|
|
|
|
camera.SetAspectRatio(800.0f / 600.0f);
|
|
float newAspect = camera.GetAspectRatio();
|
|
std::cout << " After update: " << newAspect << std::endl;
|
|
TEST_ASSERT(std::abs(newAspect - (800.0f / 600.0f)) < 0.01f, "Aspect ratio updated correctly");
|
|
|
|
GfMatrix4d proj = camera.GetProjectionMatrix();
|
|
double det = proj.GetDeterminant();
|
|
std::cout << " Projection determinant with new aspect: " << det << std::endl;
|
|
TEST_ASSERT(std::abs(det) > 0.000001, "Projection matrix valid after aspect update");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Test_ViewProjectionSanity() {
|
|
std::cout << "\n=== Test 5: View/Projection Matrix Sanity ===" << std::endl;
|
|
|
|
ViewportCamera camera;
|
|
camera.SetAspectRatio(800.0f / 600.0f);
|
|
|
|
GfRange3d bounds(GfVec3d(-25, -25, -25), GfVec3d(25, 25, 25));
|
|
camera.FrameBoundingBox(bounds);
|
|
|
|
GfMatrix4d view = camera.GetViewMatrix();
|
|
GfMatrix4d proj = camera.GetProjectionMatrix();
|
|
|
|
std::cout << " View matrix:" << std::endl;
|
|
for (int r = 0; r < 4; ++r) {
|
|
std::cout << " [" << view[r][0] << ", " << view[r][1] << ", "
|
|
<< view[r][2] << ", " << view[r][3] << "]" << std::endl;
|
|
}
|
|
|
|
std::cout << " Projection matrix:" << std::endl;
|
|
for (int r = 0; r < 4; ++r) {
|
|
std::cout << " [" << proj[r][0] << ", " << proj[r][1] << ", "
|
|
<< proj[r][2] << ", " << proj[r][3] << "]" << std::endl;
|
|
}
|
|
|
|
GfVec3d testPoint(0, 0, 0);
|
|
GfVec4d viewSpacePoint = MulMatrixVec4(view, GfVec4d(testPoint[0], testPoint[1], testPoint[2], 1.0));
|
|
std::cout << " Origin in view space: (" << viewSpacePoint[0] << ", "
|
|
<< viewSpacePoint[1] << ", " << viewSpacePoint[2] << ", "
|
|
<< viewSpacePoint[3] << ")" << std::endl;
|
|
|
|
TEST_ASSERT(viewSpacePoint[3] != 0.0, "Origin has valid w component in view space");
|
|
|
|
GfVec4d clipSpacePoint = MulMatrixVec4(proj, viewSpacePoint);
|
|
std::cout << " Origin in clip space: (" << clipSpacePoint[0] << ", "
|
|
<< clipSpacePoint[1] << ", " << clipSpacePoint[2] << ", "
|
|
<< clipSpacePoint[3] << ")" << std::endl;
|
|
|
|
if (clipSpacePoint[3] != 0.0) {
|
|
double ndcX = clipSpacePoint[0] / clipSpacePoint[3];
|
|
double ndcY = clipSpacePoint[1] / clipSpacePoint[3];
|
|
double ndcZ = clipSpacePoint[2] / clipSpacePoint[3];
|
|
std::cout << " Origin in NDC: (" << ndcX << ", " << ndcY << ", " << ndcZ << ")" << std::endl;
|
|
|
|
bool inFrustum = (ndcX >= -1.0 && ndcX <= 1.0 &&
|
|
ndcY >= -1.0 && ndcY <= 1.0 &&
|
|
ndcZ >= -1.0 && ndcZ <= 1.0);
|
|
if (inFrustum) {
|
|
std::cout << " [PASS] Scene origin is inside the view frustum" << std::endl;
|
|
g_testsPassed++;
|
|
} else {
|
|
std::cout << " [FAIL] Scene origin is OUTSIDE the view frustum - this explains nothing visible!" << std::endl;
|
|
std::cout << " This likely means the camera framing calculation is wrong." << std::endl;
|
|
g_testsFailed++;
|
|
}
|
|
} else {
|
|
std::cout << " [FAIL] Clip space w=0 - projection is broken" << std::endl;
|
|
g_testsFailed++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Test_RendererInitAndRender() {
|
|
std::cout << "\n=== Test 6: Renderer Init and Render ===" << std::endl;
|
|
|
|
if (!GL::InitExtensions()) {
|
|
TEST_SKIP("OpenGL extensions not available");
|
|
}
|
|
|
|
UsdStageRefPtr stage = CreateTestStageWithCube();
|
|
TEST_ASSERT(stage != nullptr, "Test stage created");
|
|
|
|
UsdSceneRenderer renderer;
|
|
renderer.SetStage(stage);
|
|
|
|
GfRange3d bounds = renderer.ComputeStageBounds();
|
|
TEST_ASSERT(!bounds.IsEmpty(), "Stage bounds not empty");
|
|
|
|
ViewportCamera camera;
|
|
camera.SetStage(stage);
|
|
camera.FrameBoundingBox(bounds);
|
|
camera.SetAspectRatio(800.0f / 600.0f);
|
|
|
|
renderer.SetCameraState(camera.GetViewMatrix(), camera.GetProjectionMatrix());
|
|
|
|
int width = 800, height = 600;
|
|
renderer.Render(width, height);
|
|
|
|
uint32_t texID = renderer.GetColorTextureID();
|
|
std::cout << " Color texture ID: " << texID << std::endl;
|
|
TEST_ASSERT(texID != 0, "Renderer produced a valid texture ID");
|
|
|
|
GfVec3f bgColor = renderer.GetBackgroundColor();
|
|
std::cout << " Background color: (" << bgColor[0] << ", " << bgColor[1] << ", " << bgColor[2] << ")" << std::endl;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Test_RendererDrawTargetValid() {
|
|
std::cout << "\n=== Test 7: Renderer DrawTarget Validity ===" << std::endl;
|
|
|
|
if (!GL::InitExtensions()) {
|
|
TEST_SKIP("OpenGL extensions not available");
|
|
}
|
|
|
|
UsdStageRefPtr stage = CreateTestStageWithCube();
|
|
TEST_ASSERT(stage != nullptr, "Test stage created");
|
|
|
|
UsdSceneRenderer renderer;
|
|
renderer.SetStage(stage);
|
|
|
|
ViewportCamera camera;
|
|
camera.SetStage(stage);
|
|
GfRange3d bounds = renderer.ComputeStageBounds();
|
|
if (!bounds.IsEmpty()) {
|
|
camera.FrameBoundingBox(bounds);
|
|
}
|
|
camera.SetAspectRatio(400.0f / 300.0f);
|
|
renderer.SetCameraState(camera.GetViewMatrix(), camera.GetProjectionMatrix());
|
|
|
|
renderer.Render(400, 300);
|
|
|
|
auto drawTarget = renderer.GetDrawTargetForTest();
|
|
TEST_ASSERT(drawTarget != nullptr, "DrawTarget is not null after render");
|
|
|
|
if (drawTarget) {
|
|
GLuint fboId = drawTarget->GetFramebufferId();
|
|
auto colorAttach = drawTarget->GetAttachment("color");
|
|
auto depthAttach = drawTarget->GetAttachment("depth");
|
|
|
|
std::cout << " FBO ID: " << fboId << std::endl;
|
|
std::cout << " Color attachment: " << (colorAttach ? "present" : "MISSING") << std::endl;
|
|
std::cout << " Depth attachment: " << (depthAttach ? "present" : "MISSING") << std::endl;
|
|
|
|
TEST_ASSERT(fboId != 0, "FBO ID is non-zero");
|
|
TEST_ASSERT(colorAttach != nullptr, "Color attachment exists");
|
|
TEST_ASSERT(depthAttach != nullptr, "Depth attachment exists");
|
|
|
|
if (colorAttach) {
|
|
GLuint texId = colorAttach->GetGlTextureName();
|
|
std::cout << " Color texture ID: " << texId << std::endl;
|
|
TEST_ASSERT(texId != 0, "Color attachment has valid GL texture");
|
|
|
|
GfVec2i texSize = drawTarget->GetSize();
|
|
std::cout << " DrawTarget size: " << texSize[0] << "x" << texSize[1] << std::endl;
|
|
TEST_ASSERT(texSize[0] > 0 && texSize[1] > 0, "DrawTarget has non-zero size");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Test_MultipleRenderFrames() {
|
|
std::cout << "\n=== Test 8: Multiple Render Frames (Stability) ===" << std::endl;
|
|
|
|
if (!GL::InitExtensions()) {
|
|
TEST_SKIP("OpenGL extensions not available");
|
|
}
|
|
|
|
UsdStageRefPtr stage = CreateTestStageWithCube();
|
|
TEST_ASSERT(stage != nullptr, "Stage created for stability test");
|
|
|
|
UsdSceneRenderer renderer;
|
|
renderer.SetStage(stage);
|
|
|
|
ViewportCamera camera;
|
|
camera.SetStage(stage);
|
|
GfRange3d bounds = renderer.ComputeStageBounds();
|
|
if (!bounds.IsEmpty()) {
|
|
camera.FrameBoundingBox(bounds);
|
|
}
|
|
camera.SetAspectRatio(400.0f / 300.0f);
|
|
renderer.SetCameraState(camera.GetViewMatrix(), camera.GetProjectionMatrix());
|
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
renderer.Render(400, 300);
|
|
uint32_t texID = renderer.GetColorTextureID();
|
|
if (texID == 0) {
|
|
std::cout << "[FAIL] Render frame " << i << " produced invalid texture" << std::endl;
|
|
g_testsFailed++;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::cout << " [PASS] 5 consecutive renders completed successfully" << std::endl;
|
|
g_testsPassed++;
|
|
return true;
|
|
}
|
|
|
|
bool Test_CameraOrbitAndRender() {
|
|
std::cout << "\n=== Test 9: Camera Orbit + Render ===" << std::endl;
|
|
|
|
if (!GL::InitExtensions()) {
|
|
TEST_SKIP("OpenGL extensions not available");
|
|
}
|
|
|
|
UsdStageRefPtr stage = CreateTestStageWithCube();
|
|
TEST_ASSERT(stage != nullptr, "Stage created for orbit test");
|
|
|
|
UsdSceneRenderer renderer;
|
|
renderer.SetStage(stage);
|
|
|
|
ViewportCamera camera;
|
|
camera.SetStage(stage);
|
|
GfRange3d bounds = renderer.ComputeStageBounds();
|
|
if (!bounds.IsEmpty()) {
|
|
camera.FrameBoundingBox(bounds);
|
|
}
|
|
camera.SetAspectRatio(400.0f / 300.0f);
|
|
|
|
for (int angle = 0; angle < 4; ++angle) {
|
|
camera.Orbit(45.0f, 0.0f);
|
|
renderer.SetCameraState(camera.GetViewMatrix(), camera.GetProjectionMatrix());
|
|
renderer.Render(400, 300);
|
|
|
|
uint32_t texID = renderer.GetColorTextureID();
|
|
if (texID == 0) {
|
|
std::cout << "[FAIL] Render after orbit " << angle << " failed" << std::endl;
|
|
g_testsFailed++;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::cout << " [PASS] Renders after camera orbit completed" << std::endl;
|
|
g_testsPassed++;
|
|
return true;
|
|
}
|
|
|
|
bool Test_EmptyStageRender() {
|
|
std::cout << "\n=== Test 10: Empty Stage Render ===" << std::endl;
|
|
|
|
if (!GL::InitExtensions()) {
|
|
TEST_SKIP("OpenGL extensions not available");
|
|
}
|
|
|
|
UsdStageRefPtr stage = UsdStage::CreateInMemory();
|
|
TEST_ASSERT(stage != nullptr, "Empty in-memory stage created");
|
|
|
|
UsdSceneRenderer renderer;
|
|
renderer.SetBackgroundColor(GfVec3f(0.1f, 0.1f, 0.15f));
|
|
renderer.SetStage(stage);
|
|
|
|
ViewportCamera camera;
|
|
camera.SetStage(stage);
|
|
camera.SetAspectRatio(400.0f / 300.0f);
|
|
renderer.SetCameraState(camera.GetViewMatrix(), camera.GetProjectionMatrix());
|
|
|
|
renderer.Render(400, 300);
|
|
|
|
uint32_t texID = renderer.GetColorTextureID();
|
|
TEST_ASSERT(texID != 0, "Empty stage still produces a valid texture");
|
|
|
|
auto drawTarget = renderer.GetDrawTargetForTest();
|
|
if (drawTarget) {
|
|
std::cout << " Empty stage FBO: " << drawTarget->GetFramebufferId() << std::endl;
|
|
std::cout << " [PASS] Empty stage render produces valid FBO" << std::endl;
|
|
g_testsPassed++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Test_CameraFrameSceneIntegration() {
|
|
std::cout << "\n=== Test 11: Camera + FrameScene Integration ===" << std::endl;
|
|
|
|
UsdStageRefPtr stage = CreateTestStageWithCube();
|
|
TEST_ASSERT(stage != nullptr, "Stage created for integration test");
|
|
|
|
ViewportCamera camera;
|
|
camera.SetStage(stage);
|
|
|
|
UsdSceneRenderer renderer;
|
|
renderer.SetStage(stage);
|
|
|
|
GfRange3d bounds = renderer.ComputeStageBounds();
|
|
TEST_ASSERT(!bounds.IsEmpty(), "Stage has bounds for framing");
|
|
|
|
camera.FrameBoundingBox(bounds);
|
|
|
|
float nearClip = camera.GetNearClip();
|
|
float farClip = camera.GetFarClip();
|
|
float distance = 0.0f;
|
|
|
|
GfVec3d eye = camera.GetEye();
|
|
GfVec3d focal = camera.GetFocalPoint();
|
|
GfVec3d diff = eye - focal;
|
|
distance = static_cast<float>(diff.GetLength());
|
|
|
|
std::cout << " Camera distance after FrameBoundingBox: " << distance << std::endl;
|
|
std::cout << " Near clip: " << nearClip << ", Far clip: " << farClip << std::endl;
|
|
std::cout << " Eye: (" << eye[0] << ", " << eye[1] << ", " << eye[2] << ")" << std::endl;
|
|
|
|
TEST_ASSERT(distance > 0.0f, "Camera has non-zero distance from focal point");
|
|
TEST_ASSERT(nearClip > 0.0f, "Near clip is positive");
|
|
TEST_ASSERT(farClip > nearClip, "Far clip is beyond near clip");
|
|
TEST_ASSERT(nearClip < distance, "Near clip is less than camera distance (scene is in front of camera)");
|
|
|
|
GfMatrix4d view = camera.GetViewMatrix();
|
|
GfMatrix4d proj = camera.GetProjectionMatrix();
|
|
|
|
GfVec4d originView = MulMatrixVec4(view, GfVec4d(0, 0, 0, 1));
|
|
if (originView[3] != 0.0) {
|
|
GfVec4d originClip = MulMatrixVec4(proj, originView);
|
|
if (originClip[3] != 0.0) {
|
|
double ndcZ = originClip[2] / originClip[3];
|
|
std::cout << " Scene origin NDC Z: " << ndcZ << std::endl;
|
|
bool zInRange = (ndcZ >= -1.0 && ndcZ <= 1.0);
|
|
TEST_ASSERT(zInRange, "Scene origin is within NDC Z range [-1,1] - visible in depth");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int main() {
|
|
std::cout << "============================================" << std::endl;
|
|
std::cout << " Viewport Display Logic Test Suite" << std::endl;
|
|
std::cout << "============================================" << std::endl;
|
|
|
|
char exePath[MAX_PATH];
|
|
GetModuleFileNameA(nullptr, exePath, MAX_PATH);
|
|
std::string exeDir(exePath);
|
|
size_t lastSlash = exeDir.find_last_of("\\/");
|
|
if (lastSlash != std::string::npos) {
|
|
exeDir = exeDir.substr(0, lastSlash);
|
|
}
|
|
std::string pluginPath = exeDir + "\\usd";
|
|
SetEnvironmentVariableA("PXR_PLUGINPATH_NAME", pluginPath.c_str());
|
|
|
|
Logger::Instance().SetLogLevel(LogLevel::Warning);
|
|
|
|
if (!CreateGLContext()) {
|
|
std::cerr << "FATAL: Cannot create OpenGL context. Tests require a valid GL context." << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
GL::InitExtensions();
|
|
|
|
Test_StageCreation();
|
|
Test_StageBounds();
|
|
Test_CameraSetup();
|
|
Test_CameraAspectRatio();
|
|
Test_ViewProjectionSanity();
|
|
Test_CameraFrameSceneIntegration();
|
|
Test_RendererInitAndRender();
|
|
Test_RendererDrawTargetValid();
|
|
Test_MultipleRenderFrames();
|
|
Test_CameraOrbitAndRender();
|
|
Test_EmptyStageRender();
|
|
|
|
std::cout << "\n============================================" << std::endl;
|
|
std::cout << " Results: " << g_testsPassed << " passed, "
|
|
<< g_testsFailed << " failed, "
|
|
<< g_testsSkipped << " skipped" << std::endl;
|
|
std::cout << "============================================" << std::endl;
|
|
|
|
if (g_testsFailed > 0) {
|
|
std::cout << "\n DIAGNOSIS: If Test 5 (View/Projection Sanity) shows the origin" << std::endl;
|
|
std::cout << " is outside the frustum, the camera framing logic in" << std::endl;
|
|
std::cout << " ViewportCamera::FrameBoundingBox() is broken. Check:" << std::endl;
|
|
std::cout << " 1. The view matrix construction in ComputeFreeViewMatrix()" << std::endl;
|
|
std::cout << " 2. The projection matrix in ComputeFreeProjectionMatrix()" << std::endl;
|
|
std::cout << " 3. Whether GfMatrix4d row/column conventions match USD's expectations" << std::endl;
|
|
}
|
|
|
|
DestroyGLContext();
|
|
|
|
return g_testsFailed > 0 ? 1 : 0;
|
|
}
|