Fixup some tests and match up rotation for camera and objects

This commit is contained in:
2024-02-19 16:55:25 -05:00
parent acd1891edd
commit 7444258374
19 changed files with 430 additions and 353 deletions

View File

@@ -33,4 +33,4 @@ else ()
endif ()
#GLM settings
target_compile_definitions(FGLEngine PUBLIC GLM_FORCE_RADIANS GLM_FORCE_DEPTH_ZERO_TO_ONE)
target_compile_definitions(FGLEngine PUBLIC GLM_FORCE_RADIANS GLM_FORCE_DEPTH_ZERO_TO_ONE GLM_FORCE_LEFT_HANDED)

View File

@@ -5,6 +5,7 @@
#include "Camera.hpp"
#include "GameObject.hpp"
#include "engine/math/taitBryanMatrix.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/string_cast.hpp>
@@ -45,201 +46,7 @@ namespace fgl::engine
setViewDirection( position, Vector( glm::normalize( target - position ) ), up );
}
enum RotationOrder
{
XZY,
XYZ,
YXZ,
YZX,
ZYX,
ZXY,
END_OF_ENUM,
DEFAULT = XYZ
};
template < int N >
inline std::tuple< float, float > extract( const Rotation rotation, const RotationOrder order )
{
switch ( order )
{
case XZY:
switch ( N )
{
case 1:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
case 2:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
case 3:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
}
break;
case XYZ: // DEFAULT
switch ( N )
{
case 1:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
case 2:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
case 3:
return { glm::cos( -rotation.z ), glm::sin( -rotation.z ) };
}
break;
case YXZ:
switch ( N )
{
case 1:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
case 2:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
case 3:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
}
break;
case YZX:
switch ( N )
{
case 1:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
case 2:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
case 3:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
}
break;
case ZYX:
switch ( N )
{
case 1:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
case 2:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
case 3:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
}
break;
case ZXY:
switch ( N )
{
case 1:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
case 2:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
case 3:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
}
break;
case END_OF_ENUM:
throw std::runtime_error( "Unimplemented rotation order" );
}
std::unreachable();
}
glm::mat4 taitBryanMatrix( const Rotation rotation, const RotationOrder order = DEFAULT )
{
glm::mat4 mat { 1.0f };
const auto [ c1, s1 ] = extract< 1 >( rotation, order );
const auto [ c2, s2 ] = extract< 2 >( rotation, order );
const auto [ c3, s3 ] = extract< 3 >( rotation, order );
switch ( order )
{
case RotationOrder::XZY:
{
const glm::vec3 row_0 { ( c2 * c3 ), -( s2 ), ( c2 * s3 ) };
const glm::vec3 row_1 { ( s1 * s3 ) + ( c1 * c3 * s2 ),
( c1 * c2 ),
( c1 * s2 * s3 ) - ( c3 * s1 ) };
const glm::vec3 row_2 { ( c3 * s1 * s2 ) - ( c1 * s3 ),
( c2 * s1 ),
( c1 * c3 ) + ( s1 * s2 * s3 ) };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::XYZ:
{
const glm::vec3 row_0 { ( c2 * c3 ), -( c2 * s3 ), s2 };
const glm::vec3 row_1 { ( c1 * s3 ) + ( c3 * s1 * s2 ),
( c1 * c3 ) - ( s1 * s2 * s3 ),
-( c2 * s1 ) };
const glm::vec3 row_2 { ( s1 * s3 ) - ( c1 * c3 * s2 ),
( c3 * s1 ) + ( c1 * s2 * s3 ),
( c1 * c2 ) };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::YXZ:
{
const glm::vec3 row_0 { ( c1 * c3 ) + ( s1 * s2 * s3 ), ( c3 * s1 * s2 ) - ( c1 * s3 ), c2 * s1 };
const glm::vec3 row_1 { c2 * s3, c2 * c3, -s2 };
const glm::vec3 row_2 { ( c1 * s2 * s3 ) - ( c3 * s1 ), ( c1 * c3 * s2 ) + ( s1 * s3 ), c1 * c2 };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::YZX:
{
const glm::vec3 row_0 { ( c1 * c2 ),
( s1 * s3 ) - ( c1 * c3 * s2 ),
( c3 * s1 ) + ( c1 * s2 * s3 ) };
const glm::vec3 row_1 { s2, c2 * c3, -( c2 * s3 ) };
const glm::vec3 row_2 { -( c2 * s1 ),
( c1 * s3 ) + ( c3 * s1 * s2 ),
( c1 * c3 ) - ( s1 * s2 * s3 ) };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::ZYX: // Roll, Pitch, Yaw
{
const glm::vec3 row_0 { ( c1 * c2 ),
( c1 * s2 * s3 ) - ( c3 * s1 ),
( s1 * s3 ) + ( c1 * c3 * s2 ) };
const glm::vec3 row_1 { ( c2 * s1 ),
( c1 * c3 ) + ( s1 * s2 * s3 ),
( c3 * s1 * s2 ) - ( c1 * s3 ) };
const glm::vec3 row_2 { -s2, c2 * s3, c2 * c3 };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::ZXY: // Roll, Yaw, Pitch
{
const glm::vec3 row_0 { ( c1 * c3 ) - ( s1 * s2 * s3 ),
-( c2 * s1 ),
( c1 * s3 ) + ( c3 * s1 * s2 ) };
const glm::vec3 row_1 { ( c3 * s1 ) + ( c1 * s2 * s3 ),
( c1 * c2 ),
( s1 * s3 ) - ( c1 * c3 * s2 ) };
const glm::vec3 row_2 { -( c2 * s3 ), ( s2 ), ( c2 * c3 ) };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
default:
throw std::runtime_error( "Unimplemented rotation order" );
}
}
void Camera::setViewYXZ( glm::vec3 position, const Rotation rotation, const ViewMode mode )
void Camera::setView( glm::vec3 pos, const Rotation rotation, const ViewMode mode )
{
ZoneScoped;
@@ -251,16 +58,14 @@ namespace fgl::engine
{
case ViewMode::TaitBryan:
{
static auto current_rotation_order { RotationOrder::DEFAULT };
const glm::mat4 rotation_matrix { taitBryanMatrix( rotation, current_rotation_order ) };
const glm::mat4 rotation_matrix { taitBryanMatrix( rotation ) };
const glm::vec3 forward { rotation_matrix * glm::vec4( constants::WORLD_FORWARD, 0.0f ) };
const glm::vec3 camera_up { rotation_matrix * glm::vec4( constants::WORLD_UP, 0.0f ) };
view_matrix =
Matrix< MatrixType::WorldToCamera >( glm::lookAtLH( position, position + forward, camera_up ) );
view_matrix = Matrix< MatrixType::WorldToCamera >( glm::lookAtLH( pos, pos + forward, camera_up ) );
inverse_view_matrix = glm::inverse( view_matrix );
break;
}
@@ -273,7 +78,7 @@ namespace fgl::engine
throw std::runtime_error( "Unimplemented view mode" );
}
TransformComponent transform { WorldCoordinate( position ), glm::vec3( 1.0f, 1.0f, 1.0f ), rotation };
TransformComponent transform { WorldCoordinate( pos ), glm::vec3( 1.0f, 1.0f, 1.0f ), rotation };
if ( update_frustums )
{
@@ -323,11 +128,7 @@ namespace fgl::engine
const Matrix< MatrixType::ModelToWorld > Camera::frustumTranslationMatrix() const
{
glm::mat4 translation { 1.0f };
translation = glm::translate( glm::mat4( 1.0f ), getPosition() );
return Matrix< MatrixType::ModelToWorld >( translation );
return Matrix< MatrixType::ModelToWorld >( getInverseViewMatrix() );
}
WorldCoordinate Camera::getFrustumPosition() const

View File

@@ -32,6 +32,7 @@ namespace fgl::engine
Matrix< MatrixType::CameraToScreen > projection_matrix { 1.0f };
Matrix< MatrixType::WorldToCamera > view_matrix { 1.0f };
glm::mat4 inverse_view_matrix { 1.0f };
//! Frustum of the camera in model space relative to the camera
//! @note Must be transformed by the inverse view matrix to get the frustum in world space
@@ -52,7 +53,7 @@ namespace fgl::engine
Camera()
{
setPerspectiveProjection( 90.0f, 16.0f / 9.0f, constants::NEAR_PLANE, constants::FAR_PLANE );
setViewYXZ( constants::CENTER, Rotation( 0.0f, 0.0f, 0.0f ) );
setView( constants::CENTER, Rotation( 0.0f, 0.0f, 0.0f ) );
}
WorldCoordinate getFrustumPosition() const;
@@ -71,6 +72,8 @@ namespace fgl::engine
return projection_matrix * view_matrix;
}
const glm::mat4 getInverseViewMatrix() const { return glm::inverse( view_matrix ); }
void setOrthographicProjection( float left, float right, float top, float bottom, float near, float far );
void setPerspectiveProjection( float fovy, float aspect, float near, float far );
@@ -80,23 +83,11 @@ namespace fgl::engine
return WorldCoordinate( glm::inverse( view_matrix )[ 3 ] );
}
const Vector getUp() const
{
return Vector(
glm::normalize( glm::vec3( view_matrix[ 0 ][ 1 ], view_matrix[ 1 ][ 1 ], view_matrix[ 2 ][ 1 ] ) ) );
}
const Vector getUp() const { return Vector( glm::normalize( glm::vec3( inverse_view_matrix[ 1 ] ) ) ); }
const Vector getRight() const
{
return Vector(
glm::normalize( -glm::vec3( view_matrix[ 0 ][ 0 ], view_matrix[ 1 ][ 0 ], view_matrix[ 2 ][ 0 ] ) ) );
}
const Vector getRight() const { return -Vector( glm::normalize( glm::vec3( inverse_view_matrix[ 0 ] ) ) ); }
const Vector getForward() const
{
return Vector(
glm::normalize( glm::vec3( view_matrix[ 0 ][ 2 ], view_matrix[ 1 ][ 2 ], view_matrix[ 2 ][ 2 ] ) ) );
}
const Vector getForward() const { return Vector( glm::normalize( glm::vec3( inverse_view_matrix[ 2 ] ) ) ); }
const Vector getLeft() const { return -getRight(); }
@@ -113,7 +104,7 @@ namespace fgl::engine
TaitBryan
};
void setViewYXZ( glm::vec3 pos, const Rotation rotation, const ViewMode mode = TaitBryan );
void setView( glm::vec3 pos, const Rotation rotation, const ViewMode mode = TaitBryan );
};
} // namespace fgl::engine

View File

@@ -147,7 +147,7 @@ namespace fgl::engine
}
camera_controller.moveInPlaneXZ( m_window.window(), delta_time, viewer );
camera.setViewYXZ( viewer.transform.translation, viewer.transform.rotation );
camera.setView( viewer.transform.translation, viewer.transform.rotation );
{
constexpr WorldCoordinate center { 0.0f, 0.0f, 0.0f };

View File

@@ -4,56 +4,29 @@
#include "GameObject.hpp"
#include "engine/math/taitBryanMatrix.hpp"
namespace fgl::engine
{
glm::mat4 TransformComponent::mat4() const
{
const float c3 { glm::cos( -rotation.yaw ) }; // yaw
const float s3 { glm::sin( -rotation.yaw ) }; // yaw
const float c2 { glm::cos( rotation.pitch ) }; // pitch
const float s2 { glm::sin( rotation.pitch ) }; // pitch
const float c1 { glm::cos( rotation.roll ) }; // roll
const float s1 { glm::sin( rotation.roll ) }; // roll
const glm::mat4 rotation_mat { taitBryanMatrix( rotation ) };
//TODO: This uses the Tait-Bryan angles stuff, It should use the respective function.
// Or maybe something else can be done here
return glm::mat4 {
{ 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 }
};
}
glm::mat3 TransformComponent::normalMatrix() const
{
const float c3 { glm::cos( rotation.z ) };
const float s3 { glm::sin( rotation.z ) };
const float c2 { glm::cos( rotation.x ) };
const float s2 { glm::sin( rotation.x ) };
const float c1 { glm::cos( rotation.y ) };
const float s1 { glm::sin( rotation.y ) };
const glm::vec3 invScale { 1.0f / scale };
return glm::mat3 {
{
invScale.x * ( c1 * c3 + s1 * s2 * s3 ),
invScale.x * ( c2 * s3 ),
invScale.x * ( c1 * s2 * s3 - c3 * s1 ),
},
{
invScale.y * ( c3 * s1 * s2 - c1 * s3 ),
invScale.y * ( c2 * c3 ),
invScale.y * ( c1 * c3 * s2 + s1 * s3 ),
},
{
invScale.z * ( c2 * s1 ),
invScale.z * ( -s2 ),
invScale.z * ( c1 * c2 ),
},
};
return glm::mat4 { { scale.x * rotation_mat[ 0 ][ 0 ],
scale.x * rotation_mat[ 0 ][ 1 ],
scale.x * rotation_mat[ 0 ][ 2 ],
0.0f },
{ scale.y * rotation_mat[ 1 ][ 0 ],
scale.y * rotation_mat[ 1 ][ 1 ],
scale.y * rotation_mat[ 1 ][ 2 ],
0.0f },
{ scale.z * rotation_mat[ 2 ][ 0 ],
scale.z * rotation_mat[ 2 ][ 1 ],
scale.z * rotation_mat[ 2 ][ 2 ],
0.0f },
{ translation.x, translation.y, translation.z, 1.0f } };
}
} // namespace fgl::engine

View File

@@ -31,10 +31,10 @@ namespace fgl::engine
Rotation rotate { 0.0f };
if ( glfwGetKey( window, key_mappings.look_right ) == GLFW_PRESS ) rotate.yaw += 1.f;
if ( glfwGetKey( window, key_mappings.look_left ) == GLFW_PRESS ) rotate.yaw -= 1.f;
if ( glfwGetKey( window, key_mappings.look_up ) == GLFW_PRESS ) rotate.pitch += 1.f;
if ( glfwGetKey( window, key_mappings.look_down ) == GLFW_PRESS ) rotate.pitch -= 1.f;
if ( glfwGetKey( window, key_mappings.look_right ) == GLFW_PRESS ) rotate.yaw() += 1.f;
if ( glfwGetKey( window, key_mappings.look_left ) == GLFW_PRESS ) rotate.yaw() -= 1.f;
if ( glfwGetKey( window, key_mappings.look_up ) == GLFW_PRESS ) rotate.pitch() += 1.f;
if ( glfwGetKey( window, key_mappings.look_down ) == GLFW_PRESS ) rotate.pitch() -= 1.f;
static bool cursor_enabled { true };
static bool cursor_restored { false };
@@ -77,9 +77,9 @@ namespace fgl::engine
const auto xpos { pos.x };
const auto ypos { pos.y };
target.transform.rotation.yaw +=
target.transform.rotation.yaw() +=
static_cast< float >( ( xpos * 0.006 ) * static_cast< double >( look_speed ) );
target.transform.rotation.pitch -=
target.transform.rotation.pitch() -=
static_cast< float >( ( ypos * 0.006 ) * static_cast< double >( look_speed ) );
setCursorPos( window, { 0, 0 } );
@@ -90,8 +90,8 @@ namespace fgl::engine
> std::numeric_limits< float >::epsilon() )
target.transform.rotation += look_speed * dt * glm::normalize( rotate );
target.transform.rotation.pitch = glm::clamp( target.transform.rotation.pitch, -1.5f, 1.5f );
target.transform.rotation.yaw = glm::mod( target.transform.rotation.yaw, glm::two_pi< float >() );
target.transform.rotation.pitch() = glm::clamp( target.transform.rotation.pitch(), -1.5f, 1.5f );
target.transform.rotation.yaw() = glm::mod( target.transform.rotation.yaw(), glm::two_pi< float >() );
}
const glm::vec3 forward_dir { target.transform.rotation.forward() };

View File

@@ -4,6 +4,7 @@
#pragma once
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
namespace fgl::engine::constants
@@ -33,4 +34,8 @@ namespace fgl::engine::constants
constexpr auto EPSILON { std::numeric_limits< float >::epsilon() * 2 };
constexpr auto FRUSTUM_ORIGIN { constants::WORLD_CENTER };
constexpr glm::mat4 MAT4_IDENTITY { 1.0f };
} // namespace fgl::engine::constants

View File

@@ -0,0 +1,198 @@
//
// Created by kj16609 on 2/19/24.
//
#include "taitBryanMatrix.hpp"
#include <glm/mat4x4.hpp>
#include <tuple>
#include "engine/primitives/Rotation.hpp"
namespace fgl::engine
{
template < int N >
inline std::tuple< float, float > extract( const Rotation rotation, const RotationOrder order )
{
switch ( order )
{
case XZY:
switch ( N )
{
case 1:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
case 2:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
case 3:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
}
break;
case XYZ: // DEFAULT
switch ( N )
{
case 1:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
case 2:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
case 3:
return { glm::cos( -rotation.z ), glm::sin( -rotation.z ) };
}
break;
case YXZ:
switch ( N )
{
case 1:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
case 2:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
case 3:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
}
break;
case YZX:
switch ( N )
{
case 1:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
case 2:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
case 3:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
}
break;
case ZYX:
switch ( N )
{
case 1:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
case 2:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
case 3:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
}
break;
case ZXY:
switch ( N )
{
case 1:
return { glm::cos( rotation.z ), glm::sin( rotation.z ) };
case 2:
return { glm::cos( rotation.x ), glm::sin( rotation.x ) };
case 3:
return { glm::cos( rotation.y ), glm::sin( rotation.y ) };
}
break;
case END_OF_ENUM:
throw std::runtime_error( "Unimplemented rotation order" );
}
std::unreachable();
}
glm::mat4 taitBryanMatrix( const Rotation rotation, const RotationOrder order )
{
glm::mat4 mat { 1.0f };
const auto [ c1, s1 ] = extract< 1 >( rotation, order );
const auto [ c2, s2 ] = extract< 2 >( rotation, order );
const auto [ c3, s3 ] = extract< 3 >( rotation, order );
switch ( order )
{
case RotationOrder::XZY:
{
const glm::vec3 row_0 { ( c2 * c3 ), -( s2 ), ( c2 * s3 ) };
const glm::vec3 row_1 { ( s1 * s3 ) + ( c1 * c3 * s2 ),
( c1 * c2 ),
( c1 * s2 * s3 ) - ( c3 * s1 ) };
const glm::vec3 row_2 { ( c3 * s1 * s2 ) - ( c1 * s3 ),
( c2 * s1 ),
( c1 * c3 ) + ( s1 * s2 * s3 ) };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::XYZ:
{
const glm::vec3 row_0 { ( c2 * c3 ), -( c2 * s3 ), s2 };
const glm::vec3 row_1 { ( c1 * s3 ) + ( c3 * s1 * s2 ),
( c1 * c3 ) - ( s1 * s2 * s3 ),
-( c2 * s1 ) };
const glm::vec3 row_2 { ( s1 * s3 ) - ( c1 * c3 * s2 ),
( c3 * s1 ) + ( c1 * s2 * s3 ),
( c1 * c2 ) };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::YXZ:
{
const glm::vec3 row_0 { ( c1 * c3 ) + ( s1 * s2 * s3 ), ( c3 * s1 * s2 ) - ( c1 * s3 ), c2 * s1 };
const glm::vec3 row_1 { c2 * s3, c2 * c3, -s2 };
const glm::vec3 row_2 { ( c1 * s2 * s3 ) - ( c3 * s1 ), ( c1 * c3 * s2 ) + ( s1 * s3 ), c1 * c2 };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::YZX:
{
const glm::vec3 row_0 { ( c1 * c2 ),
( s1 * s3 ) - ( c1 * c3 * s2 ),
( c3 * s1 ) + ( c1 * s2 * s3 ) };
const glm::vec3 row_1 { s2, c2 * c3, -( c2 * s3 ) };
const glm::vec3 row_2 { -( c2 * s1 ),
( c1 * s3 ) + ( c3 * s1 * s2 ),
( c1 * c3 ) - ( s1 * s2 * s3 ) };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::ZYX: // Roll, Pitch, Yaw
{
const glm::vec3 row_0 { ( c1 * c2 ),
( c1 * s2 * s3 ) - ( c3 * s1 ),
( s1 * s3 ) + ( c1 * c3 * s2 ) };
const glm::vec3 row_1 { ( c2 * s1 ),
( c1 * c3 ) + ( s1 * s2 * s3 ),
( c3 * s1 * s2 ) - ( c1 * s3 ) };
const glm::vec3 row_2 { -s2, c2 * s3, c2 * c3 };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
case RotationOrder::ZXY: // Roll, Yaw, Pitch
{
const glm::vec3 row_0 { ( c1 * c3 ) - ( s1 * s2 * s3 ),
-( c2 * s1 ),
( c1 * s3 ) + ( c3 * s1 * s2 ) };
const glm::vec3 row_1 { ( c3 * s1 ) + ( c1 * s2 * s3 ),
( c1 * c2 ),
( s1 * s3 ) - ( c1 * c3 * s2 ) };
const glm::vec3 row_2 { -( c2 * s3 ), ( s2 ), ( c2 * c3 ) };
mat[ 0 ] = glm::vec4( row_0, 0.0f );
mat[ 1 ] = glm::vec4( row_1, 0.0f );
mat[ 2 ] = glm::vec4( row_2, 0.0f );
return mat;
}
default:
throw std::runtime_error( "Unimplemented rotation order" );
}
}
} // namespace fgl::engine

View File

@@ -0,0 +1,26 @@
//
// Created by kj16609 on 2/19/24.
//
#pragma once
#include <glm/mat4x4.hpp>
namespace fgl::engine
{
class Rotation;
enum RotationOrder
{
XZY,
XYZ,
YXZ,
YZX,
ZYX,
ZXY,
END_OF_ENUM,
DEFAULT = ZXY
};
glm::mat4 taitBryanMatrix( const Rotation rotation, const RotationOrder order = DEFAULT );
} // namespace fgl::engine

View File

@@ -32,7 +32,7 @@ namespace fgl::engine
glm::vec3 Rotation::forward() const
{
//TODO: Figure out how to do this with Z axis bullshit
return glm::vec3 { glm::sin( yaw ), glm::cos( yaw ), 0.0f };
return glm::vec3 { glm::sin( yaw() ), glm::cos( yaw() ), 0.0f };
}
glm::vec3 Rotation::backwards() const

View File

@@ -10,9 +10,17 @@ namespace fgl::engine
{
struct Rotation : public glm::vec3
{
float& pitch { x };
float& roll { y };
float& yaw { z };
float& pitch() { return x; }
float pitch() const { return x; }
float& roll() { return y; }
float roll() const { return z; }
float& yaw() { return z; }
float yaw() const { return z; }
Rotation();

View File

@@ -23,8 +23,6 @@ namespace fgl::engine
glm::mat4 mat4() const;
inline Matrix< MatrixType::ModelToWorld > mat() const { return Matrix< MatrixType::ModelToWorld >( mat4() ); }
glm::mat3 normalMatrix() const;
};
} // namespace fgl::engine

View File

@@ -6,6 +6,7 @@ file(GLOB_RECURSE FGL_TEST_SOURCES "*.cpp")
add_executable(FGLTests ${FGL_TEST_SOURCES})
target_link_libraries(FGLTests FGLEngine Catch2::Catch2WithMain)
target_compile_definitions(FGLTests PUBLIC GLM_FORCE_RADIANS GLM_FORCE_DEPTH_ZERO_TO_ONE GLM_FORCE_LEFT_HANDED)
include(CTest)
include(Catch)

View File

@@ -4,6 +4,8 @@
#include <catch2/catch_all.hpp>
#include <iostream>
#define EXPOSE_CAMERA_INTERNAL
#include "engine/Camera.hpp"
#include "gtest_printers.hpp"
@@ -26,41 +28,48 @@ TEST_CASE( "Camera", "[camera]" )
camera.setPerspectiveProjection( 90.0f, 1.0f, constants::NEAR_PLANE, constants::FAR_PLANE );
SECTION( "Default orientation" )
WHEN( "Camera is it's default orientation" )
{
const auto camera_up { camera.getUp() };
REQUIRE( camera_up == constants::WORLD_UP );
camera.setView( constants::WORLD_CENTER, Rotation( 0.0f ) );
const auto camera_forward { camera.getForward() };
REQUIRE( camera_forward == constants::WORLD_FORWARD );
THEN( "Camera up is WORLD_UP" )
{
const auto camera_up { camera.getUp() };
REQUIRE( camera_up == constants::WORLD_UP );
}
const auto camera_right { camera.getRight() };
REQUIRE( camera_right == constants::WORLD_RIGHT );
THEN( "Camera right is WORLD_RIGHT" )
{
const auto camera_right { camera.getRight() };
REQUIRE( camera_right == constants::WORLD_RIGHT );
}
THEN( "Camera forward is WORLD_FORWARD" )
{
const auto camera_forward { camera.getForward() };
REQUIRE( camera_forward == constants::WORLD_FORWARD );
}
}
SECTION( "Rotations" )
{
Rotation rotation_vec { 0.0f };
SECTION( "Yaw+ (Right)" )
WHEN( "Camera is rotated +90 Yaw" )
{
rotation_vec.yaw = glm::radians( 90.0f );
camera.setViewYXZ( constants::WORLD_CENTER, rotation_vec );
rotation_vec.yaw() = glm::radians( 90.0f );
camera.setView( constants::WORLD_CENTER, rotation_vec );
const auto camera_forward { camera.getForward() };
REQUIRE( camera_forward.x == constants::WORLD_LEFT.x );
REQUIRE( camera_forward.y <= std::numeric_limits< float >::epsilon() );
REQUIRE( camera_forward.z <= std::numeric_limits< float >::epsilon() );
REQUIRE( camera_forward == constants::WORLD_RIGHT );
}
SECTION( "Yaw- (Left)" )
WHEN( "Camera is rotated -90 Yaw" )
{
rotation_vec.yaw = glm::radians( -90.0f );
camera.setViewYXZ( constants::WORLD_CENTER, rotation_vec );
rotation_vec.yaw() = glm::radians( -90.0f );
camera.setView( constants::WORLD_CENTER, rotation_vec );
const auto camera_forward { camera.getForward() };
REQUIRE( camera_forward.x == constants::WORLD_RIGHT.x );
REQUIRE( camera_forward.y <= std::numeric_limits< float >::epsilon() );
REQUIRE( camera_forward.z <= std::numeric_limits< float >::epsilon() );
REQUIRE( camera_forward == constants::WORLD_LEFT );
}
}
@@ -74,7 +83,7 @@ TEST_CASE( "Camera", "[camera]" )
const Rotation rotation { x_gen, y_gen, z_gen };
camera.setViewYXZ( constants::WORLD_RIGHT, rotation );
camera.setView( constants::WORLD_RIGHT, rotation );
THEN( "Camera translation should not change" )
{
@@ -85,7 +94,7 @@ TEST_CASE( "Camera", "[camera]" )
WHEN( "Camera is translated right by WORLD_RIGHT" )
{
camera.setViewYXZ( constants::WORLD_CENTER + constants::WORLD_RIGHT, Rotation( 0.0f ) );
camera.setView( constants::WORLD_CENTER + constants::WORLD_RIGHT, Rotation( 0.0f ) );
THEN( "camera.getPosition() should be WORLD_RIGHT" )
{
@@ -95,17 +104,29 @@ TEST_CASE( "Camera", "[camera]" )
THEN( "A point at the origin should be translated to the left" )
{
const auto matrix { camera.getProjectionViewMatrix() };
const auto point { matrix * glm::vec4( constants::WORLD_CENTER, 1.0f ) };
const glm::mat4 combined_matrix { camera.getProjectionViewMatrix() };
REQUIRE( point.x < 0.0f );
REQUIRE( point.y == 0.0f );
const glm::vec3 point { constants::WORLD_CENTER };
const glm::vec3 translated_point { combined_matrix * glm::vec4( point, 1.0f ) };
const auto projection_matrix { static_cast< glm::mat4 >( camera.getProjectionMatrix() ) };
const auto view_matrix { static_cast< glm::mat4 >( camera.getViewMatrix() ) };
CAPTURE( point );
CAPTURE( projection_matrix );
CAPTURE( view_matrix );
CAPTURE( combined_matrix );
CAPTURE( translated_point );
REQUIRE( translated_point.x < 0.0f );
REQUIRE( translated_point.y == 0.0f );
}
}
WHEN( "Camera is translated up by WORLD_UP" )
{
camera.setViewYXZ( constants::WORLD_CENTER + constants::WORLD_UP, Rotation( 0.0f ) );
camera.setView( constants::WORLD_CENTER + constants::WORLD_UP, Rotation( 0.0f ) );
THEN( "camera.getPosition() should be WORLD_UP" )
{
@@ -116,7 +137,9 @@ TEST_CASE( "Camera", "[camera]" )
THEN( "A point at the origin should be translated down" )
{
const auto matrix { camera.getProjectionViewMatrix() };
const auto point { matrix * glm::vec4( constants::WORLD_CENTER, 1.0f ) };
const glm::vec3 point { matrix * glm::vec4( constants::WORLD_CENTER, 1.0f ) };
CAPTURE( point );
REQUIRE( point.x == 0.0f );
REQUIRE( point.y < 0.0f );
@@ -125,7 +148,7 @@ TEST_CASE( "Camera", "[camera]" )
WHEN( "Camera is translated forward by WORLD_FORWARD" )
{
camera.setViewYXZ( constants::WORLD_CENTER + constants::WORLD_FORWARD, Rotation( 0.0f ) );
camera.setView( constants::WORLD_CENTER + constants::WORLD_FORWARD, Rotation( 0.0f ) );
THEN( "camera.getPosition() should be WORLD_FORWARD" )
{

View File

@@ -32,7 +32,7 @@ TEST_CASE( "Frustum", "[frustum][rotation][translation]" )
{
WHEN( "Translated backwards" )
{
camera.setViewYXZ( constants::WORLD_CENTER - constants::WORLD_FORWARD, Rotation( 0.0f, 0.0f, 0.0f ) );
camera.setView( constants::WORLD_CENTER - constants::WORLD_FORWARD, Rotation( 0.0f, 0.0f, 0.0f ) );
//Translate backwards by 1 world unit
const auto translated_backwards { camera.getFrustumBounds() };
@@ -68,7 +68,7 @@ TEST_CASE( "Frustum", "[frustum][rotation][translation]" )
WHEN( "Translated Forward" )
{
camera.setViewYXZ( constants::WORLD_CENTER + constants::WORLD_FORWARD, Rotation( 0.0f, 0.0f, 0.0f ) );
camera.setView( constants::WORLD_CENTER + constants::WORLD_FORWARD, Rotation( 0.0f, 0.0f, 0.0f ) );
//Translate forward by 1 world unit
const auto translated_forward { camera.getFrustumBounds() };
@@ -99,7 +99,7 @@ TEST_CASE( "Frustum", "[frustum][rotation][translation]" )
WHEN( "Translated Up" )
{
camera.setViewYXZ( constants::WORLD_CENTER + constants::WORLD_UP, Rotation( 0.0f, 0.0f, 0.0f ) );
camera.setView( constants::WORLD_CENTER + constants::WORLD_UP, Rotation( 0.0f, 0.0f, 0.0f ) );
//Translate up by 1 world unit
const auto translated_up { camera.getFrustumBounds() };
@@ -199,9 +199,9 @@ TEST_CASE( "Frustum", "[frustum][rotation][translation]" )
SECTION( "Pitch" )
{
Rotation rotation { 0.0f, 0.0f, 0.0f };
rotation.pitch -= glm::radians( 90.0f );
rotation.pitch() -= glm::radians( 90.0f );
camera.setViewYXZ( constants::CENTER, rotation );
camera.setView( constants::CENTER, rotation );
const auto rotated_frustum = camera.getFrustumBounds();
@@ -211,9 +211,8 @@ TEST_CASE( "Frustum", "[frustum][rotation][translation]" )
//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.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 );
@@ -230,9 +229,9 @@ TEST_CASE( "Frustum", "[frustum][rotation][translation]" )
SECTION( "Yaw" )
{
Rotation rotation { 0.0f, 0.0f, 0.0f };
rotation.yaw += glm::radians( 90.0f );
rotation.yaw() += glm::radians( 90.0f );
camera.setViewYXZ( constants::CENTER, rotation );
camera.setView( constants::CENTER, rotation );
const auto rotated_frustum = camera.getFrustumBounds();
@@ -262,9 +261,9 @@ TEST_CASE( "Frustum", "[frustum][rotation][translation]" )
SECTION( "Roll" )
{
Rotation rotation { 0.0f, 0.0f, 0.0f };
rotation.roll -= glm::radians( 90.0f );
rotation.roll() -= glm::radians( 90.0f );
camera.setViewYXZ( constants::CENTER, rotation );
camera.setView( constants::CENTER, rotation );
const auto rotated_frustum = camera.getFrustumBounds();

View File

@@ -4,6 +4,7 @@
#include <catch2/catch_all.hpp>
#include "engine/math/taitBryanMatrix.hpp"
#include "engine/primitives/TransformComponent.hpp"
#include "gtest_printers.hpp"
@@ -17,17 +18,28 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
component.scale = glm::vec3( 1.0f );
component.rotation = Rotation( 0.0f );
REQUIRE( component.mat4() == glm::mat4( 1.0f ) );
constexpr auto TEST_POINT { constants::WORLD_FORWARD };
SECTION( "Rotation" )
GIVEN( "A default rotation" )
{
THEN( "The rotation is (0,0,0)" )
{
REQUIRE( component.rotation.pitch() == 0.0f );
REQUIRE( component.rotation.yaw() == 0.0f );
REQUIRE( component.rotation.roll() == 0.0f );
}
THEN( "The rotation matrix is the identity matrix" )
{
REQUIRE( constants::MAT4_IDENTITY == taitBryanMatrix( component.rotation ) );
REQUIRE( constants::MAT4_IDENTITY == component.mat4() );
}
// Tests behaviour that a point from WORLD_FORWARD should end up WORLD_UP when pitched 90 degrees
WHEN( "Rotated +90 Pitch" )
{
//Rotate by pitch
component.rotation.pitch = glm::radians( 90.0f );
component.rotation.pitch() = glm::radians( 90.0f );
const glm::vec3 rotated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) };
@@ -40,7 +52,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
// Tests behaviour that a point from WORLD_FORWARD should end up WORLD_DOWN when pitched -90 degrees
WHEN( "Rotated -90 Pitch" )
{
component.rotation.pitch = -glm::radians( 90.0f );
component.rotation.pitch() = -glm::radians( 90.0f );
const glm::vec3 rotated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) };
@@ -53,7 +65,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
// Tests behaviour that a point from WORLD_FORWARD should end up WORLD_RIGHT when yawed 90 degrees
WHEN( "Rotated +90 Yaw" )
{
component.rotation.yaw = glm::radians( 90.0f );
component.rotation.yaw() = glm::radians( 90.0f );
const glm::vec3 rotated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) };
@@ -66,7 +78,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
// Tests behaviour that a point from WORLD_FORWARD should end up WORLD_LEFT when yawed -90 degrees
WHEN( "Rotated -90 Yaw" )
{
component.rotation.yaw = -glm::radians( 90.0f );
component.rotation.yaw() = -glm::radians( 90.0f );
const glm::vec3 rotated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) };
@@ -80,7 +92,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
//This behaviour assumes that WORLD_RIGHT is 90 deg YAW+ from WORLD_FORWARD
WHEN( "Rotated +90 Roll" )
{
component.rotation.roll = glm::radians( 90.0f );
component.rotation.roll() = glm::radians( 90.0f );
const glm::vec3 rotated_point { component.mat4() * glm::vec4( constants::WORLD_RIGHT, 1.0f ) };
@@ -93,7 +105,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
//Tests behaviour that a point from WORLD_RIGHT should end up WORLD_UP when rolled -90 degrees
WHEN( "Rotated -90 Roll" )
{
component.rotation.roll = -glm::radians( 90.0f );
component.rotation.roll() = -glm::radians( 90.0f );
const glm::vec3 rotated_point { component.mat4() * glm::vec4( constants::WORLD_RIGHT, 1.0f ) };
@@ -106,7 +118,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
SECTION( "Translation" )
{
WHEN( "Translated X+" )
WHEN( "Translated Z+" )
{
component.translation.up() += 1.0f;
@@ -130,7 +142,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
}
}
WHEN( "Translated Z+" )
WHEN( "Translated X+" )
{
component.translation.right() += 1.0f;
@@ -147,7 +159,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
{
WHEN( "Translated X+1 and Rotated Y+90" )
{
component.rotation.yaw = glm::radians( 90.0f );
component.rotation.yaw() = glm::radians( 90.0f );
component.translation.right() += 1.0f;
const glm::vec3 translated_point { component.mat4() * glm::vec4( constants::WORLD_FORWARD, 1.0f ) };
@@ -160,7 +172,7 @@ TEST_CASE( "Transform", "[transform][rotation][translation]" )
SECTION( "Translated X+1 Yaw-90" )
{
component.rotation.yaw = glm::radians( -90.0f );
component.rotation.yaw() = glm::radians( -90.0f );
component.translation.right() += 1.0f;
const glm::vec3 translated_point { component.mat4() * glm::vec4( TEST_POINT, 1.0f ) };

View File

@@ -10,19 +10,6 @@
using namespace fgl::engine;
namespace Catch
{
template <>
struct StringMaker< Rotation >
{
static std::string convert( const Rotation& value )
{
return StringMaker< glm::vec3 >::convert( static_cast< glm::vec3 >( value ) );
}
};
} // namespace Catch
TEST_CASE( "Vector", "[vector][transforms]" )
{
WHEN( "Vector is default constructed" )
@@ -48,7 +35,7 @@ TEST_CASE( "Vector", "[vector][transforms]" )
//Rotate by 90 degrees on yaw
TransformComponent transform;
transform.rotation.yaw += glm::radians( 90.0f );
transform.rotation.yaw() += glm::radians( 90.0f );
const Vector value { transform.mat() * rotation_vec };
@@ -64,7 +51,7 @@ TEST_CASE( "Vector", "[vector][transforms]" )
//Rotate by 90 degrees on yaw
TransformComponent transform;
transform.rotation.yaw += glm::radians( -90.0f );
transform.rotation.yaw() += glm::radians( -90.0f );
const Vector value { transform.mat() * rotation_vec };
@@ -82,15 +69,15 @@ TEST_CASE( "Rotation", "[vector][transforms]" )
Rotation rot {};
THEN( "Yaw should be 0.0f" )
{
REQUIRE( rot.yaw == 0.0f );
REQUIRE( rot.yaw() == 0.0f );
}
THEN( "Pitch should be 0.0f" )
{
REQUIRE( rot.pitch == 0.0f );
REQUIRE( rot.pitch() == 0.0f );
}
THEN( "Roll should be 0.0f" )
{
REQUIRE( rot.roll == 0.0f );
REQUIRE( rot.roll() == 0.0f );
}
THEN( "Forward should be WORLD_FORWARD" )

View File

@@ -9,6 +9,7 @@
#include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>
#include "engine/primitives/Rotation.hpp"
#include "engine/primitives/Vector.hpp"
namespace Catch
@@ -28,6 +29,25 @@ namespace Catch
}
};
template <>
struct StringMaker< fgl::engine::Rotation >
{
static std::string convert( const fgl::engine::Rotation& rot )
{
return StringMaker< glm::vec3 >::convert( static_cast< glm::vec3 >( rot ) );
}
};
template <>
struct StringMaker< glm::mat4 >
{
static std::string convert( const glm::mat4& mat )
{
return "\n\t" + glm::to_string( mat[ 0 ] ) + "\n\t" + glm::to_string( mat[ 1 ] ) + "\n\t"
+ glm::to_string( mat[ 2 ] ) + "\n\t" + glm::to_string( mat[ 3 ] );
}
};
} // namespace Catch
#ifndef NDEBUG

View File

@@ -0,0 +1,35 @@
//
// Created by kj16609 on 2/19/24.
//
#include <catch2/catch_all.hpp>
#include "engine/Camera.hpp"
#include "engine/math/taitBryanMatrix.hpp"
#include "gtest_printers.hpp"
using namespace fgl::engine;
TEST_CASE( "Tait-Bryan rotations", "[matrix][rotation][camera]" )
{
WHEN( "Rotation is default constructed" )
{
THEN( "The rotation is (0,0,0)" )
{
Rotation rotation;
REQUIRE( rotation.pitch() == 0.0f );
REQUIRE( rotation.yaw() == 0.0f );
REQUIRE( rotation.roll() == 0.0f );
}
}
WHEN( "Given a default rotation (0,0,0)" )
{
Rotation rot;
THEN( "The rotation matrix is the identity matrix" )
{
REQUIRE( constants::MAT4_IDENTITY == taitBryanMatrix( rot ) );
}
}
}