More progress on getting frustums working

This commit is contained in:
2024-02-16 16:19:38 -05:00
parent 516c31dc92
commit c4b01b8a27
24 changed files with 765 additions and 143 deletions

View File

@@ -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)

View File

@@ -0,0 +1 @@
add_subdirectory(${CMAKE_SOURCE_DIR}/dependencies/catch2)

View File

@@ -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)

1
dependencies/catch2 vendored Submodule

Submodule dependencies/catch2 added at b817497528

View File

@@ -4,6 +4,8 @@
#include "Camera.hpp"
#include "GameObject.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/string_cast.hpp>
@@ -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* >( &current_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

View File

@@ -4,22 +4,31 @@
#pragma once
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>
#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

View File

@@ -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" );

View File

@@ -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 }
};
}

View File

@@ -10,6 +10,8 @@
#include <unordered_map>
#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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -6,6 +6,8 @@
#include <glm/glm.hpp>
#include <ostream>
#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

View File

@@ -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 )
{

View File

@@ -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

View File

@@ -9,6 +9,9 @@
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <stdexcept>
#include <utility>
#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<CoordinateType::World> 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

View File

@@ -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

View File

@@ -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 )

View File

@@ -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)

View File

@@ -6,7 +6,9 @@
using namespace fgl::engine;
int main()
#include <catch2/catch_all.hpp>
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;
}

View File

@@ -0,0 +1,35 @@
//
// Created by kj16609 on 2/15/24.
//
#include <catch2/catch_all.hpp>
#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 );
}

View File

@@ -0,0 +1,256 @@
//
// Created by kj16609 on 2/15/24.
//
#include <catch2/catch_all.hpp>
#include <iostream>
#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 ) );
}
}

View File

@@ -0,0 +1,82 @@
//
// Created by kj16609 on 2/16/24.
//
#include <catch2/catch_all.hpp>
#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 );
}
}

View File

@@ -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