diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 47bbb27d..88353706 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -414,6 +414,18 @@ target_link_libraries(test_open_formats PRIVATE catch2_main) add_test(NAME open_formats COMMAND test_open_formats) register_test_target(test_open_formats) +# ── test_camera ────────────────────────────────────────────── +add_executable(test_camera + test_camera.cpp + ${CMAKE_SOURCE_DIR}/src/rendering/camera.cpp + ${CMAKE_SOURCE_DIR}/src/core/logger.cpp +) +target_include_directories(test_camera PRIVATE ${TEST_INCLUDE_DIRS}) +target_include_directories(test_camera SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) +target_link_libraries(test_camera PRIVATE catch2_main) +add_test(NAME camera COMMAND test_camera) +register_test_target(test_camera) + # ── test_editor_units (SQL escape, quest validation, …) ───── add_executable(test_editor_units test_editor_units.cpp diff --git a/tests/test_camera.cpp b/tests/test_camera.cpp new file mode 100644 index 00000000..8e2656c6 --- /dev/null +++ b/tests/test_camera.cpp @@ -0,0 +1,93 @@ +// Tests for rendering::Camera setter guards and degenerate-pose math. +#include +#include "rendering/camera.hpp" +#include +#include + +using namespace wowee::rendering; + +TEST_CASE("Camera::setPosition rejects NaN/inf", "[camera]") { + Camera cam; + glm::vec3 originalPos = cam.getPosition(); + + cam.setPosition(glm::vec3(std::numeric_limits::quiet_NaN(), 0, 0)); + REQUIRE(cam.getPosition() == originalPos); + + cam.setPosition(glm::vec3(std::numeric_limits::infinity(), 0, 0)); + REQUIRE(cam.getPosition() == originalPos); + + cam.setPosition(glm::vec3(10, 20, 30)); + REQUIRE(cam.getPosition() == glm::vec3(10, 20, 30)); +} + +TEST_CASE("Camera::setRotation rejects NaN", "[camera]") { + Camera cam; + cam.setRotation(45.0f, 30.0f); + cam.setRotation(std::numeric_limits::quiet_NaN(), 60.0f); + // Yaw stays at 45, pitch stays at 30 (set was rejected wholesale) + glm::vec3 fwdAfterBad = cam.getForward(); + cam.setRotation(45.0f, 30.0f); + REQUIRE(cam.getForward() == fwdAfterBad); +} + +TEST_CASE("Camera::setFov rejects NaN/zero/180+", "[camera]") { + Camera cam; + float originalFov = cam.getFovDegrees(); + + cam.setFov(std::numeric_limits::quiet_NaN()); + REQUIRE(cam.getFovDegrees() == originalFov); + + cam.setFov(0.0f); + REQUIRE(cam.getFovDegrees() == originalFov); + + cam.setFov(-30.0f); + REQUIRE(cam.getFovDegrees() == originalFov); + + cam.setFov(180.0f); + REQUIRE(cam.getFovDegrees() == originalFov); + + cam.setFov(90.0f); + REQUIRE(cam.getFovDegrees() == 90.0f); +} + +TEST_CASE("Camera::setAspectRatio rejects non-positive", "[camera]") { + Camera cam; + float originalAspect = cam.getAspectRatio(); + cam.setAspectRatio(0.0f); + REQUIRE(cam.getAspectRatio() == originalAspect); + cam.setAspectRatio(-1.0f); + REQUIRE(cam.getAspectRatio() == originalAspect); + cam.setAspectRatio(std::numeric_limits::quiet_NaN()); + REQUIRE(cam.getAspectRatio() == originalAspect); + cam.setAspectRatio(2.0f); + REQUIRE(cam.getAspectRatio() == 2.0f); +} + +TEST_CASE("Camera::getRight/getUp return finite at +/-89 pitch", "[camera]") { + Camera cam; + cam.setRotation(0.0f, 89.0f); + glm::vec3 r = cam.getRight(); + glm::vec3 u = cam.getUp(); + REQUIRE(std::isfinite(r.x)); + REQUIRE(std::isfinite(r.y)); + REQUIRE(std::isfinite(r.z)); + REQUIRE(std::isfinite(u.x)); + REQUIRE(std::isfinite(u.y)); + REQUIRE(std::isfinite(u.z)); +} + +TEST_CASE("Camera::getRight/getUp degrade safely at +/-90 pitch", "[camera]") { + Camera cam; + // Force the degenerate pose: forward exactly = world up + cam.setRotation(0.0f, 90.0f); + glm::vec3 r = cam.getRight(); + glm::vec3 u = cam.getUp(); + REQUIRE(std::isfinite(r.x)); + REQUIRE(std::isfinite(r.y)); + REQUIRE(std::isfinite(r.z)); + REQUIRE(std::isfinite(u.x)); + REQUIRE(std::isfinite(u.y)); + REQUIRE(std::isfinite(u.z)); + // Right should be the +X fallback (since cross would be zero). + REQUIRE(r == glm::vec3(1.0f, 0.0f, 0.0f)); +}