From c4b01b8a27f06b2ad6a6dbd3db49f0f0cd83dff1 Mon Sep 17 00:00:00 2001 From: kj16609 Date: Fri, 16 Feb 2024 16:19:38 -0500 Subject: [PATCH] More progress on getting frustums working --- CMakeLists.txt | 1 + cmake_modules/dependencies/catch2.cmake | 1 + cmake_modules/dependencies/gtest.cmake | 11 + dependencies/catch2 | 1 + src/engine/Camera.cpp | 79 +++--- src/engine/Camera.hpp | 31 ++- src/engine/EngineContext.cpp | 45 +++- src/engine/GameObject.cpp | 2 +- src/engine/GameObject.hpp | 15 +- src/engine/constants.hpp | 4 + src/engine/debug/drawers.cpp | 91 ++++--- src/engine/debug/drawers.hpp | 12 +- src/engine/primitives/Coordinate.hpp | 15 +- src/engine/primitives/Frustum.hpp | 23 +- src/engine/primitives/Plane.cpp | 16 ++ src/engine/primitives/Plane.hpp | 78 ++++-- src/engine/primitives/TransformComponent.hpp | 28 ++ src/engine/primitives/Vector.hpp | 17 +- tests/CMakeLists.txt | 15 +- tests/src/BoundingBoxTest.cpp | 28 +- tests/src/CameraTesting.cpp | 35 +++ tests/src/FrustumTesting.cpp | 256 +++++++++++++++++++ tests/src/RotationTests.cpp | 82 ++++++ tests/src/gtest_printers.hpp | 22 ++ 24 files changed, 765 insertions(+), 143 deletions(-) create mode 100644 cmake_modules/dependencies/catch2.cmake create mode 100644 cmake_modules/dependencies/gtest.cmake create mode 160000 dependencies/catch2 create mode 100644 src/engine/primitives/Plane.cpp create mode 100644 src/engine/primitives/TransformComponent.hpp create mode 100644 tests/src/CameraTesting.cpp create mode 100644 tests/src/FrustumTesting.cpp create mode 100644 tests/src/RotationTests.cpp create mode 100644 tests/src/gtest_printers.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5813a79..a924319 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ include(dependencies/glfw) include(dependencies/glm) include(dependencies/tracy) include(dependencies/vulkan) +include(dependencies/catch2) add_subdirectory(src) add_subdirectory(tests) diff --git a/cmake_modules/dependencies/catch2.cmake b/cmake_modules/dependencies/catch2.cmake new file mode 100644 index 0000000..cea8d3c --- /dev/null +++ b/cmake_modules/dependencies/catch2.cmake @@ -0,0 +1 @@ +add_subdirectory(${CMAKE_SOURCE_DIR}/dependencies/catch2) \ No newline at end of file diff --git a/cmake_modules/dependencies/gtest.cmake b/cmake_modules/dependencies/gtest.cmake new file mode 100644 index 0000000..bdb60af --- /dev/null +++ b/cmake_modules/dependencies/gtest.cmake @@ -0,0 +1,11 @@ + + +include(FetchContent) +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) \ No newline at end of file diff --git a/dependencies/catch2 b/dependencies/catch2 new file mode 160000 index 0000000..b817497 --- /dev/null +++ b/dependencies/catch2 @@ -0,0 +1 @@ +Subproject commit b817497528877b14d6102869a6df378d029b343b diff --git a/src/engine/Camera.cpp b/src/engine/Camera.cpp index 2e6af63..863cacc 100644 --- a/src/engine/Camera.cpp +++ b/src/engine/Camera.cpp @@ -4,6 +4,8 @@ #include "Camera.hpp" +#include "GameObject.hpp" + #define GLM_ENABLE_EXPERIMENTAL #include @@ -27,7 +29,7 @@ namespace fgl::engine ZoneScoped; projection_matrix = Matrix< MatrixType::CameraToScreen >( glm::perspectiveLH( fovy, aspect, near, far ) ); - base_frustum = createFrustum( *this, aspect, fovy, near, far ); + base_frustum = createFrustum( aspect, fovy, near, far ); } void Camera::setViewDirection( glm::vec3 position, const Vector direction, glm::vec3 up ) @@ -250,14 +252,6 @@ namespace fgl::engine { static auto current_rotation_order { RotationOrder::DEFAULT }; - ImGui::Begin( "CameraRotation" ); - ImGui::SliderInt( - "Rotation Order", - reinterpret_cast< int* >( ¤t_rotation_order ), - 0, - static_cast< int >( RotationOrder::END_OF_ENUM - 1 ) ); - ImGui::End(); - const glm::mat4 rotation_matrix { taitBryanMatrix( rotation, current_rotation_order ) }; const glm::vec3 forward { rotation_matrix * glm::vec4( constants::WORLD_FORWARD, 0.0f ) }; @@ -278,34 +272,52 @@ namespace fgl::engine throw std::runtime_error( "Unimplemented view mode" ); } - frustum = frustumTranslationMatrix() * base_frustum; + TransformComponent transform { WorldCoordinate( position ), glm::vec3( 1.0f, 1.0f, 1.0f ), rotation }; - return; + if ( update_frustums ) + { + if ( update_using_alt ) [[unlikely]] + { + frustum = frustum_alt_transform.mat() * base_frustum; + return; + } + else + { + frustum = transform.mat() * base_frustum; + return; + } + } } Frustum< CoordinateSpace::Model > - createFrustum( const Camera& camera, const float aspect, const float fov_y, const float near, const float far ) + createFrustum( const float aspect, const float fov_y, const float near, const float far ) { - Plane< CoordinateSpace::Model > near_plane { camera.getForward(), near }; - Plane< CoordinateSpace::Model > far_plane { camera.getBackward(), far }; + const Plane< CoordinateSpace::Model > near_plane { constants::WORLD_FORWARD, near }; + const Plane< CoordinateSpace::Model > far_plane { constants::WORLD_BACKWARD, -far }; - const float half_width { near * glm::tan( fov_y / 2.0f ) }; // halfHSide - const float half_height { half_width / aspect }; //halfVSide + const float half_height { far * glm::tan( fov_y / 2.0f ) }; + const float half_width { half_height * aspect }; - constexpr glm::vec3 ZERO { 0.0f, 0.0f, 0.0f }; + const ModelCoordinate far_forward { constants::WORLD_FORWARD * far }; + const ModelCoordinate right_half { constants::WORLD_RIGHT * half_width }; - const auto far_forward { camera.getForward() * far }; + const Vector right_forward { far_forward + right_half }; + const Vector left_forward { far_forward - right_half }; - //top_dir is the direction pointing at the highest point on the far plane - const auto far_up { camera.getUp() * half_height }; - const glm::vec3 top_dir { glm::normalize( far_up + far_forward ) }; + const Plane< CoordinateSpace::Model > right_plane { glm::cross( right_forward, constants::WORLD_DOWN ), 0.0f }; + const Plane< CoordinateSpace::Model > left_plane { glm::cross( left_forward, constants::WORLD_UP ), 0.0f }; - Plane< CoordinateSpace::Model > top_plane { glm::cross( top_dir, camera.getUp() ), 0.0f }; - Plane< CoordinateSpace::Model > bottom_plane { glm::cross( top_dir, camera.getDown() ), 0.0f }; + const ModelCoordinate top_half { constants::WORLD_UP * half_height }; - const glm::vec3 right_dir { glm::normalize( camera.getRight() * half_width + far_forward ) }; - Plane< CoordinateSpace::Model > right_plane { glm::cross( right_dir, camera.getRight() ), 0.0f }; - Plane< CoordinateSpace::Model > left_plane { glm::cross( right_dir, camera.getLeft() ), 0.0f }; + const Vector top_forward { far_forward + top_half }; + const Vector bottom_forward { far_forward - top_half }; + + const Plane< CoordinateSpace::Model > top_plane { glm::cross( top_forward, constants::WORLD_RIGHT ), 0.0f }; + + const Plane< CoordinateSpace::Model > bottom_plane { glm::cross( bottom_forward, constants::WORLD_LEFT ), + 0.0f }; + + std::cout << bottom_plane.direction() << std::endl; return { near_plane, far_plane, top_plane, bottom_plane, right_plane, left_plane }; } @@ -314,14 +326,17 @@ namespace fgl::engine { glm::mat4 translation { 1.0f }; - translation[ 3 ] = glm::vec4( getPosition(), 1.0f ); - - //Apply rotation - translation[ 0 ] = glm::vec4( getRight(), 0.0f ); - translation[ 1 ] = glm::vec4( getUp(), 0.0f ); - translation[ 2 ] = glm::vec4( getForward(), 0.0f ); + translation = glm::translate( glm::mat4( 1.0f ), getPosition() ); return Matrix< MatrixType::ModelToWorld >( translation ); } + WorldCoordinate Camera::getFrustumPosition() const + { + if ( update_using_alt ) [[unlikely]] + return frustum_alt_transform.translation; + else + return getPosition(); + } + } // namespace fgl::engine \ No newline at end of file diff --git a/src/engine/Camera.hpp b/src/engine/Camera.hpp index 6f0681d..bc5a9a5 100644 --- a/src/engine/Camera.hpp +++ b/src/engine/Camera.hpp @@ -4,22 +4,31 @@ #pragma once +#define GLM_ENABLE_EXPERIMENTAL #include +#include #include "constants.hpp" #include "engine/primitives/Coordinate.hpp" #include "engine/primitives/Frustum.hpp" #include "engine/primitives/Matrix.hpp" +#include "engine/primitives/TransformComponent.hpp" namespace fgl::engine { class Camera; Frustum< CoordinateSpace::Model > - createFrustum( const Camera& camera, const float aspect, const float fovy, const float near, const float far ); + createFrustum( const float aspect, const float fovy, const float near, const float far ); class Camera { +#ifdef EXPOSE_CAMERA_INTERNAL + + public: + +#endif + Matrix< MatrixType::CameraToScreen > projection_matrix { 1.0f }; Matrix< MatrixType::WorldToCamera > view_matrix { 1.0f }; @@ -29,13 +38,25 @@ namespace fgl::engine Frustum< CoordinateSpace::Model > base_frustum {}; Frustum< CoordinateSpace::World > frustum {}; - friend Frustum< CoordinateSpace::Model > createFrustum( - const Camera& camera, const float aspect, const float fovy, const float near, const float far ); - const Matrix< MatrixType::ModelToWorld > frustumTranslationMatrix() const; public: + inline static TransformComponent frustum_alt_transform { WorldCoordinate( constants::WORLD_CENTER ), + glm::vec3( 1.0f ), + Vector( 0.0f, 0.0f, 0.0f ) }; + + inline static bool update_frustums { true }; + inline static bool update_using_alt { false }; + + Camera() + { + setPerspectiveProjection( 90.0f, 16.0f / 9.0f, constants::NEAR_PLANE, constants::FAR_PLANE ); + setViewYXZ( constants::CENTER, Vector( 0.0f, 0.0f, 0.0f ) ); + } + + WorldCoordinate getFrustumPosition() const; + const Frustum< CoordinateSpace::Model >& getBaseFrustum() const { return base_frustum; } //! Returns the frustum of the camera in world space @@ -64,7 +85,7 @@ namespace fgl::engine const Vector getRight() const { return Vector( - glm::normalize( glm::vec3( view_matrix[ 0 ][ 0 ], view_matrix[ 1 ][ 0 ], view_matrix[ 2 ][ 0 ] ) ) ); + glm::normalize( glm::vec3( -view_matrix[ 0 ][ 0 ], view_matrix[ 1 ][ 0 ], view_matrix[ 2 ][ 0 ] ) ) ); } const Vector getForward() const diff --git a/src/engine/EngineContext.cpp b/src/engine/EngineContext.cpp index d7fdcec..416b9d8 100644 --- a/src/engine/EngineContext.cpp +++ b/src/engine/EngineContext.cpp @@ -105,7 +105,7 @@ namespace fgl::engine //camera.setOrthographicProjection( -aspect, aspect, -1, 1, -1, 1 ); const float aspect { m_renderer.getAspectRatio() }; - camera.setPerspectiveProjection( glm::radians( 90.0f ), aspect, 0.1f, 100.f ); + camera.setPerspectiveProjection( glm::radians( 90.0f ), aspect, constants::NEAR_PLANE, constants::FAR_PLANE ); const auto old_aspect_ratio { m_renderer.getAspectRatio() }; @@ -142,12 +142,22 @@ namespace fgl::engine if ( old_aspect_ratio != m_renderer.getAspectRatio() ) { - camera.setPerspectiveProjection( glm::radians( 90.0f ), m_renderer.getAspectRatio(), 0.1f, 100.f ); + camera.setPerspectiveProjection( + glm::radians( 90.0f ), m_renderer.getAspectRatio(), constants::NEAR_PLANE, constants::FAR_PLANE ); } camera_controller.moveInPlaneXZ( m_window.window(), delta_time, viewer ); camera.setViewYXZ( viewer.transform.translation, viewer.transform.rotation ); + { + constexpr WorldCoordinate center { 0.0f, 0.0f, 0.0f }; + //debug::world::drawVector( center, camera.getForward(), camera, { 0.0f, 0.0f, 0.0f } ); + + { + debug::world::drawFrustum( camera ); + } + } + if ( auto command_buffer = m_renderer.beginFrame(); command_buffer ) { ZoneScopedN( "Render" ); @@ -192,6 +202,20 @@ namespace fgl::engine ImGui::Text( "%.3f ms", 1000.0f / ImGui::GetIO().Framerate ); ImGui::Text( "Average rolling frametime: %.3f ms", rolling_ms_average.average() ); + auto inputVec3 = []( const std::string label, glm::vec3& vec ) + { + ImGui::PushID( label.c_str() ); + ImGui::PushItemWidth( 80 ); + ImGui::Text( label.c_str() ); + ImGui::DragFloat( "X", &vec.x, 0.1f ); + ImGui::SameLine(); + ImGui::DragFloat( "Y", &vec.y, 0.1f ); + ImGui::SameLine(); + ImGui::DragFloat( "Z", &vec.z, 0.1f ); + ImGui::PopItemWidth(); + ImGui::PopID(); + }; + if ( ImGui::CollapsingHeader( "Camera" ) ) { ImGui::PushItemWidth( 80 ); @@ -211,6 +235,19 @@ namespace fgl::engine ImGui::SameLine(); ImGui::DragFloat( "Rot Z", &viewer.transform.rotation.z, 0.1f, 0.0f, glm::two_pi< float >() ); ImGui::PopItemWidth(); + + ImGui::Separator(); + ImGui::Checkbox( "Update Frustum", &camera.update_frustums ); + + ImGui::Separator(); + ImGui::Checkbox( "Use Alt Frustum matrix", &camera.update_using_alt ); + if ( ImGui::CollapsingHeader( "Frustum matrix", &camera.update_using_alt ) ) + { + ImGui::PushID( "FrustumMatrix" ); + inputVec3( "Translation", Camera::frustum_alt_transform.translation ); + inputVec3( "Rotation", Camera::frustum_alt_transform.rotation ); + ImGui::PopID(); + } } if ( ImGui::CollapsingHeader( "View Frustum" ) ) @@ -228,7 +265,9 @@ namespace fgl::engine ImGui::SameLine( 120.0f ); printVec3( plane.direction() ); ImGui::SameLine(); - ImGui::Text( "Distance: %.6f", plane.distance() ); + ImGui::Text( "Distance: %.3f", plane.distance() ); + const auto pos { plane.getPosition() }; + ImGui::Text( "Center: %.2f %2.f %2.f", pos.x, pos.y, pos.z ); }; printPlane( frustum.near, "Near" ); diff --git a/src/engine/GameObject.cpp b/src/engine/GameObject.cpp index 8c9558f..785d8e7 100644 --- a/src/engine/GameObject.cpp +++ b/src/engine/GameObject.cpp @@ -19,7 +19,7 @@ namespace fgl::engine { scale.x * ( c1 * c3 + s1 * s2 * s3 ), scale.x * ( c2 * s3 ), scale.x * ( c1 * s2 * s3 - c3 * s1 ), 0.0f }, { scale.y * ( c3 * s1 * s2 - c1 * s3 ), scale.y * ( c2 * c3 ), scale.y * ( c1 * c3 * s2 + s1 * s3 ), 0.0f }, { scale.z * ( c2 * s1 ), scale.z * ( -s2 ), scale.z * ( c1 * c2 ), 0.0f }, - { translation.x, translation.y, translation.z, 1.0f } + { -translation.x, translation.y, -translation.z, 1.0f } }; } diff --git a/src/engine/GameObject.hpp b/src/engine/GameObject.hpp index daa360b..93180ad 100644 --- a/src/engine/GameObject.hpp +++ b/src/engine/GameObject.hpp @@ -10,6 +10,8 @@ #include #include "constants.hpp" +#include "engine/primitives/Matrix.hpp" +#include "engine/primitives/TransformComponent.hpp" #include "engine/primitives/Vector.hpp" namespace fgl::engine @@ -17,19 +19,6 @@ namespace fgl::engine class Model; - //TransformComponent is always in world space - struct TransformComponent - { - WorldCoordinate translation { constants::DEFAULT_VEC3 }; - glm::vec3 scale { 1.0f, 1.0f, 1.0f }; - Vector rotation { 0.0f, 0.0f, 0.0f }; - - //TODO: Figure this out and replace TransformComponent with a template of CType instead - glm::mat4 mat4() const; - - glm::mat3 normalMatrix() const; - }; - class GameObject { public: diff --git a/src/engine/constants.hpp b/src/engine/constants.hpp index 7c83b8c..f482d47 100644 --- a/src/engine/constants.hpp +++ b/src/engine/constants.hpp @@ -27,4 +27,8 @@ namespace fgl::engine::constants constexpr float DEFAULT_FLOAT { std::numeric_limits< float >::max() }; + constexpr float NEAR_PLANE { 0.1f }; + constexpr float FAR_PLANE { 5.0f }; + constexpr glm::vec3 CENTER { 0.0f, 0.0f, 0.0f }; + } // namespace fgl::engine::constants diff --git a/src/engine/debug/drawers.cpp b/src/engine/debug/drawers.cpp index a78af0d..a4f943d 100644 --- a/src/engine/debug/drawers.cpp +++ b/src/engine/debug/drawers.cpp @@ -23,6 +23,7 @@ namespace fgl::engine::debug const ImVec2 window_size { windowSize() }; world_point.z = -world_point.z; + world_point.x = -world_point.x; return Coordinate< CoordinateSpace::Screen >( glm::project( static_cast< glm::vec3 >( world_point ), @@ -101,7 +102,7 @@ namespace fgl::engine::debug if ( !inView( screen_point ) ) return; - drawPoint( point, camera, color ); + drawPoint( point, camera, "", color ); const std::string text { "World: (" + std::to_string( point.x ) + ", " + std::to_string( point.y ) + ", " + std::to_string( point.z ) + ")" }; @@ -120,7 +121,22 @@ namespace fgl::engine::debug drawBoolAlpha( point, camera, in_view, glm::vec2( 0.0f, 40.0f ) ); } - void drawPoint( const Coordinate< CoordinateSpace::World > point, const Camera& camera, const glm::vec3 color ) + void drawPointLabel( + const Coordinate< CoordinateSpace::World > point, const std::string label, const Camera& camera ) + { + ZoneScoped; + const glm::vec3 screen_point { toScreenSpace( point, camera ) }; + + if ( !inView( screen_point ) ) return; + + screen::drawText( glm::vec2( screen_point.x, screen_point.y ), label ); + } + + void drawPoint( + const Coordinate< CoordinateSpace::World > point, + const Camera& camera, + const std::string label, + const glm::vec3 color ) { ZoneScoped; const auto screen_point { toScreenSpace( point, camera ) }; @@ -128,6 +144,8 @@ namespace fgl::engine::debug ImGui::GetForegroundDrawList() ->AddCircleFilled( glmToImgui( screen_point ), 5.0f, ImColor( color.x, color.y, color.z ) ); + + drawPointLabel( point, label, camera ); } void drawBoolAlpha( @@ -146,12 +164,15 @@ namespace fgl::engine::debug void drawVector( const Coordinate< CoordinateSpace::World > point, - const Vector vector, + Vector vector, const Camera& camera, + const std::string label, const glm::vec3 color ) { - drawLine( point, point + vector.norm(), camera, color ); - drawPoint( point + vector.norm(), camera, color ); + drawLine( point, point + glm::normalize( vector ), camera, color ); + drawPoint( point + glm::normalize( vector ), camera, label, color ); + drawPointLabel( point, label, camera ); + drawPointText( point + glm::normalize( vector ), camera ); //Draw ending lines for the vector (two perpendicular lines) const glm::vec3 perpendicular_vector { glm::normalize( glm::cross( vector, glm::vec3( 0.0f, 1.0f, 0.0f ) ) ) @@ -160,26 +181,49 @@ namespace fgl::engine::debug / 4.0f }; drawLine( - point + vector.norm() + perpendicular_vector, - point + vector.norm() - perpendicular_vector, + point + glm::normalize( vector ) + perpendicular_vector, + point + glm::normalize( vector ) - perpendicular_vector, camera, color ); drawLine( - point + vector.norm() + perpendicular_vector2, - point + vector.norm() - perpendicular_vector2, + point + glm::normalize( vector ) + perpendicular_vector2, + point + glm::normalize( vector ) - perpendicular_vector2, camera, color ); } - void drawFrustum( const Frustum< CoordinateSpace::World >& frustum, const Camera& camera ) + void drawFrustum( + const Frustum< CoordinateSpace::World >& frustum, const Camera& camera, const WorldCoordinate point ) { - drawPlane( frustum.near, camera ); - drawPlane( frustum.far, camera ); - drawPlane( frustum.top, camera ); - drawPlane( frustum.bottom, camera ); - drawPlane( frustum.right, camera ); - drawPlane( frustum.left, camera ); + drawPlane( frustum.near, point, camera, "near" ); + drawPlane( frustum.far, point, camera, "far" ); + drawPlane( frustum.top, point, camera, "top" ); + drawPlane( frustum.bottom, point, camera, "bottom" ); + drawPlane( frustum.right, point, camera, "right" ); + drawPlane( frustum.left, point, camera, "left" ); + } + + void drawFrustum( const Camera& camera ) + { + const Frustum frustum { camera.getFrustumBounds() }; + drawFrustum( frustum, camera, camera.getFrustumPosition() ); + } + + void drawPlane( + const Plane< CoordinateSpace::World >& plane, + const WorldCoordinate point, + const Camera& camera, + const std::string label, + const glm::vec3 color ) + { + ZoneScoped; + const auto normal { plane.direction() }; + + assert( point != constants::DEFAULT_VEC3 ); + + drawLine( point, point + normal, camera, color ); + drawPoint( point + normal, camera, label, color ); } void drawCameraInfo( const Camera& camera ) @@ -191,9 +235,7 @@ namespace fgl::engine::debug */ { - const Frustum frustum { camera.getFrustumBounds() }; - - drawFrustum( frustum, camera ); + drawFrustum( camera ); } /* @@ -211,17 +253,6 @@ namespace fgl::engine::debug */ } - void drawPlane( const Plane< CoordinateSpace::World >& plane, const Camera& camera, const glm::vec3 color ) - { - assert( plane.direction() != constants::DEFAULT_VEC3 ); - assert( plane.getPosition() != constants::DEFAULT_VEC3 ); - - const auto pos { plane.getPosition() }; - const auto dir { plane.direction() }; - - drawVector( pos, dir, camera, color ); - } - } // namespace world namespace screen diff --git a/src/engine/debug/drawers.hpp b/src/engine/debug/drawers.hpp index db16eae..abefbed 100644 --- a/src/engine/debug/drawers.hpp +++ b/src/engine/debug/drawers.hpp @@ -5,6 +5,7 @@ #pragma once #include "engine/primitives/Coordinate.hpp" +#include "engine/primitives/Frustum.hpp" #include "engine/primitives/Line.hpp" #include "engine/primitives/Plane.hpp" @@ -41,6 +42,9 @@ namespace fgl::engine::debug const Camera& camera, const glm::vec3 color = { 1.0f, 1.0f, 1.0f } ); + void drawPointLabel( + const Coordinate< CoordinateSpace::World > point, const std::string label, const Camera& camera ); + void drawLine( const Line< CoordinateSpace::World > line, const Camera& camera, @@ -59,19 +63,25 @@ namespace fgl::engine::debug void drawPoint( const Coordinate< CoordinateSpace::World > point, const Camera& camera, + const std::string label = "", const glm::vec3 color = { 1.0f, 1.0f, 1.0f } ); void drawVector( const Coordinate< CoordinateSpace::World > point, - const glm::vec3 vector, + Vector vector, const Camera& camera, + const std::string label = "", const glm::vec3 color = { 1.0f, 1.0f, 1.0f } ); + void drawFrustum( + const Frustum< CoordinateSpace::World >& frustum, const Camera& camera, const WorldCoordinate coordinate ); void drawFrustum( const Camera& camera ); void drawPlane( const Plane< CoordinateSpace::World >& plane, + const WorldCoordinate point, const Camera& camera, + const std::string label = "", const glm::vec3 color = { 1.0f, 1.0f, 1.0f } ); } // namespace world diff --git a/src/engine/primitives/Coordinate.hpp b/src/engine/primitives/Coordinate.hpp index d7b3a68..b855876 100644 --- a/src/engine/primitives/Coordinate.hpp +++ b/src/engine/primitives/Coordinate.hpp @@ -6,6 +6,8 @@ #include +#include + #include "engine/constants.hpp" namespace fgl::engine @@ -36,11 +38,11 @@ namespace fgl::engine Coordinate() : glm::vec3( constants::DEFAULT_VEC3 ) {} - explicit Coordinate( const glm::vec3 position ) : glm::vec3( position ) {} + constexpr explicit Coordinate( const glm::vec3 position ) : glm::vec3( position ) {} - explicit Coordinate( const float x, const float y, const float z ) : glm::vec3( x, y, z ) {} + constexpr explicit Coordinate( const float x, const float y, const float z ) : glm::vec3( x, y, z ) {} - explicit Coordinate( const float value ) : glm::vec3( value ) {} + constexpr explicit Coordinate( const float value ) : glm::vec3( value ) {} operator glm::vec4() const { return glm::vec4( x, y, z, 1.0f ); } @@ -70,6 +72,13 @@ namespace fgl::engine static_assert( sizeof( glm::vec3 ) == sizeof( ModelCoordinate ) ); + template < CoordinateSpace CType > + inline ::std::ostream& operator<<( ::std::ostream& os, const Coordinate< CType > coord ) + { + os << "(" << coord.x << ", " << coord.y << ", " << coord.z << ")"; + return os; + } + } // namespace fgl::engine namespace glm diff --git a/src/engine/primitives/Frustum.hpp b/src/engine/primitives/Frustum.hpp index dd7db25..9896619 100644 --- a/src/engine/primitives/Frustum.hpp +++ b/src/engine/primitives/Frustum.hpp @@ -11,6 +11,8 @@ namespace fgl::engine { + class Camera; + template < CoordinateSpace CType = CoordinateSpace::World > struct Frustum { @@ -55,7 +57,7 @@ namespace fgl::engine assert( near_plane.direction() != far_plane.direction() ); } - bool pointInside( const WorldCoordinate& coord ) const + bool pointInside( const WorldCoordinate coord ) const { static_assert( CType == CoordinateSpace::World, "pointInside can only be called on World coordinate Frustums" ); @@ -63,8 +65,27 @@ namespace fgl::engine return near.isForward( coord ) && far.isForward( coord ) && top.isForward( coord ) && bottom.isForward( coord ) && right.isForward( coord ) && left.isForward( coord ); } + + bool operator==( const Frustum< CType >& other ) const + { + return near == other.near && far == other.far && top == other.top && bottom == other.bottom + && right == other.right && left == other.left; + } }; + template < CoordinateSpace CType > + inline std::ostream& operator<<( std::ostream& os, const Frustum< CType >& frustum ) + { + os << "Frustum: " << std::endl; + os << "\tNear: " << frustum.near << std::endl; + os << "\tFar: " << frustum.far << std::endl; + os << "\tTop: " << frustum.top << std::endl; + os << "\tBottom: " << frustum.bottom << std::endl; + os << "\tRight: " << frustum.right << std::endl; + os << "\tLeft: " << frustum.left << std::endl; + return os; + } + template < CoordinateSpace CType, MatrixType MType > Frustum< EvolvedType< MType >() > operator*( const Matrix< MType >& matrix, const Frustum< CType >& frustum ) { diff --git a/src/engine/primitives/Plane.cpp b/src/engine/primitives/Plane.cpp new file mode 100644 index 0000000..8753da9 --- /dev/null +++ b/src/engine/primitives/Plane.cpp @@ -0,0 +1,16 @@ +// +// Created by kj16609 on 2/16/24. +// + +#include "Plane.hpp" + +namespace fgl::engine +{ + + template <> + double Plane< CoordinateSpace::World >::distanceFrom( const WorldCoordinate coord ) const + { + return glm::dot( m_direction, coord ) - m_distance; + } + +} // namespace fgl::engine diff --git a/src/engine/primitives/Plane.hpp b/src/engine/primitives/Plane.hpp index 9293d12..87a79a8 100644 --- a/src/engine/primitives/Plane.hpp +++ b/src/engine/primitives/Plane.hpp @@ -9,6 +9,9 @@ #include #include +#include +#include + #include "Coordinate.hpp" #include "Matrix.hpp" #include "Vector.hpp" @@ -18,7 +21,7 @@ namespace fgl::engine { template < CoordinateSpace CType = CoordinateSpace::World > - class Plane + class OriginDistancePlane { float m_distance { constants::DEFAULT_FLOAT }; Vector m_direction { constants::DEFAULT_VEC3 }; @@ -27,60 +30,87 @@ namespace fgl::engine bool valid() const { return m_distance != constants::DEFAULT_FLOAT && m_direction != constants::DEFAULT_VEC3; } - Plane( const glm::vec3 vector, const float distance ) : m_distance( distance ), m_direction( vector ) {} - - Plane( const glm::vec3 normal, const glm::vec3 point ) : - Plane( glm::normalize( normal ), glm::dot( glm::normalize( normal ), point ) ) + OriginDistancePlane( const glm::vec3 vector, const float distance ) : + m_distance( distance ), + m_direction( glm::normalize( vector ) ) {} - Plane( const Vector vector, const float distance ) : m_distance( distance ), m_direction( vector ) {} + OriginDistancePlane( const Vector vector, const float distance ) : + m_distance( distance ), + m_direction( glm::normalize( vector ) ) + {} - Plane() = default; + OriginDistancePlane() = default; //! Returns the closest point on the plane to the 0,0,0 origin Coordinate< CType > getPosition() const { assert( valid() ); - return Coordinate< CType >( 0.0f ) + ( m_direction * m_distance ); } + //! Returns the distance from a point to the plane. Negative if behind, positive if in front + double distanceFrom( const WorldCoordinate coord ) const; + bool isForward( const WorldCoordinate coord ) const { return distanceFrom( coord ) > 0.0; } bool isBehind( const WorldCoordinate coord ) const { return !isForward( coord ); } - //! Returns the distance from a point to the plane. Negative if behind, positive if in front - double distanceFrom( const WorldCoordinate coord ) const + //! Returns a normalized Vector + Vector direction() const { - if constexpr ( CType == CoordinateSpace::World ) - { - assert( valid() ); - return -( glm::dot( coord, m_direction ) - m_distance ); - } - else - throw std::runtime_error( "Plane must be in Plane to use distanceFrom" ); + assert( m_direction != constants::DEFAULT_VEC3 ); + return m_direction; } - Vector direction() const { return m_direction; } + float distance() const + { + assert( m_distance != constants::DEFAULT_FLOAT ); + return m_distance; + } - float distance() const { return m_distance; } + bool operator==( const OriginDistancePlane& other ) const + { + return m_distance == other.m_distance && m_direction == other.m_direction; + } }; - template < CoordinateSpace CType, MatrixType MType > - Plane< EvolvedType< MType >() > operator*( const Matrix< MType >& matrix, const Plane< CType >& plane ) + template < CoordinateSpace CType > + inline std::ostream& operator<<( std::ostream& os, const OriginDistancePlane< CType > plane ) { + os << "Plane: " << plane.direction() << " " << plane.distance(); + return os; + } + + inline std::ostream& operator<<( std::ostream& os, const Vector vector ) + { + os << "(" << vector.x << ", " << vector.y << ", " << vector.z << ")"; + return os; + } + + template < CoordinateSpace CType, MatrixType MType > + OriginDistancePlane< EvolvedType< MType >() > + operator*( const Matrix< MType >& matrix, const OriginDistancePlane< CType >& plane ) + { + assert( plane.valid() ); constexpr auto NewCT { EvolvedType< MType >() }; constexpr auto OldCT { CType }; + const Vector old_direction { plane.direction() }; + const Vector new_direction { glm::normalize( matrix * old_direction ) }; + const Coordinate< OldCT > old_center { plane.getPosition() }; //Translate old_center using matrix const Coordinate< NewCT > new_center { matrix * old_center }; - //Calculate distance between new_center and 0,0,0 - const float new_distance { glm::dot( plane.direction(), new_center ) }; + //Project new_center onto inverse of new_direction + const auto new_distance { static_cast< float >( glm::dot( new_direction, new_center ) ) }; - return { glm::normalize( new_center ), new_distance }; + return { new_direction, new_distance }; } + template < CoordinateSpace CType > + using Plane = OriginDistancePlane< CType >; + } // namespace fgl::engine diff --git a/src/engine/primitives/TransformComponent.hpp b/src/engine/primitives/TransformComponent.hpp new file mode 100644 index 0000000..e0e0828 --- /dev/null +++ b/src/engine/primitives/TransformComponent.hpp @@ -0,0 +1,28 @@ +// +// Created by kj16609 on 2/15/24. +// + +#pragma once + +#include "Vector.hpp" +#include "engine/primitives/Matrix.hpp" + +namespace fgl::engine +{ + + //TransformComponent is always in world space + struct TransformComponent + { + WorldCoordinate translation { constants::DEFAULT_VEC3 }; + glm::vec3 scale { 1.0f, 1.0f, 1.0f }; + Vector rotation { 0.0f, 0.0f, 0.0f }; + + //TODO: Figure this out and replace TransformComponent with a template of CType instead + glm::mat4 mat4() const; + + inline Matrix< MatrixType::ModelToWorld > mat() const { return Matrix< MatrixType::ModelToWorld >( mat4() ); } + + glm::mat3 normalMatrix() const; + }; + +} // namespace fgl::engine diff --git a/src/engine/primitives/Vector.hpp b/src/engine/primitives/Vector.hpp index 84f73a0..59dcb1a 100644 --- a/src/engine/primitives/Vector.hpp +++ b/src/engine/primitives/Vector.hpp @@ -20,16 +20,14 @@ namespace fgl::engine float& pitch { x }; float& yaw { z }; - explicit Vector( const float value ) : glm::vec3( value ) {} + constexpr explicit Vector( const float value ) : glm::vec3( value ) {} - explicit Vector( const glm::vec3 vec ) : glm::vec3( vec ) {} + constexpr explicit Vector( const glm::vec3 vec ) : glm::vec3( vec ) {} - explicit Vector( const float x, const float y, const float z ) : glm::vec3( x, y, z ) {} + constexpr explicit Vector( const float x, const float y, const float z ) : glm::vec3( x, y, z ) {} operator glm::vec4() const { return glm::vec4( static_cast< glm::vec3 >( *this ), 0.0f ); } - Vector norm() const { return Vector( glm::normalize( static_cast< glm::vec3 >( *this ) ) ); } - Vector operator*( const float scalar ) const { return Vector( static_cast< glm::vec3 >( *this ) * scalar ); } glm::vec3 right() const; @@ -51,14 +49,19 @@ namespace fgl::engine return Vector( -static_cast< glm::vec3 >( vec ) ); } + inline Vector operator*( const glm::mat4 matrix, const Vector vector ) + { + return Vector( matrix * glm::vec4( static_cast< glm::vec3 >( vector ), 0.0f ) ); + } + } // namespace fgl::engine namespace glm { - inline glm::vec3 normalize( fgl::engine::Vector vector ) + inline Vector normalize( fgl::engine::Vector vector ) { - return glm::normalize( static_cast< glm::vec3 >( vector ) ); + return Vector( glm::normalize( static_cast< glm::vec3 >( vector ) ) ); } inline glm::vec3 cross( const fgl::engine::Vector vec, const glm::vec3 other ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 59338c1..a745465 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,15 +1,12 @@ -add_custom_target(FGLTests) - enable_testing() file(GLOB_RECURSE FGL_TEST_SOURCES "*.cpp") -foreach (test_source ${FGL_TEST_SOURCES}) - get_filename_component(test_name ${test_source} NAME_WE) - add_executable(${test_name} ${test_source}) - target_link_libraries(${test_name} FGLEngine) - add_test(NAME ${test_name} COMMAND ${test_name}) - add_dependencies(FGLTests ${test_name}) -endforeach (test_source) +add_executable(FGLTests ${FGL_TEST_SOURCES}) +target_link_libraries(FGLTests FGLEngine Catch2::Catch2WithMain) + +include(CTest) +include(Catch) +catch_discover_tests(FGLTests) \ No newline at end of file diff --git a/tests/src/BoundingBoxTest.cpp b/tests/src/BoundingBoxTest.cpp index 0d35556..13174f5 100644 --- a/tests/src/BoundingBoxTest.cpp +++ b/tests/src/BoundingBoxTest.cpp @@ -6,7 +6,9 @@ using namespace fgl::engine; -int main() +#include + +TEST_CASE( "BoundingBox", "[boundingbox]" ) { { std::vector< Coordinate< CoordinateSpace::Model > > model_points {}; @@ -31,20 +33,20 @@ int main() //Check that the points are correct //The middle point should not change - assert( combined_box.middle == model_box.middle ); + REQUIRE( combined_box.middle == model_box.middle ); //The scale should be the max of the two boxes - assert( combined_box.scale == glm::vec3( 2.0f, 1.0f, 2.0f ) ); + REQUIRE( combined_box.scale == glm::vec3( 2.0f, 1.0f, 2.0f ) ); //Check that the points are correct const auto out_points { combined_box.points() }; const auto box2_points { model_box2.points() }; - assert( out_points.size() == box2_points.size() ); - assert( out_points.size() == 8 ); + REQUIRE( out_points.size() == box2_points.size() ); + REQUIRE( out_points.size() == 8 ); for ( std::uint32_t i = 0; i < out_points.size(); ++i ) { - assert( out_points[ i ] == box2_points[ i ] ); + REQUIRE( out_points[ i ] == box2_points[ i ] ); } } @@ -86,13 +88,11 @@ int main() } //Check that the lowest point is the one from the combined box too - assert( model_box.bottomLeftBack().x == lowest_point.x ); - assert( model_box.bottomLeftBack().y == lowest_point.y ); - assert( model_box.bottomLeftBack().z == lowest_point.z ); - assert( model_box.topRightForward().x == highest_point.x ); - assert( model_box.topRightForward().y == highest_point.y ); - assert( model_box.topRightForward().z == highest_point.z ); + REQUIRE( model_box.bottomLeftBack().x == lowest_point.x ); + REQUIRE( model_box.bottomLeftBack().y == lowest_point.y ); + REQUIRE( model_box.bottomLeftBack().z == lowest_point.z ); + REQUIRE( model_box.topRightForward().x == highest_point.x ); + REQUIRE( model_box.topRightForward().y == highest_point.y ); + REQUIRE( model_box.topRightForward().z == highest_point.z ); } - - return EXIT_SUCCESS; } diff --git a/tests/src/CameraTesting.cpp b/tests/src/CameraTesting.cpp new file mode 100644 index 0000000..ff534e7 --- /dev/null +++ b/tests/src/CameraTesting.cpp @@ -0,0 +1,35 @@ +// +// Created by kj16609 on 2/15/24. +// + +#include + +#define EXPOSE_CAMERA_INTERNAL +#include "engine/Camera.hpp" +#include "gtest_printers.hpp" + +using namespace fgl::engine; + +TEST_CASE( "Camera", "[camera]" ) +{ + Camera camera; + + SECTION( "Perspective set" ) + { + camera.setPerspectiveProjection( 90.0f, 1.0f, constants::NEAR_PLANE, constants::FAR_PLANE ); + } + + SECTION( "Orthographic set" ) + { + camera.setOrthographicProjection( 1.0f, 1.0f, 1.0f, 1.0f, constants::NEAR_PLANE, constants::FAR_PLANE ); + } + + const auto camera_up { camera.getUp() }; + REQUIRE( camera_up == constants::WORLD_UP ); + + const auto camera_forward { camera.getForward() }; + REQUIRE( camera_forward == constants::WORLD_FORWARD ); + + const auto camera_right { camera.getRight() }; + REQUIRE( camera_right == constants::WORLD_RIGHT ); +} \ No newline at end of file diff --git a/tests/src/FrustumTesting.cpp b/tests/src/FrustumTesting.cpp new file mode 100644 index 0000000..ba264c8 --- /dev/null +++ b/tests/src/FrustumTesting.cpp @@ -0,0 +1,256 @@ +// +// Created by kj16609 on 2/15/24. +// + +#include + +#include + +#define EXPOSE_CAMERA_INTERNAL +#include "engine/Camera.hpp" +#include "engine/primitives/Frustum.hpp" +#include "gtest_printers.hpp" + +using namespace fgl::engine; + +constexpr float ASPECT_RATIO { 16.0f / 9.0f }; + +TEST_CASE( "Frustum creation", "[frustum]" ) +{ + Camera camera; + camera.setPerspectiveProjection( 90.0f, ASPECT_RATIO, constants::NEAR_PLANE, constants::FAR_PLANE ); + + const auto base_frustum = camera.getBaseFrustum(); + + REQUIRE( camera.getRight() == constants::WORLD_RIGHT ); + + REQUIRE( base_frustum.near.direction() == constants::WORLD_FORWARD ); + REQUIRE( base_frustum.near.distance() == constants::NEAR_PLANE ); + + REQUIRE( base_frustum.far.direction() == constants::WORLD_BACKWARD ); + REQUIRE( base_frustum.far.distance() == -constants::FAR_PLANE ); +} + +TEST_CASE( "Frustum translations", "[frustum][translation]" ) +{ + Camera camera; + camera.setPerspectiveProjection( 90.0f, ASPECT_RATIO, constants::NEAR_PLANE, constants::FAR_PLANE ); + + const auto base_frustum = camera.getBaseFrustum(); + glm::mat4 mat { 1.0f }; + + REQUIRE( camera.getRight() == constants::WORLD_RIGHT ); + + { + //Translate backwards by 1 world unit + mat = glm::translate( mat, glm::vec3( constants::WORLD_BACKWARD ) ); + const auto translated_backwards { Matrix< MatrixType::ModelToWorld >( mat ) * base_frustum }; + + //Verify that during a translation the direction isn't changed + REQUIRE( translated_backwards.near.direction() == base_frustum.near.direction() ); + + REQUIRE( translated_backwards.near.direction() == constants::WORLD_FORWARD ); + REQUIRE( translated_backwards.near.distance() == constants::NEAR_PLANE - 1.0f ); + + REQUIRE( translated_backwards.far.direction() == constants::WORLD_BACKWARD ); + REQUIRE( translated_backwards.far.distance() == -( constants::FAR_PLANE - 1.0f ) ); + // The distance for the far plane should be negative. Due to the fact + // that it is poining toward the origin, So in order for the center to be positive + // the distance must also be negative + } + + { + //Translate forward by 1 world unit + mat = glm::translate( glm::mat4( 1.0f ), glm::vec3( constants::WORLD_FORWARD ) ); + const auto translated_forward { Matrix< MatrixType::ModelToWorld >( mat ) * base_frustum }; + + //Verify that during a translation the direction isn't changed + REQUIRE( translated_forward.near.direction() == base_frustum.near.direction() ); + + REQUIRE( translated_forward.near.direction() == constants::WORLD_FORWARD ); + REQUIRE( translated_forward.near.distance() == constants::NEAR_PLANE + 1.0f ); + + REQUIRE( translated_forward.far.direction() == constants::WORLD_BACKWARD ); + REQUIRE( translated_forward.far.distance() == -( constants::FAR_PLANE + 1.0f ) ); + } + + { + mat = glm::rotate( glm::mat4( 1.0f ), -glm::radians( 90.0f ), constants::WORLD_UP ); + + const auto rotated_right { Matrix< MatrixType::ModelToWorld >( mat ) * base_frustum }; + + REQUIRE( rotated_right.near.direction().x == constants::WORLD_RIGHT.x ); + REQUIRE( rotated_right.near.direction().y < 0.000001f ); // Precision issues. However it's VERY close to 0 + + REQUIRE( rotated_right.near.distance() == constants::NEAR_PLANE ); + + REQUIRE( rotated_right.far.direction().x == constants::WORLD_LEFT.x ); + REQUIRE( rotated_right.far.direction().y < 0.000001f ); // Precision issues. However it's VERY close to 0 + + REQUIRE( rotated_right.far.distance() == -constants::FAR_PLANE ); + } + + const Frustum< fgl::engine::CoordinateSpace::World > frustum { Matrix< MatrixType::ModelToWorld > { 1.0f } + * camera.getBaseFrustum() }; + + { + //A point extremely far up should be in front of the bottom plane but behind the top plane + const WorldCoordinate far_up { constants::WORLD_UP * 1000.0f }; + + REQUIRE( frustum.top.distanceFrom( far_up ) < -500.0f ); + REQUIRE( frustum.bottom.distanceFrom( far_up ) > 500.0f ); + + REQUIRE_FALSE( frustum.pointInside( far_up ) ); + } + + { + //A point extremely far below should be in front of the top plane but behind the bottom plane + const WorldCoordinate far_down { constants::WORLD_DOWN * 1000.0f }; + + REQUIRE( frustum.top.distanceFrom( far_down ) > 500.0f ); + REQUIRE( frustum.bottom.distanceFrom( far_down ) < -500.0f ); + + REQUIRE_FALSE( frustum.pointInside( far_down ) ); + } + + //The point FORWARD should be in the frustum + { + const WorldCoordinate point { constants::WORLD_FORWARD * 2.0f }; + + REQUIRE( frustum.near.distanceFrom( point ) > 0.0f ); + REQUIRE( frustum.far.distanceFrom( point ) > 0.0f ); + + REQUIRE( frustum.top.distanceFrom( point ) > 0.0f ); + REQUIRE( frustum.bottom.distanceFrom( point ) > 0.0f ); + + REQUIRE( frustum.right.distanceFrom( point ) > 0.0f ); + REQUIRE( frustum.left.distanceFrom( point ) > 0.0f ); + + REQUIRE( frustum.pointInside( point ) ); + } + + //A point FORWARD and 0.2 units to the left should be farther from the right plane + { + const WorldCoordinate point { ( constants::WORLD_FORWARD * 2.0f ) + constants::WORLD_LEFT * 0.2f }; + + REQUIRE( frustum.near.distanceFrom( point ) > 0.0f ); + REQUIRE( frustum.far.distanceFrom( point ) > 0.0f ); + + REQUIRE( frustum.top.distanceFrom( point ) > 0.0f ); + REQUIRE( frustum.bottom.distanceFrom( point ) > 0.0f ); + + REQUIRE( frustum.right.distanceFrom( point ) > 0.0f ); + REQUIRE( frustum.left.distanceFrom( point ) > 0.0f ); + + // right_dist > left_dist + REQUIRE( frustum.right.distanceFrom( point ) > frustum.left.distanceFrom( point ) ); + + REQUIRE( frustum.pointInside( point ) ); + } + + //A point FORWARD and 0.2 units to the right should be farther from the left plane + { + const WorldCoordinate point { ( constants::WORLD_FORWARD * 2.0f ) + constants::WORLD_RIGHT * 0.2f }; + + // left_dist > right_dist + REQUIRE( frustum.left.distanceFrom( point ) > frustum.right.distanceFrom( point ) ); + } + + //A point FORWARD and down 0.2 units should be closer to the bottom plane + { + const WorldCoordinate point { ( constants::WORLD_FORWARD * 2.0f ) + ( constants::WORLD_DOWN * 0.02f ) }; + + REQUIRE( frustum.top.distanceFrom( point ) > frustum.bottom.distanceFrom( point ) ); + } + + //Camera should be non-rotated at 0,0,0 so it should be identical + REQUIRE( Matrix< MatrixType::ModelToWorld > { 1.0f } * camera.getBaseFrustum() == camera.getFrustumBounds() ); + + //Testing rotation of the camera + { + std::cout << "Testing rotation (Pitch)" << std::endl; + Vector rotation { 0.0f, 0.0f, 0.0f }; + rotation.pitch -= glm::radians( 90.0f ); + + camera.setViewYXZ( constants::CENTER, rotation ); + + const auto rotated_frustum = camera.getFrustumBounds(); + + //The point DOWN should be in the frustum + const WorldCoordinate point { constants::WORLD_DOWN * 2.0f }; + + //NEAR should be looking down or approaching + //FAR should be looking up or away + + //TODO: Rewrite these equals to allow for some mild precission errors + //REQUIRE( rotated_frustum.near.direction(), constants::WORLD_DOWN ); + //REQUIRE( rotated_frustum.far.direction(), constants::WORLD_UP ); + + REQUIRE( rotated_frustum.near.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.far.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.top.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.bottom.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.right.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.left.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.pointInside( point ) ); + } + + { + std::cout << "Testing rotation (Yaw)" << std::endl; + Vector rotation { 0.0f, 0.0f, 0.0f }; + rotation.yaw -= glm::radians( 90.0f ); + + camera.setViewYXZ( constants::CENTER, rotation ); + + const auto rotated_frustum = camera.getFrustumBounds(); + + //The point RIGHT should be in the frustum + const WorldCoordinate point { constants::WORLD_RIGHT * 2.0f }; + + //NEAR should be looking right or approaching + //FAR should be looking left or away + + //Precision issues. So I can't write the tests yet + + REQUIRE( rotated_frustum.near.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.far.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.top.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.bottom.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.right.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.left.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.pointInside( point ) ); + + //LEFT should be behind, Thus out outside + REQUIRE_FALSE( rotated_frustum.pointInside( WorldCoordinate( constants::WORLD_LEFT ) ) ); + } + + { + std::cout << "Testing rotation (Roll)" << std::endl; + Vector rotation { 0.0f, 0.0f, 0.0f }; + rotation.roll -= glm::radians( 90.0f ); + + camera.setViewYXZ( constants::CENTER, rotation ); + + const auto rotated_frustum = camera.getFrustumBounds(); + + //The point FORWARD should be in the frustum + const WorldCoordinate point { constants::WORLD_FORWARD * 2.0f }; + + REQUIRE( rotated_frustum.near.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.far.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.top.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.bottom.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.right.distanceFrom( point ) > 0.0f ); + REQUIRE( rotated_frustum.left.distanceFrom( point ) > 0.0f ); + + REQUIRE( rotated_frustum.pointInside( point ) ); + } +} \ No newline at end of file diff --git a/tests/src/RotationTests.cpp b/tests/src/RotationTests.cpp new file mode 100644 index 0000000..97e104d --- /dev/null +++ b/tests/src/RotationTests.cpp @@ -0,0 +1,82 @@ +// +// Created by kj16609 on 2/16/24. +// + +#include + +#include "engine/primitives/TransformComponent.hpp" +#include "gtest_printers.hpp" + +using namespace fgl::engine; + +TEST_CASE( "Transform rotations", "[transform][rotation]" ) +{ + TransformComponent component; + + component.translation = constants::WORLD_CENTER; + component.scale = glm::vec3( 1.0f ); + component.rotation = Vector( 0.0f ); + + REQUIRE( component.mat4() == glm::mat4( 1.0f ) ); + + //This should pitch any point by 90 degrees (Roughly equal to WORLD_UP) + constexpr auto TEST_POINT { constants::WORLD_FORWARD }; + + SECTION( "Pitch+ (UP)" ) + { + //Rotate by pitch + component.rotation.pitch = glm::radians( 90.0f ); + + const glm::vec3 rotated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) }; + + //Must be dot here since the precision isn't good enough to be exact. + // If the dot product is close to 1, then the vectors are close to being equal + REQUIRE( glm::dot( rotated_point, constants::WORLD_UP ) > 0.99f ); + } + + SECTION( "Pitch- (DOWN)" ) + { + component.rotation.pitch = -glm::radians( 90.0f ); + + const glm::vec3 rotated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) }; + + REQUIRE( glm::dot( rotated_point, constants::WORLD_DOWN ) > 0.99f ); + } + + SECTION( "Yaw+ (RIGHT)" ) + { + component.rotation.yaw = glm::radians( 90.0f ); + + const glm::vec3 rotated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) }; + + REQUIRE( glm::dot( rotated_point, constants::WORLD_RIGHT ) > 0.99f ); + } + + SECTION( "Yaw- (LEFT)" ) + { + component.rotation.yaw = -glm::radians( 90.0f ); + + const glm::vec3 rotated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) }; + + REQUIRE( glm::dot( rotated_point, constants::WORLD_LEFT ) > 0.99f ); + } + + //Roll must be tested some other way. The testing point should be to the right, And instead should become WORLD_UP or WORLD_DOWN (WORLD_UP for -roll, WORLD_DOWN for +roll) + SECTION( "Roll+ (FORWARD)" ) + { + component.rotation.roll = glm::radians( 90.0f ); + + const glm::vec3 rotated_point { component.mat4() * glm::vec4( constants::WORLD_RIGHT, 1.0f ) }; + + REQUIRE( glm::dot( rotated_point, constants::WORLD_UP ) > 0.99f ); + } + + SECTION( "Roll- (BACKWARD)" ) + { + component.rotation.roll = -glm::radians( 90.0f ); + + const glm::vec3 rotated_point { component.mat4() * glm::vec4( constants::WORLD_RIGHT, 1.0f ) }; + + REQUIRE( glm::dot( rotated_point, constants::WORLD_DOWN ) > 0.99f ); + } +} diff --git a/tests/src/gtest_printers.hpp b/tests/src/gtest_printers.hpp new file mode 100644 index 0000000..4d08b29 --- /dev/null +++ b/tests/src/gtest_printers.hpp @@ -0,0 +1,22 @@ +// +// Created by kj16609 on 2/15/24. +// + +#pragma once + +namespace glm +{ + inline void PrintTo( const glm::vec3& vec, ::std::ostream* os ) + { + *os << "(" << vec.x << "," << vec.y << "," << vec.z << ")"; + } + +} // namespace glm + +namespace fgl::engine +{ + inline void PrintTo( const Vector& vec, ::std::ostream* os ) + { + glm::PrintTo( static_cast< glm::vec3 >( vec ), os ); + } +} // namespace fgl::engine