From 7ce37e0b32d0c77dd79e212da6423786a448f0f7 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 20 Oct 2024 17:59:29 +0200 Subject: [PATCH 01/84] add CCubeProjection.hpp, ILinearProjection.hpp & IProjection.hpp headers (temporary to common/include/camera directory) --- common/include/camera/CCubeProjection.hpp | 31 +++++++++++++++++++++ common/include/camera/ILinearProjection.hpp | 28 +++++++++++++++++++ common/include/camera/IProjection.hpp | 24 ++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 common/include/camera/CCubeProjection.hpp create mode 100644 common/include/camera/ILinearProjection.hpp create mode 100644 common/include/camera/IProjection.hpp diff --git a/common/include/camera/CCubeProjection.hpp b/common/include/camera/CCubeProjection.hpp new file mode 100644 index 000000000..ef3d25d71 --- /dev/null +++ b/common/include/camera/CCubeProjection.hpp @@ -0,0 +1,31 @@ +#ifndef _NBL_CCUBE_PROJECTION_HPP_ +#define _NBL_CCUBE_PROJECTION_HPP_ + +#include "ILinearProjection.hpp" + +namespace nbl::hlsl +{ + +struct CCubeProjectionBase +{ + using base_t = ILinearProjection>; +}; + +//! Class providing linear cube projections with projection matrix per face of a cube, each projection matrix represents a single view-port +class CCubeProjection : public CCubeProjectionBase::base_t +{ +public: + using base_t = typename CCubeProjectionBase::base_t; + using range_t = typename base_t::range_t; + + CCubeProjection(range_t&& matrices = {}) : base_t(std::move(matrices)) {} + + range_t& getCubeFaceProjectionMatrices() + { + return base_t::getViewportMatrices(); + } +}; + +} // nbl::hlsl namespace + +#endif // _NBL_CCUBE_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp new file mode 100644 index 000000000..ba39018a5 --- /dev/null +++ b/common/include/camera/ILinearProjection.hpp @@ -0,0 +1,28 @@ +#ifndef _NBL_ILINEAR_PROJECTION_HPP_ +#define _NBL_ILINEAR_PROJECTION_HPP_ + +#include "IProjection.hpp" + +namespace nbl::hlsl +{ + +//! Interface class for linear projections range storage - a projection matrix represents single view-port +template +class ILinearProjection : protected IProjection +{ +public: + using base_t = typename IProjection; + using range_t = typename base_t::range_t; + + ILinearProjection(range_t&& matrices) : base_t(matrices) {} + +protected: + inline range_t& getViewportMatrices() + { + return base_t::m_projMatrices; + } +}; + +} // nbl::hlsl namespace + +#endif // _NBL_ILINEAR_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp new file mode 100644 index 000000000..164c48b9f --- /dev/null +++ b/common/include/camera/IProjection.hpp @@ -0,0 +1,24 @@ +#ifndef _NBL_IPROJECTION_HPP_ +#define _NBL_IPROJECTION_HPP_ + +#include + +namespace nbl::hlsl +{ +//! Interface class for projection matrices range storage +template> +requires nbl::is_any_of_v, float64_t4x4, float32_t4x4> +class IProjection +{ +public: + using value_t = std::ranges::iterator_t; + using range_t = Range; + +protected: + IProjection(const range_t& matrices) : m_projMatrices(matrices) {} + range_t m_projMatrices; +}; + +} // nbl::hlsl namespace + +#endif // _NBL_IPROJECTION_HPP_ \ No newline at end of file From e2392293550ef5865646617a2cfbf3638bd805e2 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 20 Oct 2024 19:18:14 +0200 Subject: [PATCH 02/84] add range_value_t to IProjection --- common/include/camera/IProjection.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index 164c48b9f..d0486c013 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -11,8 +11,8 @@ requires nbl::is_any_of_v, float64_t4x4, float class IProjection { public: - using value_t = std::ranges::iterator_t; using range_t = Range; + using range_value_t = std::ranges::range_value_t; protected: IProjection(const range_t& matrices) : m_projMatrices(matrices) {} From 5e039226d73450c2063df5cb5757d71483cce613 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 21 Oct 2024 14:39:53 +0200 Subject: [PATCH 03/84] create IProjectionRange, pair with IProjection, use concepts for specific range type & projection matrix type constraints, update ILinearProjection and CCubeProjection - now I can pair IProjection with a gimbal --- common/include/camera/CCubeProjection.hpp | 12 +++--- common/include/camera/ILinearProjection.hpp | 11 +++--- common/include/camera/IProjection.hpp | 43 +++++++++++++++++---- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/common/include/camera/CCubeProjection.hpp b/common/include/camera/CCubeProjection.hpp index ef3d25d71..2e2a91218 100644 --- a/common/include/camera/CCubeProjection.hpp +++ b/common/include/camera/CCubeProjection.hpp @@ -8,7 +8,9 @@ namespace nbl::hlsl struct CCubeProjectionBase { - using base_t = ILinearProjection>; + using projection_t = typename IProjection; + using projection_range_t = std::array; + using base_t = ILinearProjection; }; //! Class providing linear cube projections with projection matrix per face of a cube, each projection matrix represents a single view-port @@ -16,13 +18,13 @@ class CCubeProjection : public CCubeProjectionBase::base_t { public: using base_t = typename CCubeProjectionBase::base_t; - using range_t = typename base_t::range_t; + using projection_range_t = typename base_t::range_t; - CCubeProjection(range_t&& matrices = {}) : base_t(std::move(matrices)) {} + CCubeProjection(projection_range_t&& projections = {}) : base_t(std::move(projections)) {} - range_t& getCubeFaceProjectionMatrices() + projection_range_t& getCubeFaceProjections() { - return base_t::getViewportMatrices(); + return base_t::getViewportProjections(); } }; diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp index ba39018a5..3539b153f 100644 --- a/common/include/camera/ILinearProjection.hpp +++ b/common/include/camera/ILinearProjection.hpp @@ -8,18 +8,19 @@ namespace nbl::hlsl //! Interface class for linear projections range storage - a projection matrix represents single view-port template -class ILinearProjection : protected IProjection +class ILinearProjection : protected IProjectionRange { public: - using base_t = typename IProjection; + using base_t = typename IProjectionRange; using range_t = typename base_t::range_t; + using projection_t = typename base_t::projection_t; - ILinearProjection(range_t&& matrices) : base_t(matrices) {} + ILinearProjection(range_t&& projections) : base_t(projections) {} protected: - inline range_t& getViewportMatrices() + inline range_t& getViewportProjections() { - return base_t::m_projMatrices; + return base_t::m_projectionRange; } }; diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index d0486c013..fbd6f43e0 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -5,20 +5,49 @@ namespace nbl::hlsl { -//! Interface class for projection matrices range storage -template> -requires nbl::is_any_of_v, float64_t4x4, float32_t4x4> + +template +concept ProjectionMatrix = is_any_of_v; + +//! Interface class for projection +template class IProjection { +public: + using value_t = T; + + IProjection(const value_t& matrix = {}) : m_projectionMatrix(matrix) {} + value_t& getProjectionMatrix() { return m_projectionMatrix; } + +protected: + value_t m_projectionMatrix; +}; + +template +concept ProjectionRange = requires +{ + typename std::ranges::range_value_t; + std::is_base_of_v::value_t>, std::ranges::range_value_t>; +}; + +//! Interface class for a range of IProjection projections +template, 1u>> +class IProjectionRange +{ public: using range_t = Range; - using range_value_t = std::ranges::range_value_t; + using projection_t = std::ranges::range_value_t; + + //! Constructor for the range of projections + IProjectionRange(const range_t& projections) : m_projectionRange(projections) {} + + //! Get the stored range of projections + const range_t& getProjections() const { return m_projectionRange; } protected: - IProjection(const range_t& matrices) : m_projMatrices(matrices) {} - range_t m_projMatrices; + range_t m_projectionRange; }; -} // nbl::hlsl namespace +} // namespace nbl::hlsl #endif // _NBL_IPROJECTION_HPP_ \ No newline at end of file From 6196c611add87683dc2f53b81813976d5b82812d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 21 Oct 2024 18:39:36 +0200 Subject: [PATCH 04/84] save draft of CVirtualCameraEvent.hpp & ICameraControl.hpp, update sources, save main.cpp tests, add TODO comments --- 61_UI/main.cpp | 24 ++++ common/include/camera/CCubeProjection.hpp | 19 +-- common/include/camera/CVirtualCameraEvent.hpp | 63 ++++++++++ common/include/camera/ICameraControl.hpp | 117 ++++++++++++++++++ common/include/camera/ILinearProjection.hpp | 2 +- common/include/camera/IProjection.hpp | 14 +-- 6 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 common/include/camera/CVirtualCameraEvent.hpp create mode 100644 common/include/camera/ICameraControl.hpp diff --git a/61_UI/main.cpp b/61_UI/main.cpp index ef03d88d6..37e50805c 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -4,6 +4,10 @@ #include "common.hpp" +#include "camera/CCubeProjection.hpp" +#include "camera/ICameraControl.hpp" +#include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING + /* Renders scene texture to an offline framebuffer which color attachment @@ -489,6 +493,26 @@ class UISampleApp final : public examples::SimpleWindowedApplication oracle.reportBeginFrameRecord(); camera.mapKeysToArrows(); + /* + TESTS, TODO: remove all once finished work & integrate with the example properly + */ + + using cube_projection_t = CCubeProjection; + using constraints_t = CCubeProjection<>::constraints_t; + using camera_control_t = ICameraController; + using gimbal_t = camera_control_t::CGimbal; + + cube_projection_t cubeProjection; // can init all at construction, but will init only first for tests + auto& projections = cubeProjection.getCubeFaceProjections(); + auto firstFaceProjection = projections.front(); + firstFaceProjection = make_smart_refctd_ptr(glm::perspectiveLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); + + const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance), + target(0.f, 0.f, 0.f), up(0.f, 1.f, 0.f); + + auto gimbal = make_smart_refctd_ptr(smart_refctd_ptr(firstFaceProjection), position, target, up); + auto controller = make_smart_refctd_ptr(smart_refctd_ptr(gimbal)); + return true; } diff --git a/common/include/camera/CCubeProjection.hpp b/common/include/camera/CCubeProjection.hpp index 2e2a91218..e09d01dce 100644 --- a/common/include/camera/CCubeProjection.hpp +++ b/common/include/camera/CCubeProjection.hpp @@ -6,23 +6,26 @@ namespace nbl::hlsl { -struct CCubeProjectionBase +template +struct CCubeProjectionConstraints { - using projection_t = typename IProjection; - using projection_range_t = std::array; + using matrix_t = typename T; + using projection_t = typename IProjection; + using projection_range_t = std::array, 6u>; using base_t = ILinearProjection; }; //! Class providing linear cube projections with projection matrix per face of a cube, each projection matrix represents a single view-port -class CCubeProjection : public CCubeProjectionBase::base_t +template +class CCubeProjection : public CCubeProjectionConstraints::base_t { public: - using base_t = typename CCubeProjectionBase::base_t; - using projection_range_t = typename base_t::range_t; + using constraints_t = CCubeProjectionConstraints; + using base_t = typename constraints_t::base_t; - CCubeProjection(projection_range_t&& projections = {}) : base_t(std::move(projections)) {} + CCubeProjection(constraints_t::projection_range_t&& projections = {}) : base_t(std::move(projections)) {} - projection_range_t& getCubeFaceProjections() + constraints_t::projection_range_t& getCubeFaceProjections() { return base_t::getViewportProjections(); } diff --git a/common/include/camera/CVirtualCameraEvent.hpp b/common/include/camera/CVirtualCameraEvent.hpp new file mode 100644 index 000000000..b91712321 --- /dev/null +++ b/common/include/camera/CVirtualCameraEvent.hpp @@ -0,0 +1,63 @@ +#ifndef _NBL_VIRTUAL_CAMERA_EVENT_HPP_ +#define _NBL_VIRTUAL_CAMERA_EVENT_HPP_ + +#include "nbl/builtin/hlsl/cpp_compat/matrix.hlsl" + +// TODO: DIFFERENT NAMESPACE +namespace nbl::hlsl +{ + +//! Virtual camera event representing a manipulation +enum class VirtualEventType +{ + //! Moves the camera forward or backward + MoveForward, + + //! Zooms in or out + Zoom, + + //! Moves the camera left/right and up/down + Strafe, + + //! Rotates the camera horizontally + Pan, + + //! Rotates the camera vertically + Tilt, + + //! Rolls the camera around the Z axis + Roll, + + //! Resets the camera to a default position and orientation + Reset +}; + +//! We encode all manipulation type values into a vec4 & assume they are written in NDC coordinate system with respect to camera +using manipulation_value_t = float64_t4x4; + +class CVirtualCameraEvent +{ +public: + CVirtualCameraEvent(VirtualEventType type, const manipulation_value_t value) + : type(type), value(value) {} + + // Returns the type of virtual event + VirtualEventType getType() const + { + return type; + } + + // Returns the associated value for manipulation + manipulation_value_t getValue() const + { + return value; + } + +private: + VirtualEventType type; + manipulation_value_t value; +}; + +} + +#endif // _NBL_VIRTUAL_CAMERA_EVENT_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp new file mode 100644 index 000000000..c62871644 --- /dev/null +++ b/common/include/camera/ICameraControl.hpp @@ -0,0 +1,117 @@ +#ifndef _NBL_I_CAMERA_CONTROLLER_HPP_ +#define _NBL_I_CAMERA_CONTROLLER_HPP_ + +#include "IProjection.hpp" +#include "CVirtualCameraEvent.hpp" +#include "glm/glm/ext/matrix_transform.hpp" // TODO: TEMPORARY!!! whatever used will be moved to cpp + +// TODO: DIFFERENT NAMESPACE +namespace nbl::hlsl +{ + +template +class ICameraController : virtual public core::IReferenceCounted +{ +public: + using projection_t = typename IProjection; + + class CGimbal : virtual public core::IReferenceCounted + { + public: + CGimbal(const core::smart_refctd_ptr&& projection, const float32_t3& position, const float32_t3& lookat, const float32_t3& upVec = float32_t3(0.0f, 1.0f, 0.0f), const float32_t3& backupUpVec = float32_t3(0.5f, 1.0f, 0.0f)) + : m_projection(std::move(projection)), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_viewMatrix({}), m_isLeftHanded(nbl::hlsl::determinant(m_projection->getProjectionMatrix()) < 0.f) + { + //! I make an assumption that we take ownership of projection (just to make less calculations on gimbal update iterations [but maybe I shouldnt, think of it!]) + recomputeViewMatrix(); + } + + // TODO: ctor with core::path to json config file to load defaults + + const projection_t* getProjection() { return m_projection.get(); } + + // TODO: gimbal methods (to handle VirtualEventType requests) + + private: + inline void recomputeViewMatrix() + { + auto localTarget = glm::normalize(m_target - m_position); + + // If up vector and vector to the target are the same, adjust the up vector + auto up = glm::normalize(m_upVec); + auto cross = glm::cross(localTarget, up); + bool upVectorNeedsChange = glm::dot(cross, cross) == 0; + + if (upVectorNeedsChange) + up = glm::normalize(m_backupUpVec); + + if (m_isLeftHanded) + m_viewMatrix = glm::lookAtLH(m_position, m_target, up); + else + m_viewMatrix = glm::lookAtRH(m_position, m_target, up); + } + + const core::smart_refctd_ptr m_projection; + float32_t3 m_position, m_target, m_upVec, m_backupUpVec; + const float32_t3 m_initialPosition, m_initialTarget; + + float64_t4x4 m_viewMatrix; + const bool m_isLeftHanded; + }; + + ICameraController(core::smart_refctd_ptr&& gimbal) + : m_gimbal(std::move(gimbal)) {} + + void processVirtualEvent(const CVirtualCameraEvent& virtualEvent) + { + // we will treat all manipulation event values as NDC, also for non manipulation events + // we will define how to handle it, all values are encoded onto vec4 (manipulation_value_t) + manipulation_value_t value = virtualEvent.getValue(); + + // TODO: this will use gimbal to handle a virtual event registered by a class (wip) which maps physical keys to virtual events + + case VirtualEventType::MoveForward: + { + // TODO + } break; + + case VirtualEventType::Strafe: + { + // TODO + } break; + + case VirtualEventType::Zoom: + { + // TODO + } break; + + case VirtualEventType::Pan: + { + // TODO + } break; + + case VirtualEventType::Tilt: + { + // TODO + } break; + + case VirtualEventType::Roll: + { + // TODO + } break; + + case VirtualEventType::Reset: + { + // TODO + } break; + + default: + break; + } + +private: + core::smart_refctd_ptr m_gimbal; +}; + +} // nbl::hlsl namespace + +#endif // _NBL_I_CAMERA_CONTROLLER_HPP_ \ No newline at end of file diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp index 3539b153f..30a4db358 100644 --- a/common/include/camera/ILinearProjection.hpp +++ b/common/include/camera/ILinearProjection.hpp @@ -15,7 +15,7 @@ class ILinearProjection : protected IProjectionRange using range_t = typename base_t::range_t; using projection_t = typename base_t::projection_t; - ILinearProjection(range_t&& projections) : base_t(projections) {} + ILinearProjection(range_t&& projections) : base_t(std::move(projections)) {} protected: inline range_t& getViewportProjections() diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index fbd6f43e0..f3edfccc4 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -1,7 +1,7 @@ #ifndef _NBL_IPROJECTION_HPP_ #define _NBL_IPROJECTION_HPP_ -#include +#include "nbl/builtin/hlsl/cpp_compat/matrix.hlsl" namespace nbl::hlsl { @@ -10,8 +10,8 @@ template concept ProjectionMatrix = is_any_of_v; //! Interface class for projection -template -class IProjection +template +class IProjection : virtual public core::IReferenceCounted { public: using value_t = T; @@ -24,14 +24,14 @@ class IProjection }; template -concept ProjectionRange = requires +concept ProjectionRange = requires { typename std::ranges::range_value_t; - std::is_base_of_v::value_t>, std::ranges::range_value_t>; + // TODO: smart_refctd_ptr check for range_value_t + its type check to grant IProjection }; //! Interface class for a range of IProjection projections -template, 1u>> +template>, 1u>> class IProjectionRange { public: @@ -39,7 +39,7 @@ class IProjectionRange using projection_t = std::ranges::range_value_t; //! Constructor for the range of projections - IProjectionRange(const range_t& projections) : m_projectionRange(projections) {} + IProjectionRange(range_t&& projections) : m_projectionRange(std::move(projections)) {} //! Get the stored range of projections const range_t& getProjections() const { return m_projectionRange; } From 80be87d54b6015e0dc1843592824258a7792d87a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 23 Oct 2024 11:53:07 +0200 Subject: [PATCH 05/84] update ICameraControl & CVirtualCameraEvent, add processVirtualEvent --- common/include/camera/CVirtualCameraEvent.hpp | 78 +++++---- common/include/camera/ICameraControl.hpp | 161 +++++++++++++----- 2 files changed, 165 insertions(+), 74 deletions(-) diff --git a/common/include/camera/CVirtualCameraEvent.hpp b/common/include/camera/CVirtualCameraEvent.hpp index b91712321..efc336695 100644 --- a/common/include/camera/CVirtualCameraEvent.hpp +++ b/common/include/camera/CVirtualCameraEvent.hpp @@ -10,52 +10,72 @@ namespace nbl::hlsl //! Virtual camera event representing a manipulation enum class VirtualEventType { - //! Moves the camera forward or backward - MoveForward, - - //! Zooms in or out - Zoom, - - //! Moves the camera left/right and up/down + //! Move the camera in the direction of strafe vector Strafe, - //! Rotates the camera horizontally - Pan, - - //! Rotates the camera vertically - Tilt, + //! Update orientation of camera by rotating around X, Y, Z axes + Rotate, - //! Rolls the camera around the Z axis - Roll, - - //! Resets the camera to a default position and orientation - Reset + //! Signals boolean state, for example "reset" + State }; -//! We encode all manipulation type values into a vec4 & assume they are written in NDC coordinate system with respect to camera -using manipulation_value_t = float64_t4x4; - class CVirtualCameraEvent { public: - CVirtualCameraEvent(VirtualEventType type, const manipulation_value_t value) - : type(type), value(value) {} + using manipulation_encode_t = float32_t4; + + struct StrafeManipulation + { + float32_t3 direction = {}; + float distance = {}; + }; + + struct RotateManipulation + { + float pitch = {}, roll = {}, yaw = {}; + }; + + struct StateManipulation + { + uint32_t reset : 1; + uint32_t reserved : 31; + + StateManipulation() { memset(this, 0, sizeof(StateManipulation)); } + ~StateManipulation() {} + }; + + union ManipulationValue + { + StrafeManipulation strafe; + RotateManipulation rotation; + StateManipulation state; + + ManipulationValue() { memset(this, 0, sizeof(ManipulationValue)); } + ~ManipulationValue() {} + }; + + CVirtualCameraEvent(VirtualEventType type, const ManipulationValue manipulation) + : m_type(type), m_manipulation(manipulation) + { + static_assert(sizeof(manipulation_encode_t) == sizeof(ManipulationValue)); + } - // Returns the type of virtual event + // Returns the type of manipulation value VirtualEventType getType() const { - return type; + return m_type; } - // Returns the associated value for manipulation - manipulation_value_t getValue() const + // Returns manipulation value + ManipulationValue getManipulation() const { - return value; + return m_manipulation; } private: - VirtualEventType type; - manipulation_value_t value; + VirtualEventType m_type; + ManipulationValue m_manipulation; }; } diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index c62871644..b6be74801 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -4,6 +4,7 @@ #include "IProjection.hpp" #include "CVirtualCameraEvent.hpp" #include "glm/glm/ext/matrix_transform.hpp" // TODO: TEMPORARY!!! whatever used will be moved to cpp +#include "glm/glm/gtc/quaternion.hpp" // TODO: DIFFERENT NAMESPACE namespace nbl::hlsl @@ -19,30 +20,98 @@ class ICameraController : virtual public core::IReferenceCounted { public: CGimbal(const core::smart_refctd_ptr&& projection, const float32_t3& position, const float32_t3& lookat, const float32_t3& upVec = float32_t3(0.0f, 1.0f, 0.0f), const float32_t3& backupUpVec = float32_t3(0.5f, 1.0f, 0.0f)) - : m_projection(std::move(projection)), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_viewMatrix({}), m_isLeftHanded(nbl::hlsl::determinant(m_projection->getProjectionMatrix()) < 0.f) + : m_projection(projection), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_orientation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)), m_viewMatrix({}), m_isLeftHanded(isLeftHanded(m_projection->getProjectionMatrix())) { - //! I make an assumption that we take ownership of projection (just to make less calculations on gimbal update iterations [but maybe I shouldnt, think of it!]) recomputeViewMatrix(); } + //! Start a gimbal manipulation session + inline void begin() + { + needsToRecomputeViewMatrix = false; + } + + //! End the gimbal manipulation session, recompute matrices and check projection + inline void end() + { + m_isLeftHanded = isLeftHanded(m_projection->getProjectionMatrix()); + + // Recompute the view matrix + if(needsToRecomputeViewMatrix) + recomputeViewMatrix(); + + needsToRecomputeViewMatrix = false; + } + + inline float32_t3 getLocalTarget() const + { + return m_target - m_position; + } + + inline float32_t3 getForwardDirection() const + { + return glm::normalize(getLocalTarget()); + } + + //! Reset the gimbal to its initial position, target, and orientation + inline void reset() + { + m_position = m_initialPosition; + m_target = m_initialTarget; + m_orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + + recomputeViewMatrix(); // Recompute the view matrix after resetting + } + + //! Move the camera in the direction of strafe (mostly left/right, up/down) + void strafe(const glm::vec3& direction, float distance) + { + if (distance != 0.0f) + { + const auto strafeVector = glm::normalize(direction) * distance; + m_position += strafeVector; + m_target += strafeVector; + + needsToRecomputeViewMatrix = true; + } + } + + //! Update orientation of camera by rotating around all XYZ axes - delta rotations in radians + void rotate(float dPitchRadians, float dYawDeltaRadians, float dRollDeltaRadians) + { + // Rotate around X (pitch) + glm::quat qPitch = glm::angleAxis(dPitchRadians, glm::vec3(1.0f, 0.0f, 0.0f)); + + // Rotate around Y (yaw) + glm::quat qYaw = glm::angleAxis(dYawDeltaRadians, glm::vec3(0.0f, 1.0f, 0.0f)); + + // Rotate around Z (roll) // TODO: handness!! + glm::quat qRoll = glm::angleAxis(dRollDeltaRadians, glm::vec3(0.0f, 0.0f, 1.0f)); + + // Combine the new rotations with the current orientation + m_orientation = glm::normalize(qYaw * qPitch * qRoll * m_orientation); + + // Now we have rotation transformation as 3x3 matrix + auto rotate = glm::mat3_cast(m_orientation); + + // We do not change magnitude of the vector + auto localTargetRotated = rotate * getLocalTarget(); + + // And we can simply update target vector + m_target = m_position + localTargetRotated; + + // TODO: std::any + nice checks for deltas (radians - periodic!) + needsToRecomputeViewMatrix = true; + } + // TODO: ctor with core::path to json config file to load defaults const projection_t* getProjection() { return m_projection.get(); } - // TODO: gimbal methods (to handle VirtualEventType requests) - private: inline void recomputeViewMatrix() { - auto localTarget = glm::normalize(m_target - m_position); - - // If up vector and vector to the target are the same, adjust the up vector - auto up = glm::normalize(m_upVec); - auto cross = glm::cross(localTarget, up); - bool upVectorNeedsChange = glm::dot(cross, cross) == 0; - - if (upVectorNeedsChange) - up = glm::normalize(m_backupUpVec); + auto up = getPatchedUpVector(); if (m_isLeftHanded) m_viewMatrix = glm::lookAtLH(m_position, m_target, up); @@ -50,12 +119,37 @@ class ICameraController : virtual public core::IReferenceCounted m_viewMatrix = glm::lookAtRH(m_position, m_target, up); } + inline float32_t3 getPatchedUpVector() + { + // if up vector and vector to the target are the same we patch the up vector + auto up = glm::normalize(m_upVec); + + const auto localTarget = getForwardDirection(); + const auto cross = glm::cross(localTarget, up); + + // we compute squared length but for checking if the len is 0 it doesnt matter + const bool upVectorZeroLength = glm::dot(cross, cross) == 0.f; + + if (upVectorZeroLength) + up = glm::normalize(m_backupUpVec); + + return up; + } + + inline bool isLeftHanded(const auto& projectionMatrix) + { + return nbl::hlsl::determinant(projectionMatrix) < 0.f; + } + const core::smart_refctd_ptr m_projection; float32_t3 m_position, m_target, m_upVec, m_backupUpVec; const float32_t3 m_initialPosition, m_initialTarget; + glm::quat m_orientation; float64_t4x4 m_viewMatrix; - const bool m_isLeftHanded; + bool m_isLeftHanded; + + bool needsToRecomputeViewMatrix = false; }; ICameraController(core::smart_refctd_ptr&& gimbal) @@ -63,45 +157,22 @@ class ICameraController : virtual public core::IReferenceCounted void processVirtualEvent(const CVirtualCameraEvent& virtualEvent) { - // we will treat all manipulation event values as NDC, also for non manipulation events - // we will define how to handle it, all values are encoded onto vec4 (manipulation_value_t) - manipulation_value_t value = virtualEvent.getValue(); - - // TODO: this will use gimbal to handle a virtual event registered by a class (wip) which maps physical keys to virtual events + const auto manipulation = virtualEvent.getManipulation(); - case VirtualEventType::MoveForward: - { - // TODO - } break; - case VirtualEventType::Strafe: { - // TODO - } break; - - case VirtualEventType::Zoom: - { - // TODO + m_gimbal->strafe(manipulation.strafe.direction, manipulation.strafe.distance); } break; - - case VirtualEventType::Pan: - { - // TODO - } break; - - case VirtualEventType::Tilt: - { - // TODO - } break; - - case VirtualEventType::Roll: + + case VirtualEventType::Rotate: { - // TODO + m_gimbal->rotate(manipulation.rotation.pitch, manipulation.rotation.yaw, manipulation.rotation.roll); } break; - case VirtualEventType::Reset: + case VirtualEventType::State: { - // TODO + if (manipulation.state.reset) + m_gimbal->reset(); } break; default: From 3af8c076396891e658defa953d0ad03341d570bb Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 23 Oct 2024 18:18:17 +0200 Subject: [PATCH 06/84] put CVirtualCameraEvent logic into ICameraController, update the class with abstract manipulate method - expect virtual event & a gimbal. Update CGimbal with new manipulation & set methods, start replacing CCamera content with new gimbal & controller --- common/include/CCamera.hpp | 67 +---- common/include/camera/CVirtualCameraEvent.hpp | 71 ----- common/include/camera/ICameraControl.hpp | 283 ++++++++++++++---- 3 files changed, 241 insertions(+), 180 deletions(-) diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 1b0fe9c0f..54c6477a3 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -11,70 +11,21 @@ #include #include -class Camera +#include "camera/ICameraControl.hpp" + +// FPS Camera, we will have more types soon + +template +class Camera : public ICameraController { public: - Camera() = default; - Camera(const nbl::core::vectorSIMDf& position, const nbl::core::vectorSIMDf& lookat, const nbl::core::matrix4SIMD& projection, float moveSpeed = 1.0f, float rotateSpeed = 1.0f, const nbl::core::vectorSIMDf& upVec = nbl::core::vectorSIMDf(0.0f, 1.0f, 0.0f), const nbl::core::vectorSIMDf& backupUpVec = nbl::core::vectorSIMDf(0.5f, 1.0f, 0.0f)) - : position(position) - , initialPosition(position) - , target(lookat) - , initialTarget(lookat) - , firstUpdate(true) - , moveSpeed(moveSpeed) - , rotateSpeed(rotateSpeed) - , upVector(upVec) - , backupUpVector(backupUpVec) - { - initDefaultKeysMap(); - allKeysUp(); - setProjectionMatrix(projection); - recomputeViewMatrix(); - } + using matrix_t = T; + Camera() = default; ~Camera() = default; - enum E_CAMERA_MOVE_KEYS : uint8_t - { - ECMK_MOVE_FORWARD = 0, - ECMK_MOVE_BACKWARD, - ECMK_MOVE_LEFT, - ECMK_MOVE_RIGHT, - ECMK_COUNT, - }; - - inline void mapKeysToWASD() - { - keysMap[ECMK_MOVE_FORWARD] = nbl::ui::EKC_W; - keysMap[ECMK_MOVE_BACKWARD] = nbl::ui::EKC_S; - keysMap[ECMK_MOVE_LEFT] = nbl::ui::EKC_A; - keysMap[ECMK_MOVE_RIGHT] = nbl::ui::EKC_D; - } - - inline void mapKeysToArrows() - { - keysMap[ECMK_MOVE_FORWARD] = nbl::ui::EKC_UP_ARROW; - keysMap[ECMK_MOVE_BACKWARD] = nbl::ui::EKC_DOWN_ARROW; - keysMap[ECMK_MOVE_LEFT] = nbl::ui::EKC_LEFT_ARROW; - keysMap[ECMK_MOVE_RIGHT] = nbl::ui::EKC_RIGHT_ARROW; - } - - inline void mapKeysCustom(std::array& map) { keysMap = map; } - - inline const nbl::core::matrix4SIMD& getProjectionMatrix() const { return projMatrix; } - inline const nbl::core::matrix3x4SIMD& getViewMatrix() const { return viewMatrix; } - inline const nbl::core::matrix4SIMD& getConcatenatedMatrix() const { return concatMatrix; } - - inline void setProjectionMatrix(const nbl::core::matrix4SIMD& projection) - { - projMatrix = projection; - const auto hlslMatMap = *reinterpret_cast(&projMatrix); // TEMPORARY TILL THE CAMERA CLASS IS REFACTORED TO WORK WITH HLSL MATRICIES! - { - leftHanded = nbl::hlsl::determinant(hlslMatMap) < 0.f; - } - concatMatrix = nbl::core::matrix4SIMD::concatenateBFollowedByAPrecisely(projMatrix, nbl::core::matrix4SIMD(viewMatrix)); - } + inline void setPosition(const nbl::core::vectorSIMDf& pos) { diff --git a/common/include/camera/CVirtualCameraEvent.hpp b/common/include/camera/CVirtualCameraEvent.hpp index efc336695..309c91662 100644 --- a/common/include/camera/CVirtualCameraEvent.hpp +++ b/common/include/camera/CVirtualCameraEvent.hpp @@ -7,77 +7,6 @@ namespace nbl::hlsl { -//! Virtual camera event representing a manipulation -enum class VirtualEventType -{ - //! Move the camera in the direction of strafe vector - Strafe, - - //! Update orientation of camera by rotating around X, Y, Z axes - Rotate, - - //! Signals boolean state, for example "reset" - State -}; - -class CVirtualCameraEvent -{ -public: - using manipulation_encode_t = float32_t4; - - struct StrafeManipulation - { - float32_t3 direction = {}; - float distance = {}; - }; - - struct RotateManipulation - { - float pitch = {}, roll = {}, yaw = {}; - }; - - struct StateManipulation - { - uint32_t reset : 1; - uint32_t reserved : 31; - - StateManipulation() { memset(this, 0, sizeof(StateManipulation)); } - ~StateManipulation() {} - }; - - union ManipulationValue - { - StrafeManipulation strafe; - RotateManipulation rotation; - StateManipulation state; - - ManipulationValue() { memset(this, 0, sizeof(ManipulationValue)); } - ~ManipulationValue() {} - }; - - CVirtualCameraEvent(VirtualEventType type, const ManipulationValue manipulation) - : m_type(type), m_manipulation(manipulation) - { - static_assert(sizeof(manipulation_encode_t) == sizeof(ManipulationValue)); - } - - // Returns the type of manipulation value - VirtualEventType getType() const - { - return m_type; - } - - // Returns manipulation value - ManipulationValue getManipulation() const - { - return m_manipulation; - } - -private: - VirtualEventType m_type; - ManipulationValue m_manipulation; -}; - } #endif // _NBL_VIRTUAL_CAMERA_EVENT_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index b6be74801..0618f90de 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -16,55 +16,227 @@ class ICameraController : virtual public core::IReferenceCounted public: using projection_t = typename IProjection; + enum VirtualEventType : uint8_t + { + // Strafe forward + MoveForward = 0, + + // Strafe backward + MoveBackward, + + // Strafe left + MoveLeft, + + // Strafe right + MoveRight, + + // Strafe up + MoveUp, + + // Strafe down + MoveDown, + + // Tilt the camera upward (pitch) + TiltUp, + + // Tilt the camera downward (pitch) + TiltDown, + + // Rotate the camera left around the vertical axis (yaw) + PanLeft, + + // Rotate the camera right around the vertical axis (yaw) + PanRight, + + // Roll the camera counterclockwise around the forward axis (roll) + RollLeft, + + // Roll the camera clockwise around the forward axis (roll) + RollRight, + + // Reset the camera to the default state + Reset, + + EventsCount + }; + class CGimbal : virtual public core::IReferenceCounted { public: + //! Virtual event representing a manipulation + enum VirtualEventType + { + //! Move the camera in the direction of strafe vector + Strafe, + + //! Update orientation of camera by rotating around X, Y, Z axes + Rotate, + + //! Signals boolean state, for example "reset" + State + }; + + class CVirtualEvent + { + public: + using manipulation_encode_t = float32_t4; + + struct StrafeManipulation + { + float32_t3 direction = {}; + float distance = {}; + }; + + struct RotateManipulation + { + float pitch = {}, roll = {}, yaw = {}; + }; + + struct StateManipulation + { + uint32_t reset : 1; + uint32_t reserved : 31; + + StateManipulation() { memset(this, 0, sizeof(StateManipulation)); } + ~StateManipulation() {} + }; + + union ManipulationValue + { + StrafeManipulation strafe; + RotateManipulation rotation; + StateManipulation state; + + ManipulationValue() { memset(this, 0, sizeof(ManipulationValue)); } + ~ManipulationValue() {} + }; + + CVirtualEvent(VirtualEventType type, const ManipulationValue manipulation) + : m_type(type), m_manipulation(manipulation) + { + static_assert(sizeof(manipulation_encode_t) == sizeof(ManipulationValue)); + } + + // Returns the type of manipulation value + VirtualEventType getType() const + { + return m_type; + } + + // Returns manipulation value + ManipulationValue getManipulation() const + { + return m_manipulation; + } + + private: + VirtualEventType m_type; + ManipulationValue m_manipulation; + }; + CGimbal(const core::smart_refctd_ptr&& projection, const float32_t3& position, const float32_t3& lookat, const float32_t3& upVec = float32_t3(0.0f, 1.0f, 0.0f), const float32_t3& backupUpVec = float32_t3(0.5f, 1.0f, 0.0f)) : m_projection(projection), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_orientation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)), m_viewMatrix({}), m_isLeftHanded(isLeftHanded(m_projection->getProjectionMatrix())) { recomputeViewMatrix(); } + // TODO: ctor with core::path to json config file to load defaults + //! Start a gimbal manipulation session inline void begin() { - needsToRecomputeViewMatrix = false; + m_needsToRecomputeViewMatrix = false; + m_recordingManipulation = true; } - //! End the gimbal manipulation session, recompute matrices and check projection - inline void end() + //! Record manipulation of the gimbal, note those events are delta manipulations + void manipulate(const CVirtualEvent& virtualEvent) { - m_isLeftHanded = isLeftHanded(m_projection->getProjectionMatrix()); + if (!m_recordingManipulation) + return; // TODO: log it - // Recompute the view matrix - if(needsToRecomputeViewMatrix) - recomputeViewMatrix(); + const auto manipulation = virtualEvent.getManipulation(); - needsToRecomputeViewMatrix = false; + case VirtualEventType::Strafe: + { + strafe(manipulation.strafe.direction, manipulation.strafe.distance); + } break; + + case VirtualEventType::Rotate: + { + rotate(manipulation.rotation.pitch, manipulation.rotation.yaw, manipulation.rotation.roll); + } break; + + case VirtualEventType::State: + { + if (manipulation.state.reset) + reset(); + } break; + + default: + break; } - inline float32_t3 getLocalTarget() const + // Record change of position vector, global update + inline void setPosition(const float32_t3& position) { - return m_target - m_position; + if (!m_recordingManipulation) + return; // TODO: log it + + m_position = position; } - inline float32_t3 getForwardDirection() const + // Record change of target vector, global update + inline void setTarget(const float32_t3& target) { - return glm::normalize(getLocalTarget()); + if (!m_recordingManipulation) + return; // TODO: log it + + m_target = target; } + // Change up vector, global update + inline void setUpVector(const float32_t3& up) { m_upVec = up; } + + // Change backupUp vector, global update + inline void setBackupUpVector(const float32_t3& backupUp) { m_backupUpVec = backupUp; } + + //! End the gimbal manipulation session, recompute view matrix if required and update handedness state from projection + inline void end() + { + m_isLeftHanded = isLeftHanded(m_projection->getProjectionMatrix()); + + if (m_needsToRecomputeViewMatrix) + recomputeViewMatrix(); + + m_needsToRecomputeViewMatrix = false; + m_recordingManipulation = false; + } + + inline const float32_t3& getPosition() const { return m_position; } + inline const float32_t3& getTarget() const { return m_target; } + inline const float32_t3& getUpVector() const { return m_upVec; } + inline const float32_t3& getBackupUpVector() const { return m_backupUpVec; } + inline const float32_t3 getLocalTarget() const { return m_target - m_position; } + inline const float32_t3 getForwardDirection() const { return glm::normalize(getLocalTarget()); } + inline const projection_t* getProjection() { return m_projection.get(); } + + // TODO: getConcatenatedMatrix() + // TODO: getViewMatrix() + + private: //! Reset the gimbal to its initial position, target, and orientation inline void reset() { - m_position = m_initialPosition; + m_position = m_initialPosition; m_target = m_initialTarget; m_orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); recomputeViewMatrix(); // Recompute the view matrix after resetting } - //! Move the camera in the direction of strafe (mostly left/right, up/down) - void strafe(const glm::vec3& direction, float distance) + //! Move in the direction of strafe (mostly left/right, up/down) + inline void strafe(const glm::vec3& direction, float distance) { if (distance != 0.0f) { @@ -72,12 +244,12 @@ class ICameraController : virtual public core::IReferenceCounted m_position += strafeVector; m_target += strafeVector; - needsToRecomputeViewMatrix = true; + m_needsToRecomputeViewMatrix = true; } } - //! Update orientation of camera by rotating around all XYZ axes - delta rotations in radians - void rotate(float dPitchRadians, float dYawDeltaRadians, float dRollDeltaRadians) + //! Update orientation by rotating around all XYZ axes - delta rotations in radians + inline void rotate(float dPitchRadians, float dYawDeltaRadians, float dRollDeltaRadians) { // Rotate around X (pitch) glm::quat qPitch = glm::angleAxis(dPitchRadians, glm::vec3(1.0f, 0.0f, 0.0f)); @@ -101,14 +273,9 @@ class ICameraController : virtual public core::IReferenceCounted m_target = m_position + localTargetRotated; // TODO: std::any + nice checks for deltas (radians - periodic!) - needsToRecomputeViewMatrix = true; + m_needsToRecomputeViewMatrix = true; } - // TODO: ctor with core::path to json config file to load defaults - - const projection_t* getProjection() { return m_projection.get(); } - - private: inline void recomputeViewMatrix() { auto up = getPatchedUpVector(); @@ -138,49 +305,63 @@ class ICameraController : virtual public core::IReferenceCounted inline bool isLeftHanded(const auto& projectionMatrix) { - return nbl::hlsl::determinant(projectionMatrix) < 0.f; + return hlsl::determinant(projectionMatrix) < 0.f; } - const core::smart_refctd_ptr m_projection; + core::smart_refctd_ptr m_projection; float32_t3 m_position, m_target, m_upVec, m_backupUpVec; const float32_t3 m_initialPosition, m_initialTarget; glm::quat m_orientation; float64_t4x4 m_viewMatrix; - bool m_isLeftHanded; - bool needsToRecomputeViewMatrix = false; + bool m_isLeftHanded, + m_needsToRecomputeViewMatrix = false, + m_recordingManipulation = false; }; - ICameraController(core::smart_refctd_ptr&& gimbal) - : m_gimbal(std::move(gimbal)) {} + ICameraController() : {} - void processVirtualEvent(const CVirtualCameraEvent& virtualEvent) + // controller overrides how a manipulation is done for a given camera event with a gimbal + virtual void manipulate(CGimbal* gimbal, VirtualEventType event) = 0; + + // controller can override default set of event map + virtual void initKeysToEvent() { - const auto manipulation = virtualEvent.getManipulation(); + m_keysToEvent[MoveForward] = { ui::E_KEY_CODE::EKC_W }; + m_keysToEvent[MoveBackward] = { ui::E_KEY_CODE::EKC_S }; + m_keysToEvent[MoveLeft] = { ui::E_KEY_CODE::EKC_A }; + m_keysToEvent[MoveRight] = { ui::E_KEY_CODE::EKC_D }; + m_keysToEvent[MoveUp] = { ui::E_KEY_CODE::EKC_SPACE }; + m_keysToEvent[MoveDown] = { ui::E_KEY_CODE::EKC_LEFT_SHIFT }; + m_keysToEvent[TiltUp] = { ui::E_KEY_CODE::EKC_NONE }; + m_keysToEvent[TiltDown] = { ui::E_KEY_CODE::EKC_NONE }; + m_keysToEvent[PanLeft] = { ui::E_KEY_CODE::EKC_NONE }; + m_keysToEvent[PanRight] = { ui::E_KEY_CODE::EKC_NONE }; + m_keysToEvent[RollLeft] = { ui::E_KEY_CODE::EKC_NONE }; + m_keysToEvent[RollRight] = { ui::E_KEY_CODE::EKC_NONE }; + m_keysToEvent[Reset] = { ui::E_KEY_CODE::EKC_R }; + } - case VirtualEventType::Strafe: - { - m_gimbal->strafe(manipulation.strafe.direction, manipulation.strafe.distance); - } break; - - case VirtualEventType::Rotate: - { - m_gimbal->rotate(manipulation.rotation.pitch, manipulation.rotation.yaw, manipulation.rotation.roll); - } break; + // controller can override which keys correspond to which event + void updateKeysToEvent(const std::vector& codes, VirtualEventType event) + { + m_keysToEvent[event] = std::move(codes); + } - case VirtualEventType::State: - { - if (manipulation.state.reset) - m_gimbal->reset(); - } break; +protected: + std::array, EventsCount> m_keysToEvent = {}; - default: - break; - } + // speed factors + float m_moveSpeed = 1.f, m_rotateSpeed = 1.f; + + // states signaling if keys are pressed down or not + bool m_keysDown[EventsCount] = {}; + + // durations for which the key was being held down from lastVirtualUpTimeStamp(=last "guessed" presentation time) to nextPresentationTimeStamp + double m_perActionDt[EventsCount] = {}; -private: - core::smart_refctd_ptr m_gimbal; + std::chrono::microseconds nextPresentationTimeStamp, lastVirtualUpTimeStamp; }; } // nbl::hlsl namespace From fdd47a7518f460df596ea407ad62e4db25e44155 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 23 Oct 2024 18:22:20 +0200 Subject: [PATCH 07/84] add more getters & setters to ICameraController, remove some content from CCamera, mark TODOs for tomorrow --- common/include/CCamera.hpp | 74 +----------------------- common/include/camera/ICameraControl.hpp | 7 +++ 2 files changed, 10 insertions(+), 71 deletions(-) diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 54c6477a3..b2a56cead 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -24,62 +24,9 @@ class Camera : public ICameraController Camera() = default; ~Camera() = default; - - - - inline void setPosition(const nbl::core::vectorSIMDf& pos) - { - position.set(pos); - recomputeViewMatrix(); - } - - inline const nbl::core::vectorSIMDf& getPosition() const { return position; } - - inline void setTarget(const nbl::core::vectorSIMDf& pos) - { - target.set(pos); - recomputeViewMatrix(); - } - - inline const nbl::core::vectorSIMDf& getTarget() const { return target; } - - inline void setUpVector(const nbl::core::vectorSIMDf& up) { upVector = up; } - - inline void setBackupUpVector(const nbl::core::vectorSIMDf& up) { backupUpVector = up; } - - inline const nbl::core::vectorSIMDf& getUpVector() const { return upVector; } - - inline const nbl::core::vectorSIMDf& getBackupUpVector() const { return backupUpVector; } - - inline const float getMoveSpeed() const { return moveSpeed; } - - inline void setMoveSpeed(const float _moveSpeed) { moveSpeed = _moveSpeed; } - - inline const float getRotateSpeed() const { return rotateSpeed; } - - inline void setRotateSpeed(const float _rotateSpeed) { rotateSpeed = _rotateSpeed; } - - inline void recomputeViewMatrix() - { - nbl::core::vectorSIMDf pos = position; - nbl::core::vectorSIMDf localTarget = nbl::core::normalize(target - pos); - - // if upvector and vector to the target are the same, we have a - // problem. so solve this problem: - nbl::core::vectorSIMDf up = nbl::core::normalize(upVector); - nbl::core::vectorSIMDf cross = nbl::core::cross(localTarget, up); - bool upVectorNeedsChange = nbl::core::lengthsquared(cross)[0] == 0; - if (upVectorNeedsChange) - up = nbl::core::normalize(backupUpVector); - - if (leftHanded) - viewMatrix = nbl::core::matrix3x4SIMD::buildCameraLookAtMatrixLH(pos, target, up); - else - viewMatrix = nbl::core::matrix3x4SIMD::buildCameraLookAtMatrixRH(pos, target, up); - concatMatrix = nbl::core::matrix4SIMD::concatenateBFollowedByAPrecisely(projMatrix, nbl::core::matrix4SIMD(viewMatrix)); - } - - inline bool getLeftHanded() const { return leftHanded; } + /* + TODO: controller + gimbal to do all of this -> override virtual manipulate method + */ public: @@ -257,21 +204,6 @@ class Camera : public ICameraController mouseDown = false; } - -private: - nbl::core::vectorSIMDf initialPosition, initialTarget, position, target, upVector, backupUpVector; // TODO: make first 2 const + add default copy constructor - nbl::core::matrix3x4SIMD viewMatrix; - nbl::core::matrix4SIMD concatMatrix, projMatrix; - - float moveSpeed, rotateSpeed; - bool leftHanded, firstUpdate = true, mouseDown = false; - - std::array keysMap = { {nbl::ui::EKC_NONE} }; // map camera E_CAMERA_MOVE_KEYS to corresponding Nabla key codes, by default camera uses WSAD to move - // TODO: make them use std::array - bool keysDown[E_CAMERA_MOVE_KEYS::ECMK_COUNT] = {}; - double perActionDt[E_CAMERA_MOVE_KEYS::ECMK_COUNT] = {}; // durations for which the key was being held down from lastVirtualUpTimeStamp(=last "guessed" presentation time) to nextPresentationTimeStamp - - std::chrono::microseconds nextPresentationTimeStamp, lastVirtualUpTimeStamp; }; #endif // _CAMERA_IMPL_ \ No newline at end of file diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 0618f90de..9dfe758f1 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -350,6 +350,13 @@ class ICameraController : virtual public core::IReferenceCounted } protected: + + inline void setMoveSpeed(const float moveSpeed) { moveSpeed = m_moveSpeed; } + inline void setRotateSpeed(const float rotateSpeed) { rotateSpeed = m_rotateSpeed; } + + inline const float getMoveSpeed() const { return m_moveSpeed; } + inline const float getRotateSpeed() const { return m_rotateSpeed; } + std::array, EventsCount> m_keysToEvent = {}; // speed factors From 3ed3727285f8b7411c92008c064c58ed94e8903a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 24 Oct 2024 20:24:01 +0200 Subject: [PATCH 08/84] save work, make classes compile (but runtime crashes), mark TODOs for tomorrow - make CCamera use gimbal & treat as FPS controller --- 61_UI/main.cpp | 159 +++++++------ common/include/CCamera.hpp | 273 +++++++++-------------- common/include/CGeomtryCreatorScene.hpp | 2 +- common/include/camera/ICameraControl.hpp | 271 ++++++++++++++-------- 4 files changed, 372 insertions(+), 333 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 37e50805c..374719bab 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -8,6 +8,11 @@ #include "camera/ICameraControl.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING +// FPS Camera, TESTS +using camera_t = Camera; +using gimbal_t = camera_t::CGimbal; +using projection_t = camera_t::base_t::projection_t; + /* Renders scene texture to an offline framebuffer which color attachment @@ -170,28 +175,32 @@ class UISampleApp final : public examples::SimpleWindowedApplication pass.ui.manager->registerListener([this]() -> void { ImGuiIO& io = ImGui::GetIO(); - - camera.setProjectionMatrix([&]() { - static matrix4SIMD projection; + auto& projection = gimbal->getProjection()->getProjectionMatrix(); + + + // TODO CASTS + + /* if (isPerspective) - if(isLH) - projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovLH(core::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + { + if (isLH) + projection = glm::perspectiveLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); else - projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovRH(core::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + projection = glm::perspectiveRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + } else { float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; - if(isLH) - projection = matrix4SIMD::buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar); + if (isLH) + projection = glm::orthoLH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar); else - projection = matrix4SIMD::buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar); + projection = glm::orthoRH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar); } - - return projection; - }()); + */ + } ImGuizmo::SetOrthographic(false); ImGuizmo::BeginFrame(); @@ -250,15 +259,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (viewDirty || firstFrame) { - core::vectorSIMDf cameraPosition(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); - core::vectorSIMDf cameraTarget(0.f, 0.f, 0.f); - const static core::vectorSIMDf up(0.f, 1.f, 0.f); + float32_t3 cameraPosition(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); + float32_t3 cameraTarget(0.f, 0.f, 0.f); + const static float32_t3 up(0.f, 1.f, 0.f); - camera.setPosition(cameraPosition); - camera.setTarget(cameraTarget); - camera.setBackupUpVector(up); - - camera.recomputeViewMatrix(); + gimbal->begin(); + { + gimbal->setPosition(cameraPosition); + gimbal->setTarget(cameraTarget); + gimbal->setBackupUpVector(up); + } + gimbal->end(); firstFrame = false; } @@ -326,35 +337,42 @@ class UISampleApp final : public examples::SimpleWindowedApplication static struct { - core::matrix4SIMD view, projection, model; + float32_t4x4 view, projection, model; } imguizmoM16InOut; ImGuizmo::SetID(0u); - imguizmoM16InOut.view = core::transpose(matrix4SIMD(camera.getViewMatrix())); - imguizmoM16InOut.projection = core::transpose(camera.getProjectionMatrix()); - imguizmoM16InOut.model = core::transpose(core::matrix4SIMD(pass.scene->object.model)); + imguizmoM16InOut.view = transpose(gimbal->getViewMatrix()); + imguizmoM16InOut.projection = transpose(gimbal->getProjection()->getProjectionMatrix()); + imguizmoM16InOut.model = transpose(pass.scene->object.model); { if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ transformParams.editTransformDecomposition = true; - EditTransform(imguizmoM16InOut.view.pointer(), imguizmoM16InOut.projection.pointer(), imguizmoM16InOut.model.pointer(), transformParams); + EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); } + // to Nabla + update camera & model matrices - const auto& view = camera.getViewMatrix(); - const auto& projection = camera.getProjectionMatrix(); + const auto& view = gimbal->getViewMatrix(); + const auto& projection = gimbal->getProjection()->getProjectionMatrix(); + + + /* + + TODO!!! + // TODO: make it more nicely - const_cast(view) = core::transpose(imguizmoM16InOut.view).extractSub3x4(); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) + const_cast(view) = transpose(imguizmoM16InOut.view); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) camera.setProjectionMatrix(projection); // update concatanated matrix { static nbl::core::matrix3x4SIMD modelView, normal; static nbl::core::matrix4SIMD modelViewProjection; auto& hook = pass.scene->object; - hook.model = core::transpose(imguizmoM16InOut.model).extractSub3x4(); + hook.model = core::transpose(imguizmoM16InOut.model); { const auto& references = pass.scene->getResources().objects; const auto type = static_cast(gcIndex); @@ -382,6 +400,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } + // view matrices editor { ImGui::Begin("Matrices"); @@ -413,6 +432,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } + */ // Nabla Imgui backend MDI buffer info // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, @@ -491,27 +511,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication m_surface->recreateSwapchain(); m_winMgr->show(m_window.get()); oracle.reportBeginFrameRecord(); - camera.mapKeysToArrows(); /* TESTS, TODO: remove all once finished work & integrate with the example properly */ - using cube_projection_t = CCubeProjection; - using constraints_t = CCubeProjection<>::constraints_t; - using camera_control_t = ICameraController; - using gimbal_t = camera_control_t::CGimbal; - - cube_projection_t cubeProjection; // can init all at construction, but will init only first for tests - auto& projections = cubeProjection.getCubeFaceProjections(); - auto firstFaceProjection = projections.front(); - firstFaceProjection = make_smart_refctd_ptr(glm::perspectiveLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); - const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance), target(0.f, 0.f, 0.f), up(0.f, 1.f, 0.f); - auto gimbal = make_smart_refctd_ptr(smart_refctd_ptr(firstFaceProjection), position, target, up); - auto controller = make_smart_refctd_ptr(smart_refctd_ptr(gimbal)); + auto pMatrix = glm::perspectiveLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar); + auto projection = make_smart_refctd_ptr(); // TODO: CASTS FOR PROJ + gimbal = make_smart_refctd_ptr(smart_refctd_ptr(projection), position, target, up); + camera = make_smart_refctd_ptr(smart_refctd_ptr(gimbal)); // note we still have shared ownership, TESTS return true; } @@ -690,8 +701,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication inline void update() { - camera.setMoveSpeed(moveSpeed); - camera.setRotateSpeed(rotateSpeed); + camera->setMoveSpeed(moveSpeed); + camera->setRotateSpeed(rotateSpeed); static std::chrono::microseconds previousEventTimestamp{}; @@ -716,47 +727,40 @@ class UISampleApp final : public examples::SimpleWindowedApplication std::vector mouse{}; std::vector keyboard{}; } capturedEvents; + + if (move) + camera->begin(nextPresentationTimestamp); - if (move) camera.beginInputProcessing(nextPresentationTimestamp); + mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void { - mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void + for (const auto& e : events) // here capture { - if (move) - camera.mouseProcess(events); // don't capture the events, only let camera handle them with its impl + if (e.timeStamp < previousEventTimestamp) + continue; - for (const auto& e : events) // here capture - { - if (e.timeStamp < previousEventTimestamp) - continue; - - previousEventTimestamp = e.timeStamp; - capturedEvents.mouse.emplace_back(e); + previousEventTimestamp = e.timeStamp; + capturedEvents.mouse.emplace_back(e); - if (e.type == nbl::ui::SMouseEvent::EET_SCROLL) - gcIndex = std::clamp(int16_t(gcIndex) + int16_t(core::sign(e.scrollEvent.verticalScroll)), int64_t(0), int64_t(OT_COUNT - (uint8_t)1u)); - } - }, m_logger.get()); + if (e.type == nbl::ui::SMouseEvent::EET_SCROLL) + gcIndex = std::clamp(int16_t(gcIndex) + int16_t(core::sign(e.scrollEvent.verticalScroll)), int64_t(0), int64_t(OT_COUNT - (uint8_t)1u)); + } + }, m_logger.get()); keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void + { + for (const auto& e : events) // here capture { - if (move) - camera.keyboardProcess(events); // don't capture the events, only let camera handle them with its impl - - for (const auto& e : events) // here capture - { - if (e.timeStamp < previousEventTimestamp) - continue; + if (e.timeStamp < previousEventTimestamp) + continue; - previousEventTimestamp = e.timeStamp; - capturedEvents.keyboard.emplace_back(e); - } - }, m_logger.get()); - } - if (move) camera.endInputProcessing(nextPresentationTimestamp); + previousEventTimestamp = e.timeStamp; + capturedEvents.keyboard.emplace_back(e); + } + }, m_logger.get()); const auto cursorPosition = m_window->getCursorControl()->getPosition(); - nbl::ext::imgui::UI::SUpdateParameters params = + nbl::ext::imgui::UI::SUpdateParameters params = { .mousePosition = nbl::hlsl::float32_t2(cursorPosition.x, cursorPosition.y) - nbl::hlsl::float32_t2(m_window->getX(), m_window->getY()), .displaySize = { m_window->getWidth(), m_window->getHeight() }, @@ -764,6 +768,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication .keyboardEvents = { capturedEvents.keyboard.data(), capturedEvents.keyboard.size() } }; + if (move) + { + camera->manipulate({ .mouseEvents = params.mouseEvents, .keyboardEvents = params.keyboardEvents, }); + camera->end(nextPresentationTimestamp); + } + pass.ui.manager->update(params); } @@ -805,7 +815,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication C_UI ui; } pass; - Camera camera = Camera(core::vectorSIMDf(0, 0, 0), core::vectorSIMDf(0, 0, 0), core::matrix4SIMD()); + core::smart_refctd_ptr gimbal; + core::smart_refctd_ptr camera; video::CDumbPresentationOracle oracle; uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index b2a56cead..401c5aa4e 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -15,195 +15,132 @@ // FPS Camera, we will have more types soon +namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE +{ + template class Camera : public ICameraController { public: - using matrix_t = T; - - Camera() = default; + using matrix_t = typename T; + using base_t = typename ICameraController; + using gimbal_t = typename base_t::CGimbal; + using gimbal_virtual_event_t = typename gimbal_t::CVirtualEvent; + using controller_virtual_event_t = typename base_t::CVirtualEvent; + + Camera(core::smart_refctd_ptr&& gimbal) + : base_t(core::smart_refctd_ptr(gimbal)) {} ~Camera() = default; - /* - TODO: controller + gimbal to do all of this -> override virtual manipulate method - */ - public: - void mouseProcess(const nbl::ui::IMouseEventChannel::range_t& events) + void manipulate(base_t::SUpdateParameters parameters) { - for (auto eventIt=events.begin(); eventIt!=events.end(); eventIt++) + auto* gimbal = base_t::m_gimbal.get(); + + auto process = [&](const std::vector& virtualEvents) -> void { - auto ev = *eventIt; + const auto forward = gimbal->getForwardDirection(); + const auto up = gimbal->getPatchedUpVector(); + const bool leftHanded = gimbal->isLeftHanded(); - if(ev.type == nbl::ui::SMouseEvent::EET_CLICK && ev.clickEvent.mouseButton == nbl::ui::EMB_LEFT_BUTTON) - if(ev.clickEvent.action == nbl::ui::SMouseEvent::SClickEvent::EA_PRESSED) - mouseDown = true; - else if (ev.clickEvent.action == nbl::ui::SMouseEvent::SClickEvent::EA_RELEASED) - mouseDown = false; + // strafe vector we move along when requesting left/right movements + const auto strafeLeftRight = leftHanded ? glm::normalize(glm::cross(forward, up)) : glm::normalize(glm::cross(up, forward)); - if(ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT && mouseDown) - { - nbl::core::vectorSIMDf pos = getPosition(); - nbl::core::vectorSIMDf localTarget = getTarget() - pos; - - // Get Relative Rotation for localTarget in Radians - float relativeRotationX, relativeRotationY; - relativeRotationY = atan2(localTarget.X, localTarget.Z); - const double z1 = nbl::core::sqrt(localTarget.X*localTarget.X + localTarget.Z*localTarget.Z); - relativeRotationX = atan2(z1, localTarget.Y) - nbl::core::PI()/2; - - constexpr float RotateSpeedScale = 0.003f; - relativeRotationX -= ev.movementEvent.relativeMovementY * rotateSpeed * RotateSpeedScale * -1.0f; - float tmpYRot = ev.movementEvent.relativeMovementX * rotateSpeed * RotateSpeedScale * -1.0f; - - if (leftHanded) - relativeRotationY -= tmpYRot; - else - relativeRotationY += tmpYRot; - - const double MaxVerticalAngle = nbl::core::radians(88.0f); - - if (relativeRotationX > MaxVerticalAngle*2 && relativeRotationX < 2 * nbl::core::PI()-MaxVerticalAngle) - relativeRotationX = 2 * nbl::core::PI()-MaxVerticalAngle; - else - if (relativeRotationX > MaxVerticalAngle && relativeRotationX < 2 * nbl::core::PI()-MaxVerticalAngle) - relativeRotationX = MaxVerticalAngle; - - localTarget.set(0,0, nbl::core::max(1.f, nbl::core::length(pos)[0]), 1.f); - - nbl::core::matrix3x4SIMD mat; - mat.setRotation(nbl::core::quaternion(relativeRotationX, relativeRotationY, 0)); - mat.transformVect(localTarget); - - setTarget(localTarget + pos); - } - } - } + constexpr auto MoveSpeedScale = 0.003f; + constexpr auto RotateSpeedScale = 0.003f; - void keyboardProcess(const nbl::ui::IKeyboardEventChannel::range_t& events) - { - for(uint32_t k = 0; k < E_CAMERA_MOVE_KEYS::ECMK_COUNT; ++k) - perActionDt[k] = 0.0; + const auto dMoveFactor = base_t::m_moveSpeed * MoveSpeedScale; + const auto dRotateFactor = base_t::m_rotateSpeed * RotateSpeedScale; - /* - * If a Key was already being held down from previous frames - * Compute with this assumption that the key will be held down for this whole frame as well, - * And If an UP event was sent It will get subtracted it from this value. (Currently Disabled Because we Need better Oracle) - */ + // TODO: UB/LB for pitch [-88,88]!!! we are not in cosmos but handle FPS camera - for(uint32_t k = 0; k < E_CAMERA_MOVE_KEYS::ECMK_COUNT; ++k) - if(keysDown[k]) + for (const controller_virtual_event_t& ev : virtualEvents) { - auto timeDiff = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); - assert(timeDiff >= 0); - perActionDt[k] += timeDiff; + const auto dMoveValue = ev.value * dMoveFactor; + const auto dRotateValue = ev.value * dRotateFactor; + + gimbal_virtual_event_t gimbalEvent; + + switch (ev.type) + { + case base_t::MoveForward: + { + gimbalEvent.type = gimbal_t::Strafe; + gimbalEvent.manipulation.strafe.direction = forward; + gimbalEvent.manipulation.strafe.distance = dMoveValue; + } break; + + case base_t::MoveBackward: + { + gimbalEvent.type = gimbal_t::Strafe; + gimbalEvent.manipulation.strafe.direction = -forward; + gimbalEvent.manipulation.strafe.distance = dMoveValue; + } break; + + case base_t::MoveLeft: + { + gimbalEvent.type = gimbal_t::Strafe; + gimbalEvent.manipulation.strafe.direction = -strafeLeftRight; + gimbalEvent.manipulation.strafe.distance = dMoveValue; + } break; + + case base_t::MoveRight: + { + gimbalEvent.type = gimbal_t::Strafe; + gimbalEvent.manipulation.strafe.direction = strafeLeftRight; + gimbalEvent.manipulation.strafe.distance = dMoveValue; + } break; + + case base_t::TiltUp: + { + gimbalEvent.type = gimbal_t::Rotate; + gimbalEvent.manipulation.rotation.pitch = dRotateValue; + gimbalEvent.manipulation.rotation.roll = 0.0f; + gimbalEvent.manipulation.rotation.yaw = 0.0f; + } break; + + case base_t::TiltDown: + { + gimbalEvent.type = gimbal_t::Rotate; + gimbalEvent.manipulation.rotation.pitch = -dRotateValue; + gimbalEvent.manipulation.rotation.roll = 0.0f; + gimbalEvent.manipulation.rotation.yaw = 0.0f; + } break; + + case base_t::PanLeft: + { + gimbalEvent.type = gimbal_t::Rotate; + gimbalEvent.manipulation.rotation.pitch = 0.0f; + gimbalEvent.manipulation.rotation.roll = 0.0f; + gimbalEvent.manipulation.rotation.yaw = -dRotateValue; + } break; + + case base_t::PanRight: + { + gimbalEvent.type = gimbal_t::Rotate; + gimbalEvent.manipulation.rotation.pitch = 0.0f; + gimbalEvent.manipulation.rotation.roll = 0.0f; + gimbalEvent.manipulation.rotation.yaw = dRotateValue; + } break; + + default: + continue; + } + + gimbal->manipulate(gimbalEvent); } + }; - for (auto eventIt=events.begin(); eventIt!=events.end(); eventIt++) + gimbal->begin(); { - const auto ev = *eventIt; - - // accumulate the periods for which a key was down - const auto timeDiff = std::chrono::duration_cast(nextPresentationTimeStamp - ev.timeStamp).count(); - assert(timeDiff >= 0); - - // handle camera movement - for (const auto logicalKey : { ECMK_MOVE_FORWARD, ECMK_MOVE_BACKWARD, ECMK_MOVE_LEFT, ECMK_MOVE_RIGHT }) - { - const auto code = keysMap[logicalKey]; - - if (ev.keyCode == code) - { - if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !keysDown[logicalKey]) - { - perActionDt[logicalKey] += timeDiff; - keysDown[logicalKey] = true; - } - else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - { - // perActionDt[logicalKey] -= timeDiff; - keysDown[logicalKey] = false; - } - } - } - - // handle reset to default state - if (ev.keyCode == nbl::ui::EKC_HOME) - if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - { - position = initialPosition; - target = initialTarget; - recomputeViewMatrix(); - } + process(base_t::processMouse(parameters.mouseEvents)); + process(base_t::processKeyboard(parameters.keyboardEvents)); } - } - - void beginInputProcessing(std::chrono::microseconds _nextPresentationTimeStamp) - { - nextPresentationTimeStamp = _nextPresentationTimeStamp; - return; - } - - void endInputProcessing(std::chrono::microseconds _nextPresentationTimeStamp) - { - nbl::core::vectorSIMDf pos = getPosition(); - nbl::core::vectorSIMDf localTarget = getTarget() - pos; - - if (!firstUpdate) - { - nbl::core::vectorSIMDf movedir = localTarget; - movedir.makeSafe3D(); - movedir = nbl::core::normalize(movedir); - - constexpr float MoveSpeedScale = 0.02f; - - pos += movedir * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_FORWARD] * moveSpeed * MoveSpeedScale; - pos -= movedir * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_BACKWARD] * moveSpeed * MoveSpeedScale; - - // strafing - - // if upvector and vector to the target are the same, we have a - // problem. so solve this problem: - nbl::core::vectorSIMDf up = nbl::core::normalize(upVector); - nbl::core::vectorSIMDf cross = nbl::core::cross(localTarget, up); - bool upVectorNeedsChange = nbl::core::lengthsquared(cross)[0] == 0; - if (upVectorNeedsChange) - { - up = nbl::core::normalize(backupUpVector); - } - - nbl::core::vectorSIMDf strafevect = localTarget; - if (leftHanded) - strafevect = nbl::core::cross(strafevect, up); - else - strafevect = nbl::core::cross(up, strafevect); - - strafevect = nbl::core::normalize(strafevect); - - pos += strafevect * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_LEFT] * moveSpeed * MoveSpeedScale; - pos -= strafevect * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_RIGHT] * moveSpeed * MoveSpeedScale; - } - else - firstUpdate = false; - - setPosition(pos); - setTarget(localTarget+pos); - - lastVirtualUpTimeStamp = nextPresentationTimeStamp; - } - -private: - - inline void initDefaultKeysMap() { mapKeysToWASD(); } - - inline void allKeysUp() - { - for (uint32_t i=0; i< E_CAMERA_MOVE_KEYS::ECMK_COUNT; ++i) - keysDown[i] = false; - - mouseDown = false; + gimbal->end(); } }; +} + #endif // _CAMERA_IMPL_ \ No newline at end of file diff --git a/common/include/CGeomtryCreatorScene.hpp b/common/include/CGeomtryCreatorScene.hpp index ab90f59b9..547c0aaf7 100644 --- a/common/include/CGeomtryCreatorScene.hpp +++ b/common/include/CGeomtryCreatorScene.hpp @@ -1098,7 +1098,7 @@ class ResourceBuilder struct ObjectDrawHookCpu { - nbl::core::matrix3x4SIMD model; + nbl::hlsl::float32_t4x4 model; nbl::asset::SBasicViewParameters viewParameters; ObjectMeta meta; }; diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 9dfe758f1..c52558438 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -60,10 +60,19 @@ class ICameraController : virtual public core::IReferenceCounted EventsCount }; + //! Virtual event representing a manipulation + struct CVirtualEvent + { + using manipulation_encode_t = float64_t; + + VirtualEventType type; + manipulation_encode_t value; + }; + class CGimbal : virtual public core::IReferenceCounted { public: - //! Virtual event representing a manipulation + //! Virtual event representing a combined gimbal manipulation enum VirtualEventType { //! Move the camera in the direction of strafe vector @@ -111,31 +120,20 @@ class ICameraController : virtual public core::IReferenceCounted ~ManipulationValue() {} }; - CVirtualEvent(VirtualEventType type, const ManipulationValue manipulation) - : m_type(type), m_manipulation(manipulation) - { - static_assert(sizeof(manipulation_encode_t) == sizeof(ManipulationValue)); - } - - // Returns the type of manipulation value - VirtualEventType getType() const - { - return m_type; - } + CVirtualEvent() {} - // Returns manipulation value - ManipulationValue getManipulation() const + CVirtualEvent(VirtualEventType _type, const ManipulationValue _manipulation) + : type(_type), manipulation(_manipulation) { - return m_manipulation; + static_assert(sizeof(manipulation_encode_t) == sizeof(ManipulationValue)); } - private: - VirtualEventType m_type; - ManipulationValue m_manipulation; + VirtualEventType type; + ManipulationValue manipulation; }; CGimbal(const core::smart_refctd_ptr&& projection, const float32_t3& position, const float32_t3& lookat, const float32_t3& upVec = float32_t3(0.0f, 1.0f, 0.0f), const float32_t3& backupUpVec = float32_t3(0.5f, 1.0f, 0.0f)) - : m_projection(projection), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_orientation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)), m_viewMatrix({}), m_isLeftHanded(isLeftHanded(m_projection->getProjectionMatrix())) + : m_projection(projection), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_orientation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)), m_viewMatrix({}), m_isLeftHanded(checkIfLeftHanded(m_projection->getProjectionMatrix())) { recomputeViewMatrix(); } @@ -155,26 +153,29 @@ class ICameraController : virtual public core::IReferenceCounted if (!m_recordingManipulation) return; // TODO: log it - const auto manipulation = virtualEvent.getManipulation(); + const auto& manipulation = virtualEvent.manipulation; - case VirtualEventType::Strafe: + switch (virtualEvent.type) { - strafe(manipulation.strafe.direction, manipulation.strafe.distance); - } break; - - case VirtualEventType::Rotate: - { - rotate(manipulation.rotation.pitch, manipulation.rotation.yaw, manipulation.rotation.roll); - } break; - - case VirtualEventType::State: - { - if (manipulation.state.reset) - reset(); - } break; - - default: - break; + case VirtualEventType::Strafe: + { + strafe(manipulation.strafe.direction, manipulation.strafe.distance); + } break; + + case VirtualEventType::Rotate: + { + rotate(manipulation.rotation.pitch, manipulation.rotation.yaw, manipulation.rotation.roll); + } break; + + case VirtualEventType::State: + { + if (manipulation.state.reset) + reset(); + } break; + + default: + break; + } } // Record change of position vector, global update @@ -204,7 +205,7 @@ class ICameraController : virtual public core::IReferenceCounted //! End the gimbal manipulation session, recompute view matrix if required and update handedness state from projection inline void end() { - m_isLeftHanded = isLeftHanded(m_projection->getProjectionMatrix()); + m_isLeftHanded = checkIfLeftHanded(m_projection->getProjectionMatrix()); if (m_needsToRecomputeViewMatrix) recomputeViewMatrix(); @@ -213,13 +214,32 @@ class ICameraController : virtual public core::IReferenceCounted m_recordingManipulation = false; } + inline projection_t* getProjection() { return m_projection.get(); } inline const float32_t3& getPosition() const { return m_position; } inline const float32_t3& getTarget() const { return m_target; } inline const float32_t3& getUpVector() const { return m_upVec; } inline const float32_t3& getBackupUpVector() const { return m_backupUpVec; } inline const float32_t3 getLocalTarget() const { return m_target - m_position; } inline const float32_t3 getForwardDirection() const { return glm::normalize(getLocalTarget()); } - inline const projection_t* getProjection() { return m_projection.get(); } + inline const float32_t4x4& getViewMatrix() const { return m_viewMatrix; } + inline bool isLeftHanded() { return m_isLeftHanded; } + + inline float32_t3 getPatchedUpVector() + { + // if up vector and vector to the target are the same we patch the up vector + auto up = glm::normalize(m_upVec); + + const auto localTarget = getForwardDirection(); + const auto cross = glm::cross(localTarget, up); + + // we compute squared length but for checking if the len is 0 it doesnt matter + const bool upVectorZeroLength = glm::dot(cross, cross) == 0.f; + + if (upVectorZeroLength) + up = glm::normalize(m_backupUpVec); + + return up; + } // TODO: getConcatenatedMatrix() // TODO: getViewMatrix() @@ -257,7 +277,7 @@ class ICameraController : virtual public core::IReferenceCounted // Rotate around Y (yaw) glm::quat qYaw = glm::angleAxis(dYawDeltaRadians, glm::vec3(0.0f, 1.0f, 0.0f)); - // Rotate around Z (roll) // TODO: handness!! + // Rotate around Z (roll) glm::quat qRoll = glm::angleAxis(dRollDeltaRadians, glm::vec3(0.0f, 0.0f, 1.0f)); // Combine the new rotations with the current orientation @@ -280,30 +300,17 @@ class ICameraController : virtual public core::IReferenceCounted { auto up = getPatchedUpVector(); + // TODO!!!! CASTS + + /* if (m_isLeftHanded) m_viewMatrix = glm::lookAtLH(m_position, m_target, up); else m_viewMatrix = glm::lookAtRH(m_position, m_target, up); + */ } - inline float32_t3 getPatchedUpVector() - { - // if up vector and vector to the target are the same we patch the up vector - auto up = glm::normalize(m_upVec); - - const auto localTarget = getForwardDirection(); - const auto cross = glm::cross(localTarget, up); - - // we compute squared length but for checking if the len is 0 it doesnt matter - const bool upVectorZeroLength = glm::dot(cross, cross) == 0.f; - - if (upVectorZeroLength) - up = glm::normalize(m_backupUpVec); - - return up; - } - - inline bool isLeftHanded(const auto& projectionMatrix) + inline bool checkIfLeftHanded(const auto& projectionMatrix) { return hlsl::determinant(projectionMatrix) < 0.f; } @@ -313,35 +320,24 @@ class ICameraController : virtual public core::IReferenceCounted const float32_t3 m_initialPosition, m_initialTarget; glm::quat m_orientation; - float64_t4x4 m_viewMatrix; + float32_t4x4 m_viewMatrix; bool m_isLeftHanded, m_needsToRecomputeViewMatrix = false, m_recordingManipulation = false; }; - ICameraController() : {} + struct SUpdateParameters + { + //! Nabla mouse events you want to be handled with a controller + std::span mouseEvents = {}; - // controller overrides how a manipulation is done for a given camera event with a gimbal - virtual void manipulate(CGimbal* gimbal, VirtualEventType event) = 0; + //! Nabla keyboard events you want to be handled with a controller + std::span keyboardEvents = {}; + }; - // controller can override default set of event map - virtual void initKeysToEvent() - { - m_keysToEvent[MoveForward] = { ui::E_KEY_CODE::EKC_W }; - m_keysToEvent[MoveBackward] = { ui::E_KEY_CODE::EKC_S }; - m_keysToEvent[MoveLeft] = { ui::E_KEY_CODE::EKC_A }; - m_keysToEvent[MoveRight] = { ui::E_KEY_CODE::EKC_D }; - m_keysToEvent[MoveUp] = { ui::E_KEY_CODE::EKC_SPACE }; - m_keysToEvent[MoveDown] = { ui::E_KEY_CODE::EKC_LEFT_SHIFT }; - m_keysToEvent[TiltUp] = { ui::E_KEY_CODE::EKC_NONE }; - m_keysToEvent[TiltDown] = { ui::E_KEY_CODE::EKC_NONE }; - m_keysToEvent[PanLeft] = { ui::E_KEY_CODE::EKC_NONE }; - m_keysToEvent[PanRight] = { ui::E_KEY_CODE::EKC_NONE }; - m_keysToEvent[RollLeft] = { ui::E_KEY_CODE::EKC_NONE }; - m_keysToEvent[RollRight] = { ui::E_KEY_CODE::EKC_NONE }; - m_keysToEvent[Reset] = { ui::E_KEY_CODE::EKC_R }; - } + ICameraController(core::smart_refctd_ptr&& gimbal) + : m_gimbal(core::smart_refctd_ptr(gimbal)) {} // controller can override which keys correspond to which event void updateKeysToEvent(const std::vector& codes, VirtualEventType event) @@ -349,26 +345,121 @@ class ICameraController : virtual public core::IReferenceCounted m_keysToEvent[event] = std::move(codes); } -protected: + virtual void begin(std::chrono::microseconds nextPresentationTimeStamp) + { + m_nextPresentationTimeStamp = nextPresentationTimeStamp; + return; + } - inline void setMoveSpeed(const float moveSpeed) { moveSpeed = m_moveSpeed; } - inline void setRotateSpeed(const float rotateSpeed) { rotateSpeed = m_rotateSpeed; } + virtual void manipulate(SUpdateParameters parameters) = 0; + + void end(std::chrono::microseconds nextPresentationTimeStamp) + { + m_lastVirtualUpTimeStamp = nextPresentationTimeStamp; + } + + inline void setMoveSpeed(const float moveSpeed) { m_moveSpeed = moveSpeed; } + inline void setRotateSpeed(const float rotateSpeed) { m_rotateSpeed = rotateSpeed; } inline const float getMoveSpeed() const { return m_moveSpeed; } inline const float getRotateSpeed() const { return m_rotateSpeed; } - std::array, EventsCount> m_keysToEvent = {}; +protected: + // process keyboard to generate virtual manipulation events + std::vector processKeyboard(std::span events) + { + std::vector virtualEvents; - // speed factors - float m_moveSpeed = 1.f, m_rotateSpeed = 1.f; + for (const auto& ev : events) + { + constexpr auto NblVirtualKeys = std::to_array({ MoveForward, MoveBackward, MoveLeft, MoveRight, MoveUp, MoveDown, TiltUp, TiltDown, PanLeft, PanRight, RollLeft, RollRight, Reset }); + static_assert(NblVirtualKeys.size() == EventsCount); - // states signaling if keys are pressed down or not - bool m_keysDown[EventsCount] = {}; + for (const auto virtualKey : NblVirtualKeys) + { + const auto code = m_keysToEvent[virtualKey]; + + if (ev.keyCode == code) + { + if (code == ui::EKC_NONE) + continue; + + const auto dt = std::chrono::duration_cast(m_nextPresentationTimeStamp - ev.timeStamp).count(); + assert(dt >= 0); + + if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !m_keysDown[virtualKey]) + { + m_keysDown[virtualKey] = true; + virtualEvents.emplace_back(CVirtualEvent{ virtualKey, static_cast(dt) }); + } + else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) + { + m_keysDown[virtualKey] = false; + } + } + } + } + + return virtualEvents; + } + + /* + // [OPTIONAL]: process mouse to generate virtual manipulation events + // note: + // - all manipulations *may* be done with keyboard keys, it means you can have a pad/touch pad for which keys could be bound and skip the mouse + // - default implementation which is FPS camera-like controller doesn't perform roll rotation which is around Z axis! If you want more complex manipulations then override the method + // - it doesn't make the manipulation itself! It only process your mouse events you got from OS & accumulate data values to create manipulation virtual events! + */ + virtual std::vector processMouse(std::span events) const + { + // accumulate total pitch & yaw delta from mouse movement events + const auto [dTotalPitch, dTotalYaw] = [&]() + { + double dPitch = {}, dYaw = {}; + + for (const auto& ev : events) + if (ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT) + { + dYaw += ev.movementEvent.relativeMovementX; // (yaw) + dPitch -= ev.movementEvent.relativeMovementY; // (pitch) + } + + return std::make_tuple(dPitch, dYaw); + }(); - // durations for which the key was being held down from lastVirtualUpTimeStamp(=last "guessed" presentation time) to nextPresentationTimeStamp - double m_perActionDt[EventsCount] = {}; + CVirtualEvent pitch; + pitch.type = (pitch.value = dTotalPitch) > 0.f ? TiltUp : TiltDown; + + CVirtualEvent yaw; + yaw.type = (yaw.value = dTotalYaw) > 0.f ? PanRight : PanLeft; + + return { pitch, yaw }; + } + + // controller can override default set of event map + virtual void initKeysToEvent() + { + m_keysToEvent[MoveForward] = ui::E_KEY_CODE::EKC_W ; + m_keysToEvent[MoveBackward] = ui::E_KEY_CODE::EKC_S ; + m_keysToEvent[MoveLeft] = ui::E_KEY_CODE::EKC_A ; + m_keysToEvent[MoveRight] = ui::E_KEY_CODE::EKC_D ; + m_keysToEvent[MoveUp] = ui::E_KEY_CODE::EKC_SPACE ; + m_keysToEvent[MoveDown] = ui::E_KEY_CODE::EKC_LEFT_SHIFT ; + m_keysToEvent[TiltUp] = ui::E_KEY_CODE::EKC_NONE ; + m_keysToEvent[TiltDown] = ui::E_KEY_CODE::EKC_NONE ; + m_keysToEvent[PanLeft] = ui::E_KEY_CODE::EKC_NONE ; + m_keysToEvent[PanRight] = ui::E_KEY_CODE::EKC_NONE ; + m_keysToEvent[RollLeft] = ui::E_KEY_CODE::EKC_NONE ; + m_keysToEvent[RollRight] = ui::E_KEY_CODE::EKC_NONE ; + m_keysToEvent[Reset] = ui::E_KEY_CODE::EKC_R ; + } + + core::smart_refctd_ptr m_gimbal; + std::array m_keysToEvent = {}; + float m_moveSpeed = 1.f, m_rotateSpeed = 1.f; + bool m_keysDown[EventsCount] = {}; - std::chrono::microseconds nextPresentationTimeStamp, lastVirtualUpTimeStamp; + std::chrono::microseconds m_nextPresentationTimeStamp, m_lastVirtualUpTimeStamp; }; } // nbl::hlsl namespace From 038c5d799269a026ffa71e48f5963013632e4634 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 25 Oct 2024 14:13:35 +0200 Subject: [PATCH 09/84] create ICamera.hpp interface for all types of cameraz, update IProjection with isLeftHanded method, update sources & some of matrices to HLSL - the pure virtual controller manipulate method now takes a gimbal + span of virtual events --- 61_UI/main.cpp | 71 +++---- common/include/CCamera.hpp | 233 +++++++++++------------ common/include/CGeomtryCreatorScene.hpp | 2 +- common/include/ICamera.hpp | 40 ++++ common/include/camera/ICameraControl.hpp | 72 +++---- common/include/camera/IProjection.hpp | 5 + 6 files changed, 222 insertions(+), 201 deletions(-) create mode 100644 common/include/ICamera.hpp diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 374719bab..c78059ae5 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -7,9 +7,11 @@ #include "camera/CCubeProjection.hpp" #include "camera/ICameraControl.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING +#include "nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl" // FPS Camera, TESTS -using camera_t = Camera; +using projection_matrix_t = float32_t4x4; +using camera_t = Camera; using gimbal_t = camera_t::CGimbal; using projection_t = camera_t::base_t::projection_t; @@ -177,29 +179,23 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuiIO& io = ImGui::GetIO(); { auto& projection = gimbal->getProjection()->getProjectionMatrix(); - - - // TODO CASTS - - /* if (isPerspective) { if (isLH) - projection = glm::perspectiveLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + projection = projection_t::value_t(glm::perspectiveLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); else - projection = glm::perspectiveRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + projection = projection_t::value_t(glm::perspectiveRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); } else { float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; if (isLH) - projection = glm::orthoLH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar); + projection = projection_t::value_t(glm::orthoLH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar)); else - projection = glm::orthoRH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar); + projection = projection_t::value_t(glm::orthoRH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar)); } - */ } ImGuizmo::SetOrthographic(false); @@ -335,14 +331,22 @@ class UISampleApp final : public examples::SimpleWindowedApplication * note it also modifies input view matrix but projection matrix is immutable */ + + + /* + + TODODOD + + + + static struct { float32_t4x4 view, projection, model; } imguizmoM16InOut; ImGuizmo::SetID(0u); - - imguizmoM16InOut.view = transpose(gimbal->getViewMatrix()); + imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(gimbal->getViewMatrix())); imguizmoM16InOut.projection = transpose(gimbal->getProjection()->getProjectionMatrix()); imguizmoM16InOut.model = transpose(pass.scene->object.model); { @@ -353,26 +357,19 @@ class UISampleApp final : public examples::SimpleWindowedApplication EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); } - // to Nabla + update camera & model matrices const auto& view = gimbal->getViewMatrix(); const auto& projection = gimbal->getProjection()->getProjectionMatrix(); - - - /* - - TODO!!! - - + // TODO: make it more nicely - const_cast(view) = transpose(imguizmoM16InOut.view); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) - camera.setProjectionMatrix(projection); // update concatanated matrix + const_cast(view) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) + //camera.setProjectionMatrix(projection); // update concatanated matrix { - static nbl::core::matrix3x4SIMD modelView, normal; - static nbl::core::matrix4SIMD modelViewProjection; + static float32_t3x4 modelView, normal; + static float32_t4x4 modelViewProjection; auto& hook = pass.scene->object; - hook.model = core::transpose(imguizmoM16InOut.model); + hook.model = core::transpose(float32_t3x4(imguizmoM16InOut.model)); { const auto& references = pass.scene->getResources().objects; const auto type = static_cast(gcIndex); @@ -384,8 +381,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto& ubo = hook.viewParameters; - modelView = nbl::core::concatenateBFollowedByA(view, hook.model); - modelView.getSub3x3InverseTranspose(normal); + modelView = concatenateBFollowedByA(view, hook.model); + + // TODO + //modelView.getSub3x3InverseTranspose(normal); modelViewProjection = nbl::core::concatenateBFollowedByA(camera.getConcatenatedMatrix(), hook.model); memcpy(ubo.MVP, modelViewProjection.pointer(), sizeof(ubo.MVP)); @@ -432,7 +431,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } - */ + + */ // Nabla Imgui backend MDI buffer info // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, @@ -522,7 +522,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto pMatrix = glm::perspectiveLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar); auto projection = make_smart_refctd_ptr(); // TODO: CASTS FOR PROJ gimbal = make_smart_refctd_ptr(smart_refctd_ptr(projection), position, target, up); - camera = make_smart_refctd_ptr(smart_refctd_ptr(gimbal)); // note we still have shared ownership, TESTS + camera = make_smart_refctd_ptr(); return true; } @@ -770,7 +770,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (move) { - camera->manipulate({ .mouseEvents = params.mouseEvents, .keyboardEvents = params.keyboardEvents, }); + const auto virtualMouseEvents = camera->processMouse(params.mouseEvents); + const auto virtualKeyboardEvents = camera->processMouse(params.mouseEvents); + + gimbal->begin(); + camera->manipulate(gimbal.get(), { virtualMouseEvents.data(), virtualMouseEvents.size()}); + camera->manipulate(gimbal.get(), { virtualKeyboardEvents.data(), virtualKeyboardEvents.size()}); + gimbal->end(); + camera->end(nextPresentationTimestamp); } @@ -816,7 +823,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } pass; core::smart_refctd_ptr gimbal; - core::smart_refctd_ptr camera; + core::smart_refctd_ptr> camera; video::CDumbPresentationOracle oracle; uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 401c5aa4e..1c306c651 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -2,145 +2,126 @@ // This file is part of the "Nabla Engine". // For conditions of distribution and use, see copyright notice in nabla.h -#ifndef _CAMERA_IMPL_ -#define _CAMERA_IMPL_ +#ifndef _C_CAMERA_HPP_ +#define _C_CAMERA_HPP_ -#include -#include -#include -#include -#include - -#include "camera/ICameraControl.hpp" - -// FPS Camera, we will have more types soon +#include "ICamera.hpp" namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { +// FPS Camera template -class Camera : public ICameraController +class Camera : public ICamera { public: - using matrix_t = typename T; - using base_t = typename ICameraController; - using gimbal_t = typename base_t::CGimbal; - using gimbal_virtual_event_t = typename gimbal_t::CVirtualEvent; - using controller_virtual_event_t = typename base_t::CVirtualEvent; - - Camera(core::smart_refctd_ptr&& gimbal) - : base_t(core::smart_refctd_ptr(gimbal)) {} - ~Camera() = default; + using base_t = ICamera; + using traits_t = typename base_t::Traits; -public: + Camera() : base_t() {} + ~Camera() = default; - void manipulate(base_t::SUpdateParameters parameters) + virtual void manipulate(traits_t::gimbal_t* gimbal, std::span virtualEvents) override { - auto* gimbal = base_t::m_gimbal.get(); - - auto process = [&](const std::vector& virtualEvents) -> void - { - const auto forward = gimbal->getForwardDirection(); - const auto up = gimbal->getPatchedUpVector(); - const bool leftHanded = gimbal->isLeftHanded(); - - // strafe vector we move along when requesting left/right movements - const auto strafeLeftRight = leftHanded ? glm::normalize(glm::cross(forward, up)) : glm::normalize(glm::cross(up, forward)); - - constexpr auto MoveSpeedScale = 0.003f; - constexpr auto RotateSpeedScale = 0.003f; - - const auto dMoveFactor = base_t::m_moveSpeed * MoveSpeedScale; - const auto dRotateFactor = base_t::m_rotateSpeed * RotateSpeedScale; - - // TODO: UB/LB for pitch [-88,88]!!! we are not in cosmos but handle FPS camera - - for (const controller_virtual_event_t& ev : virtualEvents) - { - const auto dMoveValue = ev.value * dMoveFactor; - const auto dRotateValue = ev.value * dRotateFactor; - - gimbal_virtual_event_t gimbalEvent; - - switch (ev.type) - { - case base_t::MoveForward: - { - gimbalEvent.type = gimbal_t::Strafe; - gimbalEvent.manipulation.strafe.direction = forward; - gimbalEvent.manipulation.strafe.distance = dMoveValue; - } break; - - case base_t::MoveBackward: - { - gimbalEvent.type = gimbal_t::Strafe; - gimbalEvent.manipulation.strafe.direction = -forward; - gimbalEvent.manipulation.strafe.distance = dMoveValue; - } break; - - case base_t::MoveLeft: - { - gimbalEvent.type = gimbal_t::Strafe; - gimbalEvent.manipulation.strafe.direction = -strafeLeftRight; - gimbalEvent.manipulation.strafe.distance = dMoveValue; - } break; - - case base_t::MoveRight: - { - gimbalEvent.type = gimbal_t::Strafe; - gimbalEvent.manipulation.strafe.direction = strafeLeftRight; - gimbalEvent.manipulation.strafe.distance = dMoveValue; - } break; - - case base_t::TiltUp: - { - gimbalEvent.type = gimbal_t::Rotate; - gimbalEvent.manipulation.rotation.pitch = dRotateValue; - gimbalEvent.manipulation.rotation.roll = 0.0f; - gimbalEvent.manipulation.rotation.yaw = 0.0f; - } break; - - case base_t::TiltDown: - { - gimbalEvent.type = gimbal_t::Rotate; - gimbalEvent.manipulation.rotation.pitch = -dRotateValue; - gimbalEvent.manipulation.rotation.roll = 0.0f; - gimbalEvent.manipulation.rotation.yaw = 0.0f; - } break; - - case base_t::PanLeft: - { - gimbalEvent.type = gimbal_t::Rotate; - gimbalEvent.manipulation.rotation.pitch = 0.0f; - gimbalEvent.manipulation.rotation.roll = 0.0f; - gimbalEvent.manipulation.rotation.yaw = -dRotateValue; - } break; - - case base_t::PanRight: - { - gimbalEvent.type = gimbal_t::Rotate; - gimbalEvent.manipulation.rotation.pitch = 0.0f; - gimbalEvent.manipulation.rotation.roll = 0.0f; - gimbalEvent.manipulation.rotation.yaw = dRotateValue; - } break; - - default: - continue; - } - - gimbal->manipulate(gimbalEvent); - } - }; - - gimbal->begin(); - { - process(base_t::processMouse(parameters.mouseEvents)); - process(base_t::processKeyboard(parameters.keyboardEvents)); - } - gimbal->end(); + if (!gimbal) + return; // TODO: LOG + + if (!gimbal->isRecordingManipulation()) + return; // TODO: LOG + + const auto forward = gimbal->getForwardDirection(); + const auto up = gimbal->getPatchedUpVector(); + const bool leftHanded = gimbal->isLeftHanded(); + + // strafe vector we move along when requesting left/right movements + const auto strafeLeftRight = leftHanded ? glm::normalize(glm::cross(forward, up)) : glm::normalize(glm::cross(up, forward)); + + constexpr auto MoveSpeedScale = 0.003f; + constexpr auto RotateSpeedScale = 0.003f; + + const auto dMoveFactor = traits_t::controller_t::m_moveSpeed * MoveSpeedScale; + const auto dRotateFactor = traits_t::controller_t::m_rotateSpeed * RotateSpeedScale; + + // TODO: UB/LB for pitch [-88,88]!!! we are not in cosmos but handle FPS camera in default case + + for (const traits_t::controller_virtual_event_t& ev : virtualEvents) + { + const auto dMoveValue = ev.value * dMoveFactor; + const auto dRotateValue = ev.value * dRotateFactor; + + typename traits_t::gimbal_virtual_event_t gimbalEvent; + + switch (ev.type) + { + case traits_t::controller_t::MoveForward: + { + gimbalEvent.type = traits_t::gimbal_t::Strafe; + gimbalEvent.manipulation.strafe.direction = forward; + gimbalEvent.manipulation.strafe.distance = dMoveValue; + } break; + + case traits_t::controller_t::MoveBackward: + { + gimbalEvent.type = traits_t::gimbal_t::Strafe; + gimbalEvent.manipulation.strafe.direction = -forward; + gimbalEvent.manipulation.strafe.distance = dMoveValue; + } break; + + case traits_t::controller_t::MoveLeft: + { + gimbalEvent.type = traits_t::gimbal_t::Strafe; + gimbalEvent.manipulation.strafe.direction = -strafeLeftRight; + gimbalEvent.manipulation.strafe.distance = dMoveValue; + } break; + + case traits_t::controller_t::MoveRight: + { + gimbalEvent.type = traits_t::gimbal_t::Strafe; + gimbalEvent.manipulation.strafe.direction = strafeLeftRight; + gimbalEvent.manipulation.strafe.distance = dMoveValue; + } break; + + case traits_t::controller_t::TiltUp: + { + gimbalEvent.type = traits_t::gimbal_t::Rotate; + gimbalEvent.manipulation.rotation.pitch = dRotateValue; + gimbalEvent.manipulation.rotation.roll = 0.0f; + gimbalEvent.manipulation.rotation.yaw = 0.0f; + } break; + + case traits_t::controller_t::TiltDown: + { + gimbalEvent.type = traits_t::gimbal_t::Rotate; + gimbalEvent.manipulation.rotation.pitch = -dRotateValue; + gimbalEvent.manipulation.rotation.roll = 0.0f; + gimbalEvent.manipulation.rotation.yaw = 0.0f; + } break; + + case traits_t::controller_t::PanLeft: + { + gimbalEvent.type = traits_t::gimbal_t::Rotate; + gimbalEvent.manipulation.rotation.pitch = 0.0f; + gimbalEvent.manipulation.rotation.roll = 0.0f; + gimbalEvent.manipulation.rotation.yaw = -dRotateValue; + } break; + + case traits_t::controller_t::PanRight: + { + gimbalEvent.type = traits_t::gimbal_t::Rotate; + gimbalEvent.manipulation.rotation.pitch = 0.0f; + gimbalEvent.manipulation.rotation.roll = 0.0f; + gimbalEvent.manipulation.rotation.yaw = dRotateValue; + } break; + + default: + continue; + } + + gimbal->manipulate(gimbalEvent); + } } }; } -#endif // _CAMERA_IMPL_ \ No newline at end of file +#endif // _C_CAMERA_HPP_ \ No newline at end of file diff --git a/common/include/CGeomtryCreatorScene.hpp b/common/include/CGeomtryCreatorScene.hpp index 547c0aaf7..7b6492d11 100644 --- a/common/include/CGeomtryCreatorScene.hpp +++ b/common/include/CGeomtryCreatorScene.hpp @@ -1098,7 +1098,7 @@ class ResourceBuilder struct ObjectDrawHookCpu { - nbl::hlsl::float32_t4x4 model; + nbl::hlsl::float32_t3x4 model; nbl::asset::SBasicViewParameters viewParameters; ObjectMeta meta; }; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp new file mode 100644 index 000000000..22245c1e6 --- /dev/null +++ b/common/include/ICamera.hpp @@ -0,0 +1,40 @@ +// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _I_CAMERA_HPP_ +#define _I_CAMERA_HPP_ + +#include +#include +#include +#include +#include + +#include "camera/ICameraControl.hpp" + +namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE +{ + +template +class ICamera : public ICameraController +{ +public: + using base_t = typename ICameraController; + + struct Traits + { + using controller_t = base_t; + using projection_t = typename controller_t::projection_t; + using gimbal_t = typename controller_t::CGimbal; + using gimbal_virtual_event_t = typename gimbal_t::CVirtualEvent; + using controller_virtual_event_t = typename controller_t::CVirtualEvent; + }; + + ICamera() : base_t() {} + ~ICamera() = default; +}; + +} + +#endif // _I_CAMERA_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index c52558438..bcc1b6d54 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -2,6 +2,7 @@ #define _NBL_I_CAMERA_CONTROLLER_HPP_ #include "IProjection.hpp" +#include "../ICamera.hpp" #include "CVirtualCameraEvent.hpp" #include "glm/glm/ext/matrix_transform.hpp" // TODO: TEMPORARY!!! whatever used will be moved to cpp #include "glm/glm/gtc/quaternion.hpp" @@ -133,7 +134,7 @@ class ICameraController : virtual public core::IReferenceCounted }; CGimbal(const core::smart_refctd_ptr&& projection, const float32_t3& position, const float32_t3& lookat, const float32_t3& upVec = float32_t3(0.0f, 1.0f, 0.0f), const float32_t3& backupUpVec = float32_t3(0.5f, 1.0f, 0.0f)) - : m_projection(projection), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_orientation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)), m_viewMatrix({}), m_isLeftHanded(checkIfLeftHanded(m_projection->getProjectionMatrix())) + : m_projection(projection), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_orientation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)), m_viewMatrix({}) { recomputeViewMatrix(); } @@ -205,8 +206,6 @@ class ICameraController : virtual public core::IReferenceCounted //! End the gimbal manipulation session, recompute view matrix if required and update handedness state from projection inline void end() { - m_isLeftHanded = checkIfLeftHanded(m_projection->getProjectionMatrix()); - if (m_needsToRecomputeViewMatrix) recomputeViewMatrix(); @@ -214,6 +213,7 @@ class ICameraController : virtual public core::IReferenceCounted m_recordingManipulation = false; } + inline bool isRecordingManipulation() { return m_recordingManipulation; } inline projection_t* getProjection() { return m_projection.get(); } inline const float32_t3& getPosition() const { return m_position; } inline const float32_t3& getTarget() const { return m_target; } @@ -221,7 +221,7 @@ class ICameraController : virtual public core::IReferenceCounted inline const float32_t3& getBackupUpVector() const { return m_backupUpVec; } inline const float32_t3 getLocalTarget() const { return m_target - m_position; } inline const float32_t3 getForwardDirection() const { return glm::normalize(getLocalTarget()); } - inline const float32_t4x4& getViewMatrix() const { return m_viewMatrix; } + inline const float32_t3x4& getViewMatrix() const { return m_viewMatrix; } inline bool isLeftHanded() { return m_isLeftHanded; } inline float32_t3 getPatchedUpVector() @@ -300,19 +300,12 @@ class ICameraController : virtual public core::IReferenceCounted { auto up = getPatchedUpVector(); - // TODO!!!! CASTS + m_isLeftHanded = m_projection->isLeftHanded(); - /* if (m_isLeftHanded) - m_viewMatrix = glm::lookAtLH(m_position, m_target, up); + m_viewMatrix = float32_t3x4(float32_t4x4(glm::lookAtLH(m_position, m_target, up))); else - m_viewMatrix = glm::lookAtRH(m_position, m_target, up); - */ - } - - inline bool checkIfLeftHanded(const auto& projectionMatrix) - { - return hlsl::determinant(projectionMatrix) < 0.f; + m_viewMatrix = float32_t3x4(float32_t4x4(glm::lookAtRH(m_position, m_target, up))); } core::smart_refctd_ptr m_projection; @@ -320,52 +313,42 @@ class ICameraController : virtual public core::IReferenceCounted const float32_t3 m_initialPosition, m_initialTarget; glm::quat m_orientation; - float32_t4x4 m_viewMatrix; + float32_t3x4 m_viewMatrix; - bool m_isLeftHanded, + bool m_isLeftHanded = false, m_needsToRecomputeViewMatrix = false, m_recordingManipulation = false; }; - struct SUpdateParameters - { - //! Nabla mouse events you want to be handled with a controller - std::span mouseEvents = {}; - - //! Nabla keyboard events you want to be handled with a controller - std::span keyboardEvents = {}; - }; - - ICameraController(core::smart_refctd_ptr&& gimbal) - : m_gimbal(core::smart_refctd_ptr(gimbal)) {} + ICameraController() {} - // controller can override which keys correspond to which event + // override controller keys map, it binds a key code to a virtual event void updateKeysToEvent(const std::vector& codes, VirtualEventType event) { m_keysToEvent[event] = std::move(codes); } + // start controller manipulation session virtual void begin(std::chrono::microseconds nextPresentationTimeStamp) { m_nextPresentationTimeStamp = nextPresentationTimeStamp; return; } - virtual void manipulate(SUpdateParameters parameters) = 0; + // manipulate camera with gimbal & virtual events, begin must be called before that! + virtual void manipulate(CGimbal* gimbal, std::span virtualEvents) = 0; + // finish controller manipulation session, call after last manipulate in the hot loop void end(std::chrono::microseconds nextPresentationTimeStamp) { m_lastVirtualUpTimeStamp = nextPresentationTimeStamp; } - inline void setMoveSpeed(const float moveSpeed) { m_moveSpeed = moveSpeed; } - inline void setRotateSpeed(const float rotateSpeed) { m_rotateSpeed = rotateSpeed; } - - inline const float getMoveSpeed() const { return m_moveSpeed; } - inline const float getRotateSpeed() const { return m_rotateSpeed; } - -protected: + /* // process keyboard to generate virtual manipulation events + // note that: + // - it doesn't make the manipulation itself! + */ std::vector processKeyboard(std::span events) { std::vector virtualEvents; @@ -405,12 +388,11 @@ class ICameraController : virtual public core::IReferenceCounted /* // [OPTIONAL]: process mouse to generate virtual manipulation events - // note: - // - all manipulations *may* be done with keyboard keys, it means you can have a pad/touch pad for which keys could be bound and skip the mouse - // - default implementation which is FPS camera-like controller doesn't perform roll rotation which is around Z axis! If you want more complex manipulations then override the method - // - it doesn't make the manipulation itself! It only process your mouse events you got from OS & accumulate data values to create manipulation virtual events! + // note that: + // - all manipulations *may* be done with keyboard keys (if you have a touchpad or sth an ui:: event could be a code!) + // - it doesn't make the manipulation itself! */ - virtual std::vector processMouse(std::span events) const + std::vector processMouse(std::span events) const { // accumulate total pitch & yaw delta from mouse movement events const auto [dTotalPitch, dTotalYaw] = [&]() @@ -436,6 +418,13 @@ class ICameraController : virtual public core::IReferenceCounted return { pitch, yaw }; } + inline void setMoveSpeed(const float moveSpeed) { m_moveSpeed = moveSpeed; } + inline void setRotateSpeed(const float rotateSpeed) { m_rotateSpeed = rotateSpeed; } + + inline const float getMoveSpeed() const { return m_moveSpeed; } + inline const float getRotateSpeed() const { return m_rotateSpeed; } + +protected: // controller can override default set of event map virtual void initKeysToEvent() { @@ -454,7 +443,6 @@ class ICameraController : virtual public core::IReferenceCounted m_keysToEvent[Reset] = ui::E_KEY_CODE::EKC_R ; } - core::smart_refctd_ptr m_gimbal; std::array m_keysToEvent = {}; float m_moveSpeed = 1.f, m_rotateSpeed = 1.f; bool m_keysDown[EventsCount] = {}; diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index f3edfccc4..2210da7e5 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -19,6 +19,11 @@ class IProjection : virtual public core::IReferenceCounted IProjection(const value_t& matrix = {}) : m_projectionMatrix(matrix) {} value_t& getProjectionMatrix() { return m_projectionMatrix; } + inline bool isLeftHanded() + { + return hlsl::determinant(m_projectionMatrix) < 0.f; + } + protected: value_t m_projectionMatrix; }; From 242a871561427d77e04267920c626002fe45a089 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 25 Oct 2024 15:18:47 +0200 Subject: [PATCH 10/84] improve IProjection, make the matrix private & allow to change it with a setter & get with a getter therefore determine at set time handness (compute determinant only once after the matrix gets changed!), update main.cpp & sources - time to test it --- 61_UI/main.cpp | 48 ++++++++++-------------- common/include/CCamera.hpp | 2 +- common/include/camera/ICameraControl.hpp | 12 +----- common/include/camera/IProjection.hpp | 19 +++++++--- 4 files changed, 36 insertions(+), 45 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index c78059ae5..c561ab326 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -178,23 +178,23 @@ class UISampleApp final : public examples::SimpleWindowedApplication { ImGuiIO& io = ImGui::GetIO(); { - auto& projection = gimbal->getProjection()->getProjectionMatrix(); + auto* projection = gimbal->getProjection(); if (isPerspective) { if (isLH) - projection = projection_t::value_t(glm::perspectiveLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + projection->setMatrix(projection_t::value_t(glm::perspectiveLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar))); else - projection = projection_t::value_t(glm::perspectiveRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + projection->setMatrix(projection_t::value_t(glm::perspectiveRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar))); } else { float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; if (isLH) - projection = projection_t::value_t(glm::orthoLH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar)); + projection->setMatrix(projection_t::value_t(glm::orthoLH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar))); else - projection = projection_t::value_t(glm::orthoRH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar)); + projection->setMatrix(projection_t::value_t(glm::orthoRH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar))); } } @@ -331,24 +331,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication * note it also modifies input view matrix but projection matrix is immutable */ - - - /* - - TODODOD - - - - static struct { float32_t4x4 view, projection, model; } imguizmoM16InOut; + const auto& projectionMatrix = gimbal->getProjection()->getMatrix(); + ImGuizmo::SetID(0u); imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(gimbal->getViewMatrix())); - imguizmoM16InOut.projection = transpose(gimbal->getProjection()->getProjectionMatrix()); - imguizmoM16InOut.model = transpose(pass.scene->object.model); + imguizmoM16InOut.projection = transpose(projectionMatrix); + imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(pass.scene->object.model)); { if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ @@ -359,17 +352,15 @@ class UISampleApp final : public examples::SimpleWindowedApplication // to Nabla + update camera & model matrices const auto& view = gimbal->getViewMatrix(); - const auto& projection = gimbal->getProjection()->getProjectionMatrix(); // TODO: make it more nicely const_cast(view) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) - //camera.setProjectionMatrix(projection); // update concatanated matrix { static float32_t3x4 modelView, normal; static float32_t4x4 modelViewProjection; auto& hook = pass.scene->object; - hook.model = core::transpose(float32_t3x4(imguizmoM16InOut.model)); + hook.model = float32_t3x4(transpose(imguizmoM16InOut.model)); { const auto& references = pass.scene->getResources().objects; const auto type = static_cast(gcIndex); @@ -385,11 +376,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication // TODO //modelView.getSub3x3InverseTranspose(normal); - modelViewProjection = nbl::core::concatenateBFollowedByA(camera.getConcatenatedMatrix(), hook.model); - memcpy(ubo.MVP, modelViewProjection.pointer(), sizeof(ubo.MVP)); - memcpy(ubo.MV, modelView.pointer(), sizeof(ubo.MV)); - memcpy(ubo.NormalMat, normal.pointer(), sizeof(ubo.NormalMat)); + auto concatMatrix = mul(projectionMatrix, getMatrix3x4As4x4(view)); + modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(hook.model)); + + memcpy(ubo.MVP, &modelViewProjection[0][0], sizeof(ubo.MVP)); + memcpy(ubo.MV, &modelView[0][0], sizeof(ubo.MV)); + memcpy(ubo.NormalMat, &normal[0][0], sizeof(ubo.NormalMat)); // object meta display { @@ -399,7 +392,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - // view matrices editor { ImGui::Begin("Matrices"); @@ -425,15 +417,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Separator(); }; - addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, pass.scene->object.model.pointer()); - addMatrixTable("Camera View Matrix", "ViewMatrixTable", 3, 4, view.pointer()); - addMatrixTable("Camera View Projection Matrix", "ViewProjectionMatrixTable", 4, 4, projection.pointer(), false); + addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); + addMatrixTable("Camera Gimbal View Matrix", "ViewMatrixTable", 3, 4, &view[0][0]); + addMatrixTable("Camera Gimbal Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); ImGui::End(); } - */ - // Nabla Imgui backend MDI buffer info // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, // so unless you do that, don't use this basic info to optimize the size of your IMGUI buffer. diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 1c306c651..7024e69a9 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -31,7 +31,7 @@ class Camera : public ICamera const auto forward = gimbal->getForwardDirection(); const auto up = gimbal->getPatchedUpVector(); - const bool leftHanded = gimbal->isLeftHanded(); + const bool leftHanded = gimbal->getProjection()->isLeftHanded(); // strafe vector we move along when requesting left/right movements const auto strafeLeftRight = leftHanded ? glm::normalize(glm::cross(forward, up)) : glm::normalize(glm::cross(up, forward)); diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index bcc1b6d54..c6cef13ad 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -222,7 +222,6 @@ class ICameraController : virtual public core::IReferenceCounted inline const float32_t3 getLocalTarget() const { return m_target - m_position; } inline const float32_t3 getForwardDirection() const { return glm::normalize(getLocalTarget()); } inline const float32_t3x4& getViewMatrix() const { return m_viewMatrix; } - inline bool isLeftHanded() { return m_isLeftHanded; } inline float32_t3 getPatchedUpVector() { @@ -241,9 +240,6 @@ class ICameraController : virtual public core::IReferenceCounted return up; } - // TODO: getConcatenatedMatrix() - // TODO: getViewMatrix() - private: //! Reset the gimbal to its initial position, target, and orientation inline void reset() @@ -292,7 +288,6 @@ class ICameraController : virtual public core::IReferenceCounted // And we can simply update target vector m_target = m_position + localTargetRotated; - // TODO: std::any + nice checks for deltas (radians - periodic!) m_needsToRecomputeViewMatrix = true; } @@ -300,9 +295,7 @@ class ICameraController : virtual public core::IReferenceCounted { auto up = getPatchedUpVector(); - m_isLeftHanded = m_projection->isLeftHanded(); - - if (m_isLeftHanded) + if (m_projection->isLeftHanded()) m_viewMatrix = float32_t3x4(float32_t4x4(glm::lookAtLH(m_position, m_target, up))); else m_viewMatrix = float32_t3x4(float32_t4x4(glm::lookAtRH(m_position, m_target, up))); @@ -315,8 +308,7 @@ class ICameraController : virtual public core::IReferenceCounted glm::quat m_orientation; float32_t3x4 m_viewMatrix; - bool m_isLeftHanded = false, - m_needsToRecomputeViewMatrix = false, + bool m_needsToRecomputeViewMatrix = true, m_recordingManipulation = false; }; diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index 2210da7e5..1384f8e87 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -16,16 +16,25 @@ class IProjection : virtual public core::IReferenceCounted public: using value_t = T; - IProjection(const value_t& matrix = {}) : m_projectionMatrix(matrix) {} - value_t& getProjectionMatrix() { return m_projectionMatrix; } + IProjection(const value_t& matrix = {}) : m_projectionMatrix(matrix) { updateHandnessState(); } - inline bool isLeftHanded() + inline void setMatrix(const value_t& projectionMatrix) { - return hlsl::determinant(m_projectionMatrix) < 0.f; + m_projectionMatrix = projectionMatrix; + updateHandnessState(); + } + + inline const value_t& getMatrix() { return m_projectionMatrix; } + inline bool isLeftHanded() { return m_isLeftHanded; } + +private: + inline void updateHandnessState() + { + m_isLeftHanded = hlsl::determinant(m_projectionMatrix) < 0.f; } -protected: value_t m_projectionMatrix; + bool m_isLeftHanded; }; template From 0415999a7035f56f3d9cca7255329916c697ab36 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 25 Oct 2024 16:21:40 +0200 Subject: [PATCH 11/84] address https://github.com/Devsh-Graphics-Programming/Nabla/pull/760/files#r1816728485 comment in https://github.com/Devsh-Graphics-Programming/Nabla/pull/760 PR --- 61_UI/main.cpp | 5 ++--- common/include/camera/ICameraControl.hpp | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index c561ab326..321da0fce 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -7,7 +7,6 @@ #include "camera/CCubeProjection.hpp" #include "camera/ICameraControl.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING -#include "nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl" // FPS Camera, TESTS using projection_matrix_t = float32_t4x4; @@ -509,8 +508,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance), target(0.f, 0.f, 0.f), up(0.f, 1.f, 0.f); - auto pMatrix = glm::perspectiveLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar); - auto projection = make_smart_refctd_ptr(); // TODO: CASTS FOR PROJ + auto projection = make_smart_refctd_ptr(); + projection->setMatrix(projection_matrix_t(glm::perspectiveLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar))); gimbal = make_smart_refctd_ptr(smart_refctd_ptr(projection), position, target, up); camera = make_smart_refctd_ptr(); diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index c6cef13ad..96951dbb9 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -6,6 +6,7 @@ #include "CVirtualCameraEvent.hpp" #include "glm/glm/ext/matrix_transform.hpp" // TODO: TEMPORARY!!! whatever used will be moved to cpp #include "glm/glm/gtc/quaternion.hpp" +#include "nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl" // TODO: DIFFERENT NAMESPACE namespace nbl::hlsl @@ -296,9 +297,9 @@ class ICameraController : virtual public core::IReferenceCounted auto up = getPatchedUpVector(); if (m_projection->isLeftHanded()) - m_viewMatrix = float32_t3x4(float32_t4x4(glm::lookAtLH(m_position, m_target, up))); + m_viewMatrix = buildCameraLookAtMatrixLH(m_position, m_target, up); else - m_viewMatrix = float32_t3x4(float32_t4x4(glm::lookAtRH(m_position, m_target, up))); + m_viewMatrix = buildCameraLookAtMatrixRH(m_position, m_target, up); } core::smart_refctd_ptr m_projection; From 84f35e5d6259d0c814af72ccefcba0d00818668e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 25 Oct 2024 16:55:34 +0200 Subject: [PATCH 12/84] init default keys to virtual events, make ICameraController inheritance virtual, init time stamps with defaults --- common/include/CCamera.hpp | 4 ++-- common/include/ICamera.hpp | 2 +- common/include/camera/ICameraControl.hpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 7024e69a9..11a532719 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -12,13 +12,13 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE // FPS Camera template -class Camera : public ICamera +class Camera final : public ICamera { public: using base_t = ICamera; using traits_t = typename base_t::Traits; - Camera() : base_t() {} + Camera() : base_t() { traits_t::controller_t::initKeysToEvent(); } ~Camera() = default; virtual void manipulate(traits_t::gimbal_t* gimbal, std::span virtualEvents) override diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index 22245c1e6..da01f830b 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -17,7 +17,7 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { template -class ICamera : public ICameraController +class ICamera : public virtual ICameraController { public: using base_t = typename ICameraController; diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 96951dbb9..8896b5390 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -440,7 +440,7 @@ class ICameraController : virtual public core::IReferenceCounted float m_moveSpeed = 1.f, m_rotateSpeed = 1.f; bool m_keysDown[EventsCount] = {}; - std::chrono::microseconds m_nextPresentationTimeStamp, m_lastVirtualUpTimeStamp; + std::chrono::microseconds m_nextPresentationTimeStamp = {}, m_lastVirtualUpTimeStamp = {}; }; } // nbl::hlsl namespace From d8ff36a70a679e96718d098714db761e9b0a7a71 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 26 Oct 2024 17:41:40 +0200 Subject: [PATCH 13/84] split responsibilities & update sources --- 61_UI/main.cpp | 33 ++- common/include/CCamera.hpp | 130 +++++------- common/include/ICamera.hpp | 48 ++++- common/include/camera/ICameraControl.hpp | 245 ++++------------------- 4 files changed, 148 insertions(+), 308 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 321da0fce..388eab1e8 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -177,7 +177,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { ImGuiIO& io = ImGui::GetIO(); { - auto* projection = gimbal->getProjection(); + auto* projection = camera->getProjection(); if (isPerspective) { @@ -256,15 +256,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication { float32_t3 cameraPosition(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); float32_t3 cameraTarget(0.f, 0.f, 0.f); - const static float32_t3 up(0.f, 1.f, 0.f); - gimbal->begin(); - { - gimbal->setPosition(cameraPosition); - gimbal->setTarget(cameraTarget); - gimbal->setBackupUpVector(up); - } - gimbal->end(); + gimbal->setPosition(cameraPosition); + camera->setTarget(cameraTarget); firstFrame = false; } @@ -335,10 +329,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication float32_t4x4 view, projection, model; } imguizmoM16InOut; - const auto& projectionMatrix = gimbal->getProjection()->getMatrix(); + const auto& projectionMatrix = camera->getProjection()->getMatrix(); ImGuizmo::SetID(0u); - imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(gimbal->getViewMatrix())); + imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(camera->getViewMatrix())); imguizmoM16InOut.projection = transpose(projectionMatrix); imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(pass.scene->object.model)); { @@ -350,7 +344,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } // to Nabla + update camera & model matrices - const auto& view = gimbal->getViewMatrix(); + const auto& view = camera->getViewMatrix(); // TODO: make it more nicely const_cast(view) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) @@ -449,6 +443,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetCursorPosX(windowPadding); + + if (freePercentage > 70.0f) ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.0f, 1.0f, 0.0f, 0.4f)); // Green else if (freePercentage > 30.0f) @@ -506,12 +502,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication */ const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance), - target(0.f, 0.f, 0.f), up(0.f, 1.f, 0.f); + target(0.f, 0.f, 0.f); auto projection = make_smart_refctd_ptr(); projection->setMatrix(projection_matrix_t(glm::perspectiveLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar))); - gimbal = make_smart_refctd_ptr(smart_refctd_ptr(projection), position, target, up); - camera = make_smart_refctd_ptr(); + + gimbal = make_smart_refctd_ptr(position); + camera = make_smart_refctd_ptr(core::smart_refctd_ptr(gimbal), core::smart_refctd_ptr(projection), target); return true; } @@ -762,10 +759,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto virtualMouseEvents = camera->processMouse(params.mouseEvents); const auto virtualKeyboardEvents = camera->processMouse(params.mouseEvents); - gimbal->begin(); - camera->manipulate(gimbal.get(), { virtualMouseEvents.data(), virtualMouseEvents.size()}); - camera->manipulate(gimbal.get(), { virtualKeyboardEvents.data(), virtualKeyboardEvents.size()}); - gimbal->end(); + camera->manipulate({ virtualMouseEvents.data(), virtualMouseEvents.size()}); + camera->manipulate({ virtualKeyboardEvents.data(), virtualKeyboardEvents.size()}); camera->end(nextPresentationTimestamp); } diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 11a532719..bf33373a1 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -18,23 +18,20 @@ class Camera final : public ICamera using base_t = ICamera; using traits_t = typename base_t::Traits; - Camera() : base_t() { traits_t::controller_t::initKeysToEvent(); } + Camera(core::smart_refctd_ptr&& gimbal, core::smart_refctd_ptr projection, const float32_t3& target = { 0,0,0 }) + : base_t(core::smart_refctd_ptr(gimbal), core::smart_refctd_ptr(projection), target) { traits_t::controller_t::initKeysToEvent(); } ~Camera() = default; - virtual void manipulate(traits_t::gimbal_t* gimbal, std::span virtualEvents) override + virtual void manipulate(std::span virtualEvents) override { - if (!gimbal) - return; // TODO: LOG + auto* gimbal = traits_t::controller_t::m_gimbal.get(); + auto* projection = base_t::getProjection(); - if (!gimbal->isRecordingManipulation()) - return; // TODO: LOG + assert(gimbal); // TODO + assert(projection); // TODO - const auto forward = gimbal->getForwardDirection(); - const auto up = gimbal->getPatchedUpVector(); - const bool leftHanded = gimbal->getProjection()->isLeftHanded(); - - // strafe vector we move along when requesting left/right movements - const auto strafeLeftRight = leftHanded ? glm::normalize(glm::cross(forward, up)) : glm::normalize(glm::cross(up, forward)); + const auto [forward, up, right] = std::make_tuple(gimbal->getZAxis(), gimbal->getYAxis(), gimbal->getXAxis()); + const bool isLeftHanded = projection->isLeftHanded(); // TODO? constexpr auto MoveSpeedScale = 0.003f; constexpr auto RotateSpeedScale = 0.003f; @@ -43,81 +40,58 @@ class Camera final : public ICamera const auto dRotateFactor = traits_t::controller_t::m_rotateSpeed * RotateSpeedScale; // TODO: UB/LB for pitch [-88,88]!!! we are not in cosmos but handle FPS camera in default case + // TODO: accumulate move scalars & rotate scalars then do single move & rotate for (const traits_t::controller_virtual_event_t& ev : virtualEvents) { const auto dMoveValue = ev.value * dMoveFactor; const auto dRotateValue = ev.value * dRotateFactor; - typename traits_t::gimbal_virtual_event_t gimbalEvent; - switch (ev.type) { - case traits_t::controller_t::MoveForward: - { - gimbalEvent.type = traits_t::gimbal_t::Strafe; - gimbalEvent.manipulation.strafe.direction = forward; - gimbalEvent.manipulation.strafe.distance = dMoveValue; - } break; - - case traits_t::controller_t::MoveBackward: - { - gimbalEvent.type = traits_t::gimbal_t::Strafe; - gimbalEvent.manipulation.strafe.direction = -forward; - gimbalEvent.manipulation.strafe.distance = dMoveValue; - } break; - - case traits_t::controller_t::MoveLeft: - { - gimbalEvent.type = traits_t::gimbal_t::Strafe; - gimbalEvent.manipulation.strafe.direction = -strafeLeftRight; - gimbalEvent.manipulation.strafe.distance = dMoveValue; - } break; - - case traits_t::controller_t::MoveRight: - { - gimbalEvent.type = traits_t::gimbal_t::Strafe; - gimbalEvent.manipulation.strafe.direction = strafeLeftRight; - gimbalEvent.manipulation.strafe.distance = dMoveValue; - } break; - - case traits_t::controller_t::TiltUp: - { - gimbalEvent.type = traits_t::gimbal_t::Rotate; - gimbalEvent.manipulation.rotation.pitch = dRotateValue; - gimbalEvent.manipulation.rotation.roll = 0.0f; - gimbalEvent.manipulation.rotation.yaw = 0.0f; - } break; - - case traits_t::controller_t::TiltDown: - { - gimbalEvent.type = traits_t::gimbal_t::Rotate; - gimbalEvent.manipulation.rotation.pitch = -dRotateValue; - gimbalEvent.manipulation.rotation.roll = 0.0f; - gimbalEvent.manipulation.rotation.yaw = 0.0f; - } break; - - case traits_t::controller_t::PanLeft: - { - gimbalEvent.type = traits_t::gimbal_t::Rotate; - gimbalEvent.manipulation.rotation.pitch = 0.0f; - gimbalEvent.manipulation.rotation.roll = 0.0f; - gimbalEvent.manipulation.rotation.yaw = -dRotateValue; - } break; - - case traits_t::controller_t::PanRight: - { - gimbalEvent.type = traits_t::gimbal_t::Rotate; - gimbalEvent.manipulation.rotation.pitch = 0.0f; - gimbalEvent.manipulation.rotation.roll = 0.0f; - gimbalEvent.manipulation.rotation.yaw = dRotateValue; - } break; - - default: - continue; + case traits_t::controller_t::MoveForward: + { + gimbal->advance(dMoveValue); + } break; + + case traits_t::controller_t::MoveBackward: + { + gimbal->advance(-dMoveValue); + } break; + + case traits_t::controller_t::MoveLeft: + { + gimbal->strafe(dMoveValue); + } break; + + case traits_t::controller_t::MoveRight: + { + gimbal->strafe(-dMoveValue); + } break; + + case traits_t::controller_t::TiltUp: + { + gimbal->rotate(right, dRotateValue); + } break; + + case traits_t::controller_t::TiltDown: + { + gimbal->rotate(right, -dRotateValue); + } break; + + case traits_t::controller_t::PanLeft: + { + gimbal->rotate(up, dRotateValue); + } break; + + case traits_t::controller_t::PanRight: + { + gimbal->rotate(up, -dRotateValue); + } break; + + default: + continue; } - - gimbal->manipulate(gimbalEvent); } } }; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index da01f830b..e436bd8d7 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -17,7 +17,7 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { template -class ICamera : public virtual ICameraController +class ICamera : public ICameraController { public: using base_t = typename ICameraController; @@ -27,12 +27,54 @@ class ICamera : public virtual ICameraController using controller_t = base_t; using projection_t = typename controller_t::projection_t; using gimbal_t = typename controller_t::CGimbal; - using gimbal_virtual_event_t = typename gimbal_t::CVirtualEvent; using controller_virtual_event_t = typename controller_t::CVirtualEvent; }; - ICamera() : base_t() {} + ICamera(core::smart_refctd_ptr&& gimbal, core::smart_refctd_ptr projection, const float32_t3& target = {0,0,0}) + : base_t(core::smart_refctd_ptr(gimbal)), m_projection(core::smart_refctd_ptr(projection)) { setTarget(target); } ~ICamera() = default; + + inline void setPosition(const float32_t3& position) + { + const auto* gimbal = base_t::m_gimbal.get(); + gimbal->setPosition(position); + recomputeViewMatrix(); + } + + inline void setTarget(const float32_t3& position) + { + m_target = position; + + const auto* gimbal = base_t::m_gimbal.get(); + auto localTarget = m_target - gimbal->getPosition(); + + // TODO: use gimbal to perform a rotation! + + recomputeViewMatrix(); + } + + inline const float32_t3& getPosition() { return base_t::m_gimbal->getPosition(); } + inline const float32_t3& getTarget() { return m_target; } + inline const float32_t3x4& getViewMatrix() const { return m_viewMatrix; } + inline Traits::projection_t* getProjection() { return m_projection.get(); } + +private: + inline void recomputeViewMatrix() + { + // TODO: adjust for handedness (axes flip) + const bool isLeftHanded = m_projection->isLeftHanded(); + const auto* gimbal = base_t::m_gimbal.get(); + const auto& position = gimbal->getPosition(); + + const auto [xaxis, yaxis, zaxis] = std::make_tuple(gimbal->getXAxis(), gimbal->getYAxis(), gimbal->getZAxis()); + m_viewMatrix[0u] = float32_t4(xaxis, -hlsl::dot(xaxis, position)); + m_viewMatrix[1u] = float32_t4(yaxis, -hlsl::dot(yaxis, position)); + m_viewMatrix[2u] = float32_t4(zaxis, -hlsl::dot(zaxis, position)); + } + + const core::smart_refctd_ptr m_projection; + float32_t3x4 m_viewMatrix; + float32_t3 m_target; }; } diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 8896b5390..96e738b65 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -74,246 +74,74 @@ class ICameraController : virtual public core::IReferenceCounted class CGimbal : virtual public core::IReferenceCounted { public: - //! Virtual event representing a combined gimbal manipulation - enum VirtualEventType - { - //! Move the camera in the direction of strafe vector - Strafe, - - //! Update orientation of camera by rotating around X, Y, Z axes - Rotate, - - //! Signals boolean state, for example "reset" - State - }; - - class CVirtualEvent - { - public: - using manipulation_encode_t = float32_t4; - - struct StrafeManipulation - { - float32_t3 direction = {}; - float distance = {}; - }; - - struct RotateManipulation - { - float pitch = {}, roll = {}, yaw = {}; - }; - - struct StateManipulation - { - uint32_t reset : 1; - uint32_t reserved : 31; - - StateManipulation() { memset(this, 0, sizeof(StateManipulation)); } - ~StateManipulation() {} - }; - - union ManipulationValue - { - StrafeManipulation strafe; - RotateManipulation rotation; - StateManipulation state; - - ManipulationValue() { memset(this, 0, sizeof(ManipulationValue)); } - ~ManipulationValue() {} - }; - - CVirtualEvent() {} - - CVirtualEvent(VirtualEventType _type, const ManipulationValue _manipulation) - : type(_type), manipulation(_manipulation) - { - static_assert(sizeof(manipulation_encode_t) == sizeof(ManipulationValue)); - } - - VirtualEventType type; - ManipulationValue manipulation; - }; + CGimbal(const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + : m_position(position), m_orientation(orientation) {} - CGimbal(const core::smart_refctd_ptr&& projection, const float32_t3& position, const float32_t3& lookat, const float32_t3& upVec = float32_t3(0.0f, 1.0f, 0.0f), const float32_t3& backupUpVec = float32_t3(0.5f, 1.0f, 0.0f)) - : m_projection(projection), m_position(position), m_target(lookat), m_upVec(upVec), m_backupUpVec(backupUpVec), m_initialPosition(position), m_initialTarget(lookat), m_orientation(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)), m_viewMatrix({}) - { - recomputeViewMatrix(); - } - - // TODO: ctor with core::path to json config file to load defaults - - //! Start a gimbal manipulation session - inline void begin() - { - m_needsToRecomputeViewMatrix = false; - m_recordingManipulation = true; - } - - //! Record manipulation of the gimbal, note those events are delta manipulations - void manipulate(const CVirtualEvent& virtualEvent) - { - if (!m_recordingManipulation) - return; // TODO: log it - - const auto& manipulation = virtualEvent.manipulation; - - switch (virtualEvent.type) - { - case VirtualEventType::Strafe: - { - strafe(manipulation.strafe.direction, manipulation.strafe.distance); - } break; - - case VirtualEventType::Rotate: - { - rotate(manipulation.rotation.pitch, manipulation.rotation.yaw, manipulation.rotation.roll); - } break; - - case VirtualEventType::State: - { - if (manipulation.state.reset) - reset(); - } break; - - default: - break; - } - } - - // Record change of position vector, global update inline void setPosition(const float32_t3& position) { - if (!m_recordingManipulation) - return; // TODO: log it - m_position = position; } - // Record change of target vector, global update - inline void setTarget(const float32_t3& target) + inline void rotate(const float32_t3& axis, float dRadians) { - if (!m_recordingManipulation) - return; // TODO: log it - - m_target = target; + glm::quat dRotation = glm::angleAxis(dRadians, axis); + m_orientation = glm::normalize(dRotation * m_orientation); + m_orthonormal = float32_t3x3(glm::mat3_cast(m_orientation)); } - // Change up vector, global update - inline void setUpVector(const float32_t3& up) { m_upVec = up; } - - // Change backupUp vector, global update - inline void setBackupUpVector(const float32_t3& backupUp) { m_backupUpVec = backupUp; } - - //! End the gimbal manipulation session, recompute view matrix if required and update handedness state from projection - inline void end() + inline void strafe(float distance) { - if (m_needsToRecomputeViewMatrix) - recomputeViewMatrix(); - - m_needsToRecomputeViewMatrix = false; - m_recordingManipulation = false; + move({ 0.f, distance, 0.f }); } - inline bool isRecordingManipulation() { return m_recordingManipulation; } - inline projection_t* getProjection() { return m_projection.get(); } - inline const float32_t3& getPosition() const { return m_position; } - inline const float32_t3& getTarget() const { return m_target; } - inline const float32_t3& getUpVector() const { return m_upVec; } - inline const float32_t3& getBackupUpVector() const { return m_backupUpVec; } - inline const float32_t3 getLocalTarget() const { return m_target - m_position; } - inline const float32_t3 getForwardDirection() const { return glm::normalize(getLocalTarget()); } - inline const float32_t3x4& getViewMatrix() const { return m_viewMatrix; } - - inline float32_t3 getPatchedUpVector() + inline void climb(float distance) { - // if up vector and vector to the target are the same we patch the up vector - auto up = glm::normalize(m_upVec); - - const auto localTarget = getForwardDirection(); - const auto cross = glm::cross(localTarget, up); - - // we compute squared length but for checking if the len is 0 it doesnt matter - const bool upVectorZeroLength = glm::dot(cross, cross) == 0.f; - - if (upVectorZeroLength) - up = glm::normalize(m_backupUpVec); - - return up; + move({ 0.f, 0.f, distance }); } - private: - //! Reset the gimbal to its initial position, target, and orientation - inline void reset() + inline void advance(float distance) { - m_position = m_initialPosition; - m_target = m_initialTarget; - m_orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); - - recomputeViewMatrix(); // Recompute the view matrix after resetting + move({ distance, 0.f, 0.f }); } - //! Move in the direction of strafe (mostly left/right, up/down) - inline void strafe(const glm::vec3& direction, float distance) + inline void move(float32_t3 delta) { - if (distance != 0.0f) - { - const auto strafeVector = glm::normalize(direction) * distance; - m_position += strafeVector; - m_target += strafeVector; - - m_needsToRecomputeViewMatrix = true; - } + m_position += mul(m_orthonormal, delta); } - //! Update orientation by rotating around all XYZ axes - delta rotations in radians - inline void rotate(float dPitchRadians, float dYawDeltaRadians, float dRollDeltaRadians) + inline void reset() { - // Rotate around X (pitch) - glm::quat qPitch = glm::angleAxis(dPitchRadians, glm::vec3(1.0f, 0.0f, 0.0f)); - - // Rotate around Y (yaw) - glm::quat qYaw = glm::angleAxis(dYawDeltaRadians, glm::vec3(0.0f, 1.0f, 0.0f)); - - // Rotate around Z (roll) - glm::quat qRoll = glm::angleAxis(dRollDeltaRadians, glm::vec3(0.0f, 0.0f, 1.0f)); - - // Combine the new rotations with the current orientation - m_orientation = glm::normalize(qYaw * qPitch * qRoll * m_orientation); - - // Now we have rotation transformation as 3x3 matrix - auto rotate = glm::mat3_cast(m_orientation); + // TODO + } - // We do not change magnitude of the vector - auto localTargetRotated = rotate * getLocalTarget(); + // Position of gimbal + inline const float32_t3& getPosition() const { return m_position; } - // And we can simply update target vector - m_target = m_position + localTargetRotated; + // Orientation of gimbal + inline const glm::quat& getOrientation() const { return m_orientation; } - m_needsToRecomputeViewMatrix = true; - } + // Orthonormal [getXAxis(), getYAxis(), getZAxis()] matrix + inline const float32_t3x3& getOrthonornalMatrix() const { return m_orthonormal; } - inline void recomputeViewMatrix() - { - auto up = getPatchedUpVector(); + // Base right vector in orthonormal basis, base "right" vector (X-axis) + inline const float32_t3& getXAxis() const { return m_orthonormal[0u]; } - if (m_projection->isLeftHanded()) - m_viewMatrix = buildCameraLookAtMatrixLH(m_position, m_target, up); - else - m_viewMatrix = buildCameraLookAtMatrixRH(m_position, m_target, up); - } + // Base up vector in orthonormal basis, base "up" vector (Y-axis) + inline const float32_t3& getYAxis() const { return m_orthonormal[1u]; } - core::smart_refctd_ptr m_projection; - float32_t3 m_position, m_target, m_upVec, m_backupUpVec; - const float32_t3 m_initialPosition, m_initialTarget; + // Base forward vector in orthonormal basis, base "forward" vector (Z-axis) + inline const float32_t3& getZAxis() const { return m_orthonormal[2u]; } + private: + float32_t3 m_position; glm::quat m_orientation; - float32_t3x4 m_viewMatrix; - bool m_needsToRecomputeViewMatrix = true, - m_recordingManipulation = false; + // Represents the camera's orthonormal basis + // https://en.wikipedia.org/wiki/Orthonormal_basis + float32_t3x3 m_orthonormal; }; - ICameraController() {} + ICameraController(core::smart_refctd_ptr&& gimbal) : m_gimbal(core::smart_refctd_ptr(gimbal)) {} // override controller keys map, it binds a key code to a virtual event void updateKeysToEvent(const std::vector& codes, VirtualEventType event) @@ -329,7 +157,7 @@ class ICameraController : virtual public core::IReferenceCounted } // manipulate camera with gimbal & virtual events, begin must be called before that! - virtual void manipulate(CGimbal* gimbal, std::span virtualEvents) = 0; + virtual void manipulate(std::span virtualEvents) = 0; // finish controller manipulation session, call after last manipulate in the hot loop void end(std::chrono::microseconds nextPresentationTimeStamp) @@ -436,6 +264,7 @@ class ICameraController : virtual public core::IReferenceCounted m_keysToEvent[Reset] = ui::E_KEY_CODE::EKC_R ; } + core::smart_refctd_ptr m_gimbal; std::array m_keysToEvent = {}; float m_moveSpeed = 1.f, m_rotateSpeed = 1.f; bool m_keysDown[EventsCount] = {}; From e0c66bd8ef9a5d91feb7100f37663eee38eefdff Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 26 Oct 2024 17:50:08 +0200 Subject: [PATCH 14/84] add updateOrthonormalMatrix --- common/include/camera/ICameraControl.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 96e738b65..e650324f5 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -75,7 +75,7 @@ class ICameraController : virtual public core::IReferenceCounted { public: CGimbal(const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) - : m_position(position), m_orientation(orientation) {} + : m_position(position), m_orientation(orientation) { updateOrthonormalMatrix(); } inline void setPosition(const float32_t3& position) { @@ -86,7 +86,7 @@ class ICameraController : virtual public core::IReferenceCounted { glm::quat dRotation = glm::angleAxis(dRadians, axis); m_orientation = glm::normalize(dRotation * m_orientation); - m_orthonormal = float32_t3x3(glm::mat3_cast(m_orientation)); + updateOrthonormalMatrix(); } inline void strafe(float distance) @@ -133,6 +133,8 @@ class ICameraController : virtual public core::IReferenceCounted inline const float32_t3& getZAxis() const { return m_orthonormal[2u]; } private: + inline void updateOrthonormalMatrix() { m_orthonormal = float32_t3x3(glm::mat3_cast(glm::normalize(m_orientation))); } + float32_t3 m_position; glm::quat m_orientation; From 0574d725b2d0ae02a0d0e409e642a14981bc1e97 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 26 Oct 2024 18:14:49 +0200 Subject: [PATCH 15/84] start eliminating first runtime bugs & typos --- 61_UI/main.cpp | 2 +- common/include/CCamera.hpp | 2 +- common/include/camera/ICameraControl.hpp | 42 +++++++++++++----------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 388eab1e8..aca5ec34d 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -757,7 +757,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (move) { const auto virtualMouseEvents = camera->processMouse(params.mouseEvents); - const auto virtualKeyboardEvents = camera->processMouse(params.mouseEvents); + const auto virtualKeyboardEvents = camera->processKeyboard(params.keyboardEvents); camera->manipulate({ virtualMouseEvents.data(), virtualMouseEvents.size()}); camera->manipulate({ virtualKeyboardEvents.data(), virtualKeyboardEvents.size()}); diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index bf33373a1..2b610082c 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -40,7 +40,7 @@ class Camera final : public ICamera const auto dRotateFactor = traits_t::controller_t::m_rotateSpeed * RotateSpeedScale; // TODO: UB/LB for pitch [-88,88]!!! we are not in cosmos but handle FPS camera in default case - // TODO: accumulate move scalars & rotate scalars then do single move & rotate + // TODO: accumulate move & rotate scalars then do single move & rotate gimbal manipulation for (const traits_t::controller_virtual_event_t& ev : virtualEvents) { diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index e650324f5..67781b250 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -174,7 +174,7 @@ class ICameraController : virtual public core::IReferenceCounted */ std::vector processKeyboard(std::span events) { - std::vector virtualEvents; + std::vector output; for (const auto& ev : events) { @@ -196,7 +196,7 @@ class ICameraController : virtual public core::IReferenceCounted if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !m_keysDown[virtualKey]) { m_keysDown[virtualKey] = true; - virtualEvents.emplace_back(CVirtualEvent{ virtualKey, static_cast(dt) }); + output.emplace_back(CVirtualEvent{ virtualKey, static_cast(dt) }); } else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) { @@ -206,7 +206,7 @@ class ICameraController : virtual public core::IReferenceCounted } } - return virtualEvents; + return output; } /* @@ -217,28 +217,30 @@ class ICameraController : virtual public core::IReferenceCounted */ std::vector processMouse(std::span events) const { - // accumulate total pitch & yaw delta from mouse movement events - const auto [dTotalPitch, dTotalYaw] = [&]() - { - double dPitch = {}, dYaw = {}; + double dPitch = {}, dYaw = {}; - for (const auto& ev : events) - if (ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT) - { - dYaw += ev.movementEvent.relativeMovementX; // (yaw) - dPitch -= ev.movementEvent.relativeMovementY; // (pitch) - } + for (const auto& ev : events) + if (ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT) + { + dYaw += ev.movementEvent.relativeMovementX; + dPitch += ev.movementEvent.relativeMovementY; + } - return std::make_tuple(dPitch, dYaw); - }(); + std::vector output; - CVirtualEvent pitch; - pitch.type = (pitch.value = dTotalPitch) > 0.f ? TiltUp : TiltDown; + if (dPitch) + { + auto& pitch = output.emplace_back(); + pitch.type = (pitch.value = dPitch) > 0.f ? TiltUp : TiltDown; + } - CVirtualEvent yaw; - yaw.type = (yaw.value = dTotalYaw) > 0.f ? PanRight : PanLeft; + if (dYaw) + { + auto& yaw = output.emplace_back(); + yaw.type = (yaw.value = dYaw) > 0.f ? PanRight : PanLeft; + } - return { pitch, yaw }; + return output; } inline void setMoveSpeed(const float moveSpeed) { m_moveSpeed = moveSpeed; } From 221e1121e1581ab44d880bd26fd92797d573a243 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 28 Oct 2024 12:29:23 +0100 Subject: [PATCH 16/84] add debug asserts for matrix base checks --- common/include/camera/ICameraControl.hpp | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 67781b250..00fe649a2 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -133,7 +133,31 @@ class ICameraController : virtual public core::IReferenceCounted inline const float32_t3& getZAxis() const { return m_orthonormal[2u]; } private: - inline void updateOrthonormalMatrix() { m_orthonormal = float32_t3x3(glm::mat3_cast(glm::normalize(m_orientation))); } + inline void updateOrthonormalMatrix() + { + m_orthonormal = float32_t3x3(glm::mat3_cast(glm::normalize(m_orientation))); + + // DEBUG + const auto [xaxis, yaxis, zaxis] = std::make_tuple(getXAxis(),getYAxis(), getZAxis()); + + auto isNormalized = [](const auto& v, float epsilon) -> bool + { + return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); + }; + + auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool + { + return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); + }; + + auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool + { + return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && + isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); + }; + + assert(isOrthoBase(xaxis, yaxis, zaxis)); + } float32_t3 m_position; glm::quat m_orientation; From 13613c359318468819252dba1068c86e6dd9e54d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 28 Oct 2024 15:45:43 +0100 Subject: [PATCH 17/84] fix another bugs, make the rotation work! --- 61_UI/main.cpp | 21 ++++++++++++++------- common/include/CCamera.hpp | 12 +++++++++--- common/include/ICamera.hpp | 2 +- common/include/camera/ICameraControl.hpp | 6 ++++-- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index aca5ec34d..9e65776a7 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -182,18 +182,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (isPerspective) { if (isLH) - projection->setMatrix(projection_t::value_t(glm::perspectiveLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar))); + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); else - projection->setMatrix(projection_t::value_t(glm::perspectiveRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar))); + projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); } else { float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; if (isLH) - projection->setMatrix(projection_t::value_t(glm::orthoLH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar))); + projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); else - projection->setMatrix(projection_t::value_t(glm::orthoRH(-viewWidth / 2.0f, viewWidth / 2.0f, -viewHeight / 2.0f, viewHeight / 2.0f, zNear, zFar))); + projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); } } @@ -410,7 +410,16 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Separator(); }; + auto& orientation = gimbal->getOrthonornalMatrix(); + addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); + + addMatrixTable("Right", "OrientationRightVec", 1, 3, &gimbal->getXAxis()[0]); + addMatrixTable("Up", "OrientationUpVec", 1, 3, &gimbal->getYAxis()[0]); + addMatrixTable("Forward", "OrientationForwardVec", 1, 3, &gimbal->getZAxis()[0]); + addMatrixTable("Position", "PositionForwardVec", 1, 3, &gimbal->getPosition()[0]); + + //addMatrixTable("Camera Gimbal Orientation Matrix", "OrientationMatrixTable", 3, 3, &orientation[0][0]); addMatrixTable("Camera Gimbal View Matrix", "ViewMatrixTable", 3, 4, &view[0][0]); addMatrixTable("Camera Gimbal Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); @@ -443,8 +452,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetCursorPosX(windowPadding); - - if (freePercentage > 70.0f) ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.0f, 1.0f, 0.0f, 0.4f)); // Green else if (freePercentage > 30.0f) @@ -505,7 +512,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication target(0.f, 0.f, 0.f); auto projection = make_smart_refctd_ptr(); - projection->setMatrix(projection_matrix_t(glm::perspectiveLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar))); + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); gimbal = make_smart_refctd_ptr(position); camera = make_smart_refctd_ptr(core::smart_refctd_ptr(gimbal), core::smart_refctd_ptr(projection), target); diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 2b610082c..2f9751969 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -19,7 +19,11 @@ class Camera final : public ICamera using traits_t = typename base_t::Traits; Camera(core::smart_refctd_ptr&& gimbal, core::smart_refctd_ptr projection, const float32_t3& target = { 0,0,0 }) - : base_t(core::smart_refctd_ptr(gimbal), core::smart_refctd_ptr(projection), target) { traits_t::controller_t::initKeysToEvent(); } + : base_t(core::smart_refctd_ptr(gimbal), core::smart_refctd_ptr(projection), target) + { + traits_t::controller_t::initKeysToEvent(); + base_t::recomputeViewMatrix(); + } ~Camera() = default; virtual void manipulate(std::span virtualEvents) override @@ -79,12 +83,12 @@ class Camera final : public ICamera gimbal->rotate(right, -dRotateValue); } break; - case traits_t::controller_t::PanLeft: + case traits_t::controller_t::PanRight: { gimbal->rotate(up, dRotateValue); } break; - case traits_t::controller_t::PanRight: + case traits_t::controller_t::PanLeft: { gimbal->rotate(up, -dRotateValue); } break; @@ -93,6 +97,8 @@ class Camera final : public ICamera continue; } } + + base_t::recomputeViewMatrix(); } }; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index e436bd8d7..2f17fe79d 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -58,7 +58,7 @@ class ICamera : public ICameraController inline const float32_t3x4& getViewMatrix() const { return m_viewMatrix; } inline Traits::projection_t* getProjection() { return m_projection.get(); } -private: +protected: inline void recomputeViewMatrix() { // TODO: adjust for handedness (axes flip) diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 00fe649a2..52a6020ed 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -255,13 +255,15 @@ class ICameraController : virtual public core::IReferenceCounted if (dPitch) { auto& pitch = output.emplace_back(); - pitch.type = (pitch.value = dPitch) > 0.f ? TiltUp : TiltDown; + pitch.type = dPitch > 0.f ? TiltUp : TiltDown; + pitch.value = std::abs(dPitch); } if (dYaw) { auto& yaw = output.emplace_back(); - yaw.type = (yaw.value = dYaw) > 0.f ? PanRight : PanLeft; + yaw.type = dYaw > 0.f ? PanRight : PanLeft; + yaw.value = std::abs(dYaw); } return output; From db77a10ab0be16b629a3ec3c3b6690c5b5951cae Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 28 Oct 2024 17:16:22 +0100 Subject: [PATCH 18/84] movement fixes, finally gimbal fully controls camera position & orientation (TODO: constraints for type of camera + I broke continuity of events signaling & need those keysPressed bools I guess) --- common/include/CCamera.hpp | 18 ++++++++++-------- common/include/camera/ICameraControl.hpp | 11 ++--------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 2f9751969..9ccd59521 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -37,6 +37,8 @@ class Camera final : public ICamera const auto [forward, up, right] = std::make_tuple(gimbal->getZAxis(), gimbal->getYAxis(), gimbal->getXAxis()); const bool isLeftHanded = projection->isLeftHanded(); // TODO? + const auto moveDirection = float32_t3(gimbal->getOrientation() * glm::vec3(0.0f, 0.0f, 1.0f)); + constexpr auto MoveSpeedScale = 0.003f; constexpr auto RotateSpeedScale = 0.003f; @@ -48,29 +50,29 @@ class Camera final : public ICamera for (const traits_t::controller_virtual_event_t& ev : virtualEvents) { - const auto dMoveValue = ev.value * dMoveFactor; - const auto dRotateValue = ev.value * dRotateFactor; + const float dMoveValue = ev.value * dMoveFactor; + const float dRotateValue = ev.value * dRotateFactor; switch (ev.type) { case traits_t::controller_t::MoveForward: { - gimbal->advance(dMoveValue); + gimbal->move(moveDirection * dMoveValue); } break; case traits_t::controller_t::MoveBackward: { - gimbal->advance(-dMoveValue); + gimbal->move(moveDirection * (-dMoveValue)); } break; - case traits_t::controller_t::MoveLeft: + case traits_t::controller_t::MoveRight: { - gimbal->strafe(dMoveValue); + gimbal->move(right * dMoveValue); } break; - case traits_t::controller_t::MoveRight: + case traits_t::controller_t::MoveLeft: { - gimbal->strafe(-dMoveValue); + gimbal->move(right * (-dMoveValue)); } break; case traits_t::controller_t::TiltUp: diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 52a6020ed..fe248c76d 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -106,7 +106,7 @@ class ICameraController : virtual public core::IReferenceCounted inline void move(float32_t3 delta) { - m_position += mul(m_orthonormal, delta); + m_position += delta; } inline void reset() @@ -217,15 +217,8 @@ class ICameraController : virtual public core::IReferenceCounted const auto dt = std::chrono::duration_cast(m_nextPresentationTimeStamp - ev.timeStamp).count(); assert(dt >= 0); - if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !m_keysDown[virtualKey]) - { - m_keysDown[virtualKey] = true; + if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED) output.emplace_back(CVirtualEvent{ virtualKey, static_cast(dt) }); - } - else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - { - m_keysDown[virtualKey] = false; - } } } } From ae2b5201ca039e502d9e743cf587f6ef92431f49 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 28 Oct 2024 18:17:35 +0100 Subject: [PATCH 19/84] small typo --- common/include/CCamera.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 9ccd59521..7a49fffe1 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -34,11 +34,8 @@ class Camera final : public ICamera assert(gimbal); // TODO assert(projection); // TODO - const auto [forward, up, right] = std::make_tuple(gimbal->getZAxis(), gimbal->getYAxis(), gimbal->getXAxis()); const bool isLeftHanded = projection->isLeftHanded(); // TODO? - const auto moveDirection = float32_t3(gimbal->getOrientation() * glm::vec3(0.0f, 0.0f, 1.0f)); - constexpr auto MoveSpeedScale = 0.003f; constexpr auto RotateSpeedScale = 0.003f; @@ -50,6 +47,7 @@ class Camera final : public ICamera for (const traits_t::controller_virtual_event_t& ev : virtualEvents) { + const auto& forward = gimbal->getZAxis(), up = gimbal->getYAxis(), right = gimbal->getXAxis(); const float dMoveValue = ev.value * dMoveFactor; const float dRotateValue = ev.value * dRotateFactor; @@ -57,12 +55,12 @@ class Camera final : public ICamera { case traits_t::controller_t::MoveForward: { - gimbal->move(moveDirection * dMoveValue); + gimbal->move(forward * dMoveValue); } break; case traits_t::controller_t::MoveBackward: { - gimbal->move(moveDirection * (-dMoveValue)); + gimbal->move(forward * (-dMoveValue)); } break; case traits_t::controller_t::MoveRight: From d21bd96d60c5b03ebcf593f6d7a7baaa6de1a110 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 29 Oct 2024 16:48:57 +0100 Subject: [PATCH 20/84] accumulate move & rotation deltas, add setOrientation & correct processKeyboard a bit --- common/include/CCamera.hpp | 114 ++++++++++------------- common/include/camera/ICameraControl.hpp | 55 ++++++++--- 2 files changed, 92 insertions(+), 77 deletions(-) diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp index 7a49fffe1..31a105824 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CCamera.hpp @@ -26,80 +26,68 @@ class Camera final : public ICamera } ~Camera() = default; - virtual void manipulate(std::span virtualEvents) override - { + virtual void manipulate(std::span virtualEvents) override + { auto* gimbal = traits_t::controller_t::m_gimbal.get(); - auto* projection = base_t::getProjection(); + assert(gimbal); - assert(gimbal); // TODO - assert(projection); // TODO + constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; + const auto& gForward = gimbal->getZAxis(), gRight = gimbal->getXAxis(); - const bool isLeftHanded = projection->isLeftHanded(); // TODO? - - constexpr auto MoveSpeedScale = 0.003f; - constexpr auto RotateSpeedScale = 0.003f; - - const auto dMoveFactor = traits_t::controller_t::m_moveSpeed * MoveSpeedScale; - const auto dRotateFactor = traits_t::controller_t::m_rotateSpeed * RotateSpeedScale; - - // TODO: UB/LB for pitch [-88,88]!!! we are not in cosmos but handle FPS camera in default case - // TODO: accumulate move & rotate scalars then do single move & rotate gimbal manipulation + struct + { + float dPitch = 0.f, dYaw = 0.f; + float32_t3 dMove = { 0.f, 0.f, 0.f }; + } accumulated; - for (const traits_t::controller_virtual_event_t& ev : virtualEvents) + for (const auto& event : virtualEvents) { - const auto& forward = gimbal->getZAxis(), up = gimbal->getYAxis(), right = gimbal->getXAxis(); - const float dMoveValue = ev.value * dMoveFactor; - const float dRotateValue = ev.value * dRotateFactor; + const float moveScalar = event.value * MoveSpeedScale; + const float rotateScalar = event.value * RotateSpeedScale; - switch (ev.type) + switch (event.type) { - case traits_t::controller_t::MoveForward: - { - gimbal->move(forward * dMoveValue); - } break; - - case traits_t::controller_t::MoveBackward: - { - gimbal->move(forward * (-dMoveValue)); - } break; - - case traits_t::controller_t::MoveRight: - { - gimbal->move(right * dMoveValue); - } break; - - case traits_t::controller_t::MoveLeft: - { - gimbal->move(right * (-dMoveValue)); - } break; - - case traits_t::controller_t::TiltUp: - { - gimbal->rotate(right, dRotateValue); - } break; - - case traits_t::controller_t::TiltDown: - { - gimbal->rotate(right, -dRotateValue); - } break; - - case traits_t::controller_t::PanRight: - { - gimbal->rotate(up, dRotateValue); - } break; - - case traits_t::controller_t::PanLeft: - { - gimbal->rotate(up, -dRotateValue); - } break; - - default: - continue; + case traits_t::controller_t::MoveForward: + accumulated.dMove += gForward * moveScalar; + break; + case traits_t::controller_t::MoveBackward: + accumulated.dMove -= gForward * moveScalar; + break; + case traits_t::controller_t::MoveRight: + accumulated.dMove += gRight * moveScalar; + break; + case traits_t::controller_t::MoveLeft: + accumulated.dMove -= gRight * moveScalar; + break; + case traits_t::controller_t::TiltUp: + accumulated.dPitch += rotateScalar; + break; + case traits_t::controller_t::TiltDown: + accumulated.dPitch -= rotateScalar; + break; + case traits_t::controller_t::PanRight: + accumulated.dYaw += rotateScalar; + break; + case traits_t::controller_t::PanLeft: + accumulated.dYaw -= rotateScalar; + break; + default: + break; } } + float currentPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(); + float currentYaw = atan2(gForward.x, gForward.z); + + currentPitch = std::clamp(currentPitch + accumulated.dPitch, MinVerticalAngle, MaxVerticalAngle); + currentYaw += accumulated.dYaw; + + glm::quat orientation = glm::quat(glm::vec3(currentPitch, currentYaw, 0.0f)); + gimbal->setOrientation(orientation); + gimbal->move(accumulated.dMove); + base_t::recomputeViewMatrix(); - } + } }; } diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index fe248c76d..8503746da 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -82,6 +82,12 @@ class ICameraController : virtual public core::IReferenceCounted m_position = position; } + inline void setOrientation(const glm::quat& orientation) + { + m_orientation = glm::normalize(orientation); + updateOrthonormalMatrix(); + } + inline void rotate(const float32_t3& axis, float dRadians) { glm::quat dRotation = glm::angleAxis(dRadians, axis); @@ -198,29 +204,50 @@ class ICameraController : virtual public core::IReferenceCounted */ std::vector processKeyboard(std::span events) { + if (events.empty()) + return {}; + std::vector output; - for (const auto& ev : events) + constexpr auto NblVirtualKeys = std::to_array({ MoveForward, MoveBackward, MoveLeft, MoveRight, MoveUp, MoveDown, TiltUp, TiltDown, PanLeft, PanRight, RollLeft, RollRight, Reset }); + static_assert(NblVirtualKeys.size() == EventsCount); + + for (const auto virtualKey : NblVirtualKeys) { - constexpr auto NblVirtualKeys = std::to_array({ MoveForward, MoveBackward, MoveLeft, MoveRight, MoveUp, MoveDown, TiltUp, TiltDown, PanLeft, PanRight, RollLeft, RollRight, Reset }); - static_assert(NblVirtualKeys.size() == EventsCount); + const auto code = m_keysToEvent[virtualKey]; + bool& keyDown = m_keysDown[virtualKey]; - for (const auto virtualKey : NblVirtualKeys) + using virtual_key_state_t = std::tuple; + + auto updateVirtualState = [&]() -> virtual_key_state_t { - const auto code = m_keysToEvent[virtualKey]; + virtual_key_state_t state = { ui::E_KEY_CODE::EKC_NONE, false, 0.f }; - if (ev.keyCode == code) + for (const auto& ev : events) // TODO: improve the search { - if (code == ui::EKC_NONE) - continue; + if (ev.keyCode == code) + { + if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !keyDown) + keyDown = true; + else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) + keyDown = false; + + const auto dt = std::chrono::duration_cast(m_nextPresentationTimeStamp - ev.timeStamp).count(); + assert(dt >= 0); + + state = std::make_tuple(code, keyDown, dt); + break; + } + } + + return state; + }; - const auto dt = std::chrono::duration_cast(m_nextPresentationTimeStamp - ev.timeStamp).count(); - assert(dt >= 0); + const auto&& [physicalKey, isDown, dtAction] = updateVirtualState(); - if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED) - output.emplace_back(CVirtualEvent{ virtualKey, static_cast(dt) }); - } - } + if (physicalKey != ui::E_KEY_CODE::EKC_NONE) + if (isDown) + output.emplace_back(CVirtualEvent{ virtualKey, static_cast(dtAction) }); } return output; From ccbb37cf2bf150dec0c9ad8ea5d328fbb3806745 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 30 Oct 2024 07:46:11 +0100 Subject: [PATCH 21/84] remove old test methods --- common/include/camera/ICameraControl.hpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 8503746da..cafbbf1b0 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -95,21 +95,6 @@ class ICameraController : virtual public core::IReferenceCounted updateOrthonormalMatrix(); } - inline void strafe(float distance) - { - move({ 0.f, distance, 0.f }); - } - - inline void climb(float distance) - { - move({ 0.f, 0.f, distance }); - } - - inline void advance(float distance) - { - move({ distance, 0.f, 0.f }); - } - inline void move(float32_t3 delta) { m_position += delta; From d3f325d6d2b4c9c623b8e70a0882a1bae17471fb Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 2 Nov 2024 15:49:33 +0100 Subject: [PATCH 22/84] remove gimbal member from camera interface, have matrix precision typename, rename CCamera to CFPSCamera, create GeneralPurposeRange and update sources, remove unused files, add some concepts, mark notes & todos --- 61_UI/include/common.hpp | 2 +- 61_UI/main.cpp | 45 +++++++------- .../include/{CCamera.hpp => CFPSCamera.hpp} | 33 ++++++----- common/include/ICamera.hpp | 58 ++++++------------- common/include/IRange.hpp | 29 ++++++++++ common/include/camera/CVirtualCameraEvent.hpp | 12 ---- common/include/camera/ICameraControl.hpp | 45 +++++++++++--- common/include/camera/IProjection.hpp | 35 ++++++----- 8 files changed, 147 insertions(+), 112 deletions(-) rename common/include/{CCamera.hpp => CFPSCamera.hpp} (77%) create mode 100644 common/include/IRange.hpp delete mode 100644 common/include/camera/CVirtualCameraEvent.hpp diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index a5def7551..cf8afeea8 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -4,7 +4,7 @@ #include // common api -#include "CCamera.hpp" +#include "CFPSCamera.hpp" #include "SimpleWindowedApplication.hpp" #include "CEventCallback.hpp" diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 9e65776a7..b9a72c618 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -3,16 +3,13 @@ // For conditions of distribution and use, see copyright notice in nabla.h #include "common.hpp" - #include "camera/CCubeProjection.hpp" -#include "camera/ICameraControl.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING // FPS Camera, TESTS -using projection_matrix_t = float32_t4x4; -using camera_t = Camera; -using gimbal_t = camera_t::CGimbal; -using projection_t = camera_t::base_t::projection_t; +using matrix_precision_t = float32_t; +using camera_t = CFPSCamera; +using projection_t = camera_t::traits_t::projection_t; /* Renders scene texture to an offline @@ -182,18 +179,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (isPerspective) { if (isLH) - projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); else - projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); } else { float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; if (isLH) - projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); + projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); else - projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); + projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); } } @@ -257,8 +254,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication float32_t3 cameraPosition(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); float32_t3 cameraTarget(0.f, 0.f, 0.f); - gimbal->setPosition(cameraPosition); - camera->setTarget(cameraTarget); + // TODO: lets generate events and make it + // happen purely on gimbal manipulation! + + //camera->getGimbal()->setPosition(cameraPosition); + //camera->getGimbal()->setTarget(cameraTarget); firstFrame = false; } @@ -410,14 +410,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Separator(); }; - auto& orientation = gimbal->getOrthonornalMatrix(); + const auto& orientation = camera->getGimbal().getOrthonornalMatrix(); addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); - addMatrixTable("Right", "OrientationRightVec", 1, 3, &gimbal->getXAxis()[0]); - addMatrixTable("Up", "OrientationUpVec", 1, 3, &gimbal->getYAxis()[0]); - addMatrixTable("Forward", "OrientationForwardVec", 1, 3, &gimbal->getZAxis()[0]); - addMatrixTable("Position", "PositionForwardVec", 1, 3, &gimbal->getPosition()[0]); + addMatrixTable("Right", "OrientationRightVec", 1, 3, &camera->getGimbal().getXAxis()[0]); + addMatrixTable("Up", "OrientationUpVec", 1, 3, &camera->getGimbal().getYAxis()[0]); + addMatrixTable("Forward", "OrientationForwardVec", 1, 3, &camera->getGimbal().getZAxis()[0]); + addMatrixTable("Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); //addMatrixTable("Camera Gimbal Orientation Matrix", "OrientationMatrixTable", 3, 3, &orientation[0][0]); addMatrixTable("Camera Gimbal View Matrix", "ViewMatrixTable", 3, 4, &view[0][0]); @@ -508,14 +508,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication TESTS, TODO: remove all once finished work & integrate with the example properly */ - const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance), - target(0.f, 0.f, 0.f); + const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); auto projection = make_smart_refctd_ptr(); - projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); - gimbal = make_smart_refctd_ptr(position); - camera = make_smart_refctd_ptr(core::smart_refctd_ptr(gimbal), core::smart_refctd_ptr(projection), target); + camera = make_smart_refctd_ptr(core::smart_refctd_ptr(projection), position); return true; } @@ -813,8 +811,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication C_UI ui; } pass; - core::smart_refctd_ptr gimbal; - core::smart_refctd_ptr> camera; + core::smart_refctd_ptr> camera; video::CDumbPresentationOracle oracle; uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed diff --git a/common/include/CCamera.hpp b/common/include/CFPSCamera.hpp similarity index 77% rename from common/include/CCamera.hpp rename to common/include/CFPSCamera.hpp index 31a105824..8962a521c 100644 --- a/common/include/CCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -11,28 +11,30 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { // FPS Camera -template -class Camera final : public ICamera +template +class CFPSCamera final : public ICamera { public: - using base_t = ICamera; + using base_t = ICamera; using traits_t = typename base_t::Traits; - Camera(core::smart_refctd_ptr&& gimbal, core::smart_refctd_ptr projection, const float32_t3& target = { 0,0,0 }) - : base_t(core::smart_refctd_ptr(gimbal), core::smart_refctd_ptr(projection), target) + CFPSCamera(core::smart_refctd_ptr projection, const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + : base_t(core::smart_refctd_ptr(projection)), m_gimbal(position, orientation) { traits_t::controller_t::initKeysToEvent(); - base_t::recomputeViewMatrix(); + base_t::recomputeViewMatrix(m_gimbal); } - ~Camera() = default; + ~CFPSCamera() = default; - virtual void manipulate(std::span virtualEvents) override + const typename traits_t::gimbal_t& getGimbal() override { - auto* gimbal = traits_t::controller_t::m_gimbal.get(); - assert(gimbal); + return m_gimbal; + } + virtual void manipulate(std::span virtualEvents) override + { constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; - const auto& gForward = gimbal->getZAxis(), gRight = gimbal->getXAxis(); + const auto& gForward = m_gimbal.getZAxis(), gRight = m_gimbal.getXAxis(); struct { @@ -83,11 +85,14 @@ class Camera final : public ICamera currentYaw += accumulated.dYaw; glm::quat orientation = glm::quat(glm::vec3(currentPitch, currentYaw, 0.0f)); - gimbal->setOrientation(orientation); - gimbal->move(accumulated.dMove); + m_gimbal.setOrientation(orientation); + m_gimbal.move(accumulated.dMove); - base_t::recomputeViewMatrix(); + base_t::recomputeViewMatrix(m_gimbal); } + +private: + traits_t::gimbal_t m_gimbal; }; } diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index 2f17fe79d..b7978d100 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -16,11 +16,11 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { -template -class ICamera : public ICameraController +template +class ICamera : public ICameraController { public: - using base_t = typename ICameraController; + using base_t = typename ICameraController; struct Traits { @@ -28,53 +28,33 @@ class ICamera : public ICameraController using projection_t = typename controller_t::projection_t; using gimbal_t = typename controller_t::CGimbal; using controller_virtual_event_t = typename controller_t::CVirtualEvent; + using matrix_precision_t = typename T; // TODO: actually all vectors/scalars should have precision type T and because of projection matrix constraints allowed is only float32_t & float64_t }; - ICamera(core::smart_refctd_ptr&& gimbal, core::smart_refctd_ptr projection, const float32_t3& target = {0,0,0}) - : base_t(core::smart_refctd_ptr(gimbal)), m_projection(core::smart_refctd_ptr(projection)) { setTarget(target); } + ICamera(core::smart_refctd_ptr projection) + : base_t(), m_projection(core::smart_refctd_ptr(projection)) {} ~ICamera() = default; - inline void setPosition(const float32_t3& position) - { - const auto* gimbal = base_t::m_gimbal.get(); - gimbal->setPosition(position); - recomputeViewMatrix(); - } - - inline void setTarget(const float32_t3& position) - { - m_target = position; + // NOTE: I dont like to make it virtual but if we assume we can + // have more gimbals & we dont store single one in the interface + // then one of them must be the one we model the camera view with + // eg "Follow Camera" -> range of 2 gimbals but only first + // models the camera view + virtual const Traits::gimbal_t& getGimbal() = 0u; - const auto* gimbal = base_t::m_gimbal.get(); - auto localTarget = m_target - gimbal->getPosition(); - - // TODO: use gimbal to perform a rotation! - - recomputeViewMatrix(); - } - - inline const float32_t3& getPosition() { return base_t::m_gimbal->getPosition(); } - inline const float32_t3& getTarget() { return m_target; } - inline const float32_t3x4& getViewMatrix() const { return m_viewMatrix; } + inline const matrix& getViewMatrix() const { return m_viewMatrix; } inline Traits::projection_t* getProjection() { return m_projection.get(); } protected: - inline void recomputeViewMatrix() + // Recomputes view matrix for a given gimbal. Note that a camera type implementation could have multiple gimbals - not all of them will be used to model the camera view itself but one + // (TODO: (*) unless? I guess its to decide if we talk bout single view or to allow to have view per gimbal, but imho we should have 1 -> could just spam more instances of camera type to cover more views) + inline void recomputeViewMatrix(Traits::gimbal_t& gimbal) { - // TODO: adjust for handedness (axes flip) - const bool isLeftHanded = m_projection->isLeftHanded(); - const auto* gimbal = base_t::m_gimbal.get(); - const auto& position = gimbal->getPosition(); - - const auto [xaxis, yaxis, zaxis] = std::make_tuple(gimbal->getXAxis(), gimbal->getYAxis(), gimbal->getZAxis()); - m_viewMatrix[0u] = float32_t4(xaxis, -hlsl::dot(xaxis, position)); - m_viewMatrix[1u] = float32_t4(yaxis, -hlsl::dot(yaxis, position)); - m_viewMatrix[2u] = float32_t4(zaxis, -hlsl::dot(zaxis, position)); + gimbal.computeViewMatrix(m_viewMatrix, m_projection->isLeftHanded()); } - const core::smart_refctd_ptr m_projection; - float32_t3x4 m_viewMatrix; - float32_t3 m_target; + const core::smart_refctd_ptr m_projection; // TODO: move it from here + matrix m_viewMatrix; }; } diff --git a/common/include/IRange.hpp b/common/include/IRange.hpp new file mode 100644 index 000000000..5c6fe751b --- /dev/null +++ b/common/include/IRange.hpp @@ -0,0 +1,29 @@ +#ifndef _NBL_IRANGE_HPP_ +#define _NBL_IRANGE_HPP_ + +namespace nbl::hlsl +{ + +template +concept GeneralPurposeRange = requires +{ + typename std::ranges::range_value_t; +}; + +//! Interface class for a general purpose range +template +class IRange +{ +public: + using range_t = Range; + using range_value_t = std::ranges::range_value_t; + + IRange(range_t&& range) : m_range(std::move(range)) {} + +protected: + range_t m_range; +}; + +} // namespace nbl::hlsl + +#endif // _NBL_IRANGE_HPP_ \ No newline at end of file diff --git a/common/include/camera/CVirtualCameraEvent.hpp b/common/include/camera/CVirtualCameraEvent.hpp deleted file mode 100644 index 309c91662..000000000 --- a/common/include/camera/CVirtualCameraEvent.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef _NBL_VIRTUAL_CAMERA_EVENT_HPP_ -#define _NBL_VIRTUAL_CAMERA_EVENT_HPP_ - -#include "nbl/builtin/hlsl/cpp_compat/matrix.hlsl" - -// TODO: DIFFERENT NAMESPACE -namespace nbl::hlsl -{ - -} - -#endif // _NBL_VIRTUAL_CAMERA_EVENT_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index cafbbf1b0..565b7970a 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -2,8 +2,6 @@ #define _NBL_I_CAMERA_CONTROLLER_HPP_ #include "IProjection.hpp" -#include "../ICamera.hpp" -#include "CVirtualCameraEvent.hpp" #include "glm/glm/ext/matrix_transform.hpp" // TODO: TEMPORARY!!! whatever used will be moved to cpp #include "glm/glm/gtc/quaternion.hpp" #include "nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl" @@ -12,11 +10,12 @@ namespace nbl::hlsl { -template +template class ICameraController : virtual public core::IReferenceCounted { public: - using projection_t = typename IProjection; + using matrix_precision_t = typename T; + using projection_t = typename IProjection>; enum VirtualEventType : uint8_t { @@ -71,7 +70,7 @@ class ICameraController : virtual public core::IReferenceCounted manipulation_encode_t value; }; - class CGimbal : virtual public core::IReferenceCounted + class CGimbal { public: CGimbal(const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) @@ -123,6 +122,14 @@ class ICameraController : virtual public core::IReferenceCounted // Base forward vector in orthonormal basis, base "forward" vector (Z-axis) inline const float32_t3& getZAxis() const { return m_orthonormal[2u]; } + inline void computeViewMatrix(matrix& output, bool isLeftHanded) + { + const auto&& [xaxis, yaxis, zaxis] = std::make_tuple(getXAxis(), getYAxis(), getZAxis() * (isLeftHanded ? 1.f : -1.f)); + output[0u] = vector(xaxis, -hlsl::dot(xaxis, m_position)); + output[1u] = vector(yaxis, -hlsl::dot(yaxis, m_position)); + output[2u] = vector(zaxis, -hlsl::dot(zaxis, m_position)); + } + private: inline void updateOrthonormalMatrix() { @@ -158,7 +165,7 @@ class ICameraController : virtual public core::IReferenceCounted float32_t3x3 m_orthonormal; }; - ICameraController(core::smart_refctd_ptr&& gimbal) : m_gimbal(core::smart_refctd_ptr(gimbal)) {} + ICameraController() {} // override controller keys map, it binds a key code to a virtual event void updateKeysToEvent(const std::vector& codes, VirtualEventType event) @@ -299,7 +306,6 @@ class ICameraController : virtual public core::IReferenceCounted m_keysToEvent[Reset] = ui::E_KEY_CODE::EKC_R ; } - core::smart_refctd_ptr m_gimbal; std::array m_keysToEvent = {}; float m_moveSpeed = 1.f, m_rotateSpeed = 1.f; bool m_keysDown[EventsCount] = {}; @@ -307,6 +313,31 @@ class ICameraController : virtual public core::IReferenceCounted std::chrono::microseconds m_nextPresentationTimeStamp = {}, m_lastVirtualUpTimeStamp = {}; }; +template +concept GimbalRange = GeneralPurposeRange && requires +{ + requires ProjectionMatrix::projection_t>; + requires std::same_as, typename ICameraController::projection_t>::CGimbal>; +}; + +template +class IGimbalRange : public IRange +{ +public: + using base_t = IRange; + using range_t = typename base_t::range_t; + using gimbal_t = typename base_t::range_value_t; + + IGimbalRange(range_t&& gimbals) : base_t(std::move(gimbals)) {} + inline const range_t& getGimbals() const { return base_t::m_range; } + +protected: + inline range_t& getGimbals() const { return base_t::m_range; } +}; + +// TODO NOTE: eg. "follow camera" should use GimbalRange::CGimbal, 2u>>, +// one per camera itself and one for target it follows + } // nbl::hlsl namespace #endif // _NBL_I_CAMERA_CONTROLLER_HPP_ \ No newline at end of file diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index 1384f8e87..8de5c19f6 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -2,12 +2,13 @@ #define _NBL_IPROJECTION_HPP_ #include "nbl/builtin/hlsl/cpp_compat/matrix.hlsl" +#include "IRange.hpp" namespace nbl::hlsl { template -concept ProjectionMatrix = is_any_of_v; +concept ProjectionMatrix = is_any_of_v; //! Interface class for projection template @@ -37,29 +38,33 @@ class IProjection : virtual public core::IReferenceCounted bool m_isLeftHanded; }; +template +struct is_projection : std::false_type {}; + +template +struct is_projection> : std::true_type {}; + +template +inline constexpr bool is_projection_v = is_projection::value; + template -concept ProjectionRange = requires +concept ProjectionRange = GeneralPurposeRange && requires { - typename std::ranges::range_value_t; - // TODO: smart_refctd_ptr check for range_value_t + its type check to grant IProjection + requires core::is_smart_refctd_ptr_v>; + requires is_projection_v::pointee>; }; //! Interface class for a range of IProjection projections template>, 1u>> -class IProjectionRange +class IProjectionRange : public IRange { public: - using range_t = Range; - using projection_t = std::ranges::range_value_t; - - //! Constructor for the range of projections - IProjectionRange(range_t&& projections) : m_projectionRange(std::move(projections)) {} - - //! Get the stored range of projections - const range_t& getProjections() const { return m_projectionRange; } + using base_t = IRange; + using range_t = typename base_t::range_t; + using projection_t = typename base_t::range_value_t; -protected: - range_t m_projectionRange; + IProjectionRange(range_t&& projections) : base_t(std::move(projections)) {} + const range_t& getProjections() const { return base_t::m_range; } }; } // namespace nbl::hlsl From f9fce56971dd80264eb107c413ba0b038f4ebf96 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 3 Nov 2024 19:29:23 +0100 Subject: [PATCH 23/84] move virtual events outside templates into CVirtualGimbalEvent, have nice constexpr table to iterate over them, move view responsibility modeling to gimbal + make it optional! (yes - it's a good idea imo), keep manipulation state flag & manipulation counter to avoid updating the view too frequently, remove projection from cameras (projections will own/reference cameras), update a few structs, sources, clean some code, mark TODOs & notes --- 61_UI/main.cpp | 41 ++- common/include/CFPSCamera.hpp | 54 ++-- common/include/ICamera.hpp | 25 +- common/include/camera/ICameraControl.hpp | 338 ++++++++++++++--------- 4 files changed, 259 insertions(+), 199 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index b9a72c618..1d6016830 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -9,7 +9,7 @@ // FPS Camera, TESTS using matrix_precision_t = float32_t; using camera_t = CFPSCamera; -using projection_t = camera_t::traits_t::projection_t; +using projection_t = IProjection>; // TODO: temporary -> projections will own/reference cameras /* Renders scene texture to an offline @@ -174,8 +174,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication { ImGuiIO& io = ImGui::GetIO(); { - auto* projection = camera->getProjection(); - if (isPerspective) { if (isLH) @@ -329,10 +327,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication float32_t4x4 view, projection, model; } imguizmoM16InOut; - const auto& projectionMatrix = camera->getProjection()->getMatrix(); + const auto& projectionMatrix = projection->getMatrix(); + const auto& view = camera->getGimbal().getView().value(); ImGuizmo::SetID(0u); - imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(camera->getViewMatrix())); + imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(view.matrix)); imguizmoM16InOut.projection = transpose(projectionMatrix); imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(pass.scene->object.model)); { @@ -344,10 +343,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication } // to Nabla + update camera & model matrices - const auto& view = camera->getViewMatrix(); // TODO: make it more nicely - const_cast(view) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) + const_cast(view.matrix) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) { static float32_t3x4 modelView, normal; static float32_t4x4 modelViewProjection; @@ -365,12 +363,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto& ubo = hook.viewParameters; - modelView = concatenateBFollowedByA(view, hook.model); + modelView = concatenateBFollowedByA(view.matrix, hook.model); // TODO //modelView.getSub3x3InverseTranspose(normal); - auto concatMatrix = mul(projectionMatrix, getMatrix3x4As4x4(view)); + auto concatMatrix = mul(projectionMatrix, getMatrix3x4As4x4(view.matrix)); modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(hook.model)); memcpy(ubo.MVP, &modelViewProjection[0][0], sizeof(ubo.MVP)); @@ -420,7 +418,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication addMatrixTable("Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); //addMatrixTable("Camera Gimbal Orientation Matrix", "OrientationMatrixTable", 3, 3, &orientation[0][0]); - addMatrixTable("Camera Gimbal View Matrix", "ViewMatrixTable", 3, 4, &view[0][0]); + addMatrixTable("Camera Gimbal View Matrix", "ViewMatrixTable", 3, 4, &view.matrix[0][0]); addMatrixTable("Camera Gimbal Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); ImGui::End(); @@ -509,11 +507,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication */ const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); - - auto projection = make_smart_refctd_ptr(); projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); - - camera = make_smart_refctd_ptr(core::smart_refctd_ptr(projection), position); + camera = make_smart_refctd_ptr(position); return true; } @@ -692,9 +687,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication inline void update() { - camera->setMoveSpeed(moveSpeed); - camera->setRotateSpeed(rotateSpeed); - static std::chrono::microseconds previousEventTimestamp{}; m_inputSystem->getDefaultMouse(&mouse); @@ -718,9 +710,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication std::vector mouse{}; std::vector keyboard{}; } capturedEvents; - - if (move) - camera->begin(nextPresentationTimestamp); mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void { @@ -761,13 +750,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (move) { - const auto virtualMouseEvents = camera->processMouse(params.mouseEvents); - const auto virtualKeyboardEvents = camera->processKeyboard(params.keyboardEvents); + static std::vector virtualMouseEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size()), virtualKeyboardEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size()); + uint32_t vEventsMouseCount, vEventsKeyboardCount; - camera->manipulate({ virtualMouseEvents.data(), virtualMouseEvents.size()}); - camera->manipulate({ virtualKeyboardEvents.data(), virtualKeyboardEvents.size()}); + camera->processMouse(virtualMouseEvents.data(), vEventsMouseCount, params.mouseEvents); + camera->processKeyboard(virtualKeyboardEvents.data(), vEventsKeyboardCount, params.keyboardEvents); - camera->end(nextPresentationTimestamp); + camera->manipulate({ virtualMouseEvents.data(), vEventsMouseCount }); + camera->manipulate({ virtualKeyboardEvents.data(), vEventsKeyboardCount }); } pass.ui.manager->update(params); @@ -811,6 +801,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication C_UI ui; } pass; + smart_refctd_ptr projection = make_smart_refctd_ptr(); // TMP! core::smart_refctd_ptr> camera; video::CDumbPresentationOracle oracle; diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 8962a521c..58e07e1c0 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -18,11 +18,10 @@ class CFPSCamera final : public ICamera using base_t = ICamera; using traits_t = typename base_t::Traits; - CFPSCamera(core::smart_refctd_ptr projection, const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) - : base_t(core::smart_refctd_ptr(projection)), m_gimbal(position, orientation) + CFPSCamera(const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + : base_t(), m_gimbal({ .position = position, .orientation = orientation, .withView = true }) { - traits_t::controller_t::initKeysToEvent(); - base_t::recomputeViewMatrix(m_gimbal); + initKeysToEvent(); } ~CFPSCamera() = default; @@ -31,8 +30,11 @@ class CFPSCamera final : public ICamera return m_gimbal; } - virtual void manipulate(std::span virtualEvents) override + virtual void manipulate(std::span virtualEvents) override { + if (!virtualEvents.size()) + return; + constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; const auto& gForward = m_gimbal.getZAxis(), gRight = m_gimbal.getXAxis(); @@ -44,33 +46,33 @@ class CFPSCamera final : public ICamera for (const auto& event : virtualEvents) { - const float moveScalar = event.value * MoveSpeedScale; - const float rotateScalar = event.value * RotateSpeedScale; + const float moveScalar = event.magnitude * MoveSpeedScale; + const float rotateScalar = event.magnitude * RotateSpeedScale; switch (event.type) { - case traits_t::controller_t::MoveForward: + case CVirtualGimbalEvent::MoveForward: accumulated.dMove += gForward * moveScalar; break; - case traits_t::controller_t::MoveBackward: + case CVirtualGimbalEvent::MoveBackward: accumulated.dMove -= gForward * moveScalar; break; - case traits_t::controller_t::MoveRight: + case CVirtualGimbalEvent::MoveRight: accumulated.dMove += gRight * moveScalar; break; - case traits_t::controller_t::MoveLeft: + case CVirtualGimbalEvent::MoveLeft: accumulated.dMove -= gRight * moveScalar; break; - case traits_t::controller_t::TiltUp: + case CVirtualGimbalEvent::TiltUp: accumulated.dPitch += rotateScalar; break; - case traits_t::controller_t::TiltDown: + case CVirtualGimbalEvent::TiltDown: accumulated.dPitch -= rotateScalar; break; - case traits_t::controller_t::PanRight: + case CVirtualGimbalEvent::PanRight: accumulated.dYaw += rotateScalar; break; - case traits_t::controller_t::PanLeft: + case CVirtualGimbalEvent::PanLeft: accumulated.dYaw -= rotateScalar; break; default: @@ -85,13 +87,29 @@ class CFPSCamera final : public ICamera currentYaw += accumulated.dYaw; glm::quat orientation = glm::quat(glm::vec3(currentPitch, currentYaw, 0.0f)); - m_gimbal.setOrientation(orientation); - m_gimbal.move(accumulated.dMove); - base_t::recomputeViewMatrix(m_gimbal); + m_gimbal.begin(); + { + m_gimbal.setOrientation(orientation); + m_gimbal.move(accumulated.dMove); + } + m_gimbal.end(); } private: + void initKeysToEvent() override + { + traits_t::controller_t::updateKeysToEvent([](CVirtualGimbalEvent::keys_to_virtual_events_t& keys) + { + keys[CVirtualGimbalEvent::MoveForward] = ui::E_KEY_CODE::EKC_W; + keys[CVirtualGimbalEvent::MoveBackward] = ui::E_KEY_CODE::EKC_S; + keys[CVirtualGimbalEvent::MoveLeft] = ui::E_KEY_CODE::EKC_A; + keys[CVirtualGimbalEvent::MoveRight] = ui::E_KEY_CODE::EKC_D; + keys[CVirtualGimbalEvent::MoveUp] = ui::E_KEY_CODE::EKC_SPACE; + keys[CVirtualGimbalEvent::MoveDown] = ui::E_KEY_CODE::EKC_LEFT_SHIFT; + }); + } + traits_t::gimbal_t m_gimbal; }; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index b7978d100..4faebe536 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -25,36 +25,15 @@ class ICamera : public ICameraController struct Traits { using controller_t = base_t; - using projection_t = typename controller_t::projection_t; using gimbal_t = typename controller_t::CGimbal; - using controller_virtual_event_t = typename controller_t::CVirtualEvent; using matrix_precision_t = typename T; // TODO: actually all vectors/scalars should have precision type T and because of projection matrix constraints allowed is only float32_t & float64_t }; - ICamera(core::smart_refctd_ptr projection) - : base_t(), m_projection(core::smart_refctd_ptr(projection)) {} + ICamera() : base_t() {} ~ICamera() = default; - // NOTE: I dont like to make it virtual but if we assume we can - // have more gimbals & we dont store single one in the interface - // then one of them must be the one we model the camera view with - // eg "Follow Camera" -> range of 2 gimbals but only first - // models the camera view + // Returns a gimbal which *models the camera view*, note that a camera type implementation may have multiple gimbals under the hood virtual const Traits::gimbal_t& getGimbal() = 0u; - - inline const matrix& getViewMatrix() const { return m_viewMatrix; } - inline Traits::projection_t* getProjection() { return m_projection.get(); } - -protected: - // Recomputes view matrix for a given gimbal. Note that a camera type implementation could have multiple gimbals - not all of them will be used to model the camera view itself but one - // (TODO: (*) unless? I guess its to decide if we talk bout single view or to allow to have view per gimbal, but imho we should have 1 -> could just spam more instances of camera type to cover more views) - inline void recomputeViewMatrix(Traits::gimbal_t& gimbal) - { - gimbal.computeViewMatrix(m_viewMatrix, m_projection->isLeftHanded()); - } - - const core::smart_refctd_ptr m_projection; // TODO: move it from here - matrix m_viewMatrix; }; } diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 565b7970a..ff918faa4 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -10,13 +10,9 @@ namespace nbl::hlsl { -template -class ICameraController : virtual public core::IReferenceCounted +struct CVirtualGimbalEvent { -public: - using matrix_precision_t = typename T; - using projection_t = typename IProjection>; - + //! Virtual event representing a gimbal manipulation enum VirtualEventType : uint8_t { // Strafe forward @@ -55,53 +51,125 @@ class ICameraController : virtual public core::IReferenceCounted // Roll the camera clockwise around the forward axis (roll) RollRight, - // Reset the camera to the default state - Reset, - EventsCount }; - //! Virtual event representing a manipulation - struct CVirtualEvent + using manipulation_encode_t = float64_t; + using keys_to_virtual_events_t = std::array; + + VirtualEventType type; + manipulation_encode_t magnitude; + + static inline constexpr auto VirtualEventsTypeTable = []() { - using manipulation_encode_t = float64_t; + std::array output; - VirtualEventType type; - manipulation_encode_t value; - }; + for (uint16_t i = 0u; i < EventsCount - 1u; ++i) + { + output[i] = static_cast(i); + } + + return output; + }(); +}; + +template +class ICameraController : virtual public core::IReferenceCounted +{ +public: + using matrix_precision_t = typename T; class CGimbal { public: - CGimbal(const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) - : m_position(position), m_orientation(orientation) { updateOrthonormalMatrix(); } + struct SCreationParameters + { + float32_t3 position; + glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + bool withView = true; + }; + + // Gimbal's view matrix consists of an orthonormal basis (https://en.wikipedia.org/wiki/Orthonormal_basis) + // for orientation and a translation component that positions the world relative to the gimbal's position. + // Any camera type is supposed to manipulate a position & orientation of a gimbal + // with "virtual events" which model its view bound to the camera + struct SView + { + matrix matrix = {}; + bool isLeftHandSystem = true; + }; + + CGimbal(SCreationParameters&& parameters) + : m_position(parameters.position), m_orientation(parameters.orientation) + { + updateOrthonormalOrientationBase(); + + if (parameters.withView) + { + m_view = std::optional(SView{}); // RVO + updateView(); + } + } + + void begin() + { + m_isManipulating = true; + m_counter = 0u; + } inline void setPosition(const float32_t3& position) { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if (m_position != position) + m_counter++; + m_position = position; } inline void setOrientation(const glm::quat& orientation) { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if(m_orientation != orientation) + m_counter++; + m_orientation = glm::normalize(orientation); - updateOrthonormalMatrix(); + updateOrthonormalOrientationBase(); } inline void rotate(const float32_t3& axis, float dRadians) { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if(dRadians) + m_counter++; + glm::quat dRotation = glm::angleAxis(dRadians, axis); m_orientation = glm::normalize(dRotation * m_orientation); - updateOrthonormalMatrix(); + updateOrthonormalOrientationBase(); } inline void move(float32_t3 delta) { - m_position += delta; + assert(m_isManipulating); // TODO: log error and return without doing nothing + + auto newPosition = m_position + delta; + + if (newPosition != m_position) + m_counter++; + + m_position = newPosition; } - inline void reset() + void end() { - // TODO + m_isManipulating = false; + + if (m_counter > 0u) + updateView(); + + m_counter = 0u; } // Position of gimbal @@ -110,104 +178,113 @@ class ICameraController : virtual public core::IReferenceCounted // Orientation of gimbal inline const glm::quat& getOrientation() const { return m_orientation; } - // Orthonormal [getXAxis(), getYAxis(), getZAxis()] matrix + // Orthonormal [getXAxis(), getYAxis(), getZAxis()] orientation matrix inline const float32_t3x3& getOrthonornalMatrix() const { return m_orthonormal; } - // Base right vector in orthonormal basis, base "right" vector (X-axis) + // Base "right" vector in orthonormal orientation basis (X-axis) inline const float32_t3& getXAxis() const { return m_orthonormal[0u]; } - // Base up vector in orthonormal basis, base "up" vector (Y-axis) + // Base "up" vector in orthonormal orientation basis (Y-axis) inline const float32_t3& getYAxis() const { return m_orthonormal[1u]; } - // Base forward vector in orthonormal basis, base "forward" vector (Z-axis) + // Base "forward" vector in orthonormal orientation basis (Z-axis) inline const float32_t3& getZAxis() const { return m_orthonormal[2u]; } - inline void computeViewMatrix(matrix& output, bool isLeftHanded) + // Optional view of a gimbal + inline const std::optional& getView() const { return m_view; } + + inline const size_t& getManipulationCounter() { return m_counter; } + inline bool isManipulating() const { return m_isManipulating; } + + private: + inline void updateOrthonormalOrientationBase() { - const auto&& [xaxis, yaxis, zaxis] = std::make_tuple(getXAxis(), getYAxis(), getZAxis() * (isLeftHanded ? 1.f : -1.f)); - output[0u] = vector(xaxis, -hlsl::dot(xaxis, m_position)); - output[1u] = vector(yaxis, -hlsl::dot(yaxis, m_position)); - output[2u] = vector(zaxis, -hlsl::dot(zaxis, m_position)); + m_orthonormal = matrix(glm::mat3_cast(glm::normalize(m_orientation))); } - private: - inline void updateOrthonormalMatrix() - { - m_orthonormal = float32_t3x3(glm::mat3_cast(glm::normalize(m_orientation))); + inline void updateView() + { + if (m_view.has_value()) // TODO: this could be templated + constexpr actually if gimbal doesn't init this on runtime depending on sth + { + auto& view = m_view.value(); + const auto& gRight = getXAxis(), gUp = getYAxis(), gForward = getZAxis(); - // DEBUG - const auto [xaxis, yaxis, zaxis] = std::make_tuple(getXAxis(),getYAxis(), getZAxis()); + // TODO: I think I will provide convert utility allowing to go from one hand system to another, its just a matter to take care of m_view->matrix[2u] to perform a LH/RH flip + // in general this should not know about projections which are now supposed to be independent and store reference to a camera (or own it) + view.isLeftHandSystem = hlsl::determinant(m_orthonormal) < 0.0f; - auto isNormalized = [](const auto& v, float epsilon) -> bool - { - return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); - }; + auto isNormalized = [](const auto& v, float epsilon) -> bool + { + return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); + }; - auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool - { - return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); - }; + auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool + { + return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); + }; - auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool - { - return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && - isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); - }; + auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool + { + return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && + isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); + }; + + assert(isOrthoBase(gRight, gUp, gForward)); - assert(isOrthoBase(xaxis, yaxis, zaxis)); + view.matrix[0u] = vector(gRight, -glm::dot(gRight, m_position)); + view.matrix[1u] = vector(gUp, -glm::dot(gUp, m_position)); + view.matrix[2u] = vector(gForward, -glm::dot(gForward, m_position)); + } } float32_t3 m_position; glm::quat m_orientation; + matrix m_orthonormal; + + // For a camera implementation at least one gimbal models its view but not all gimbals (if multiple) are expected to do so + std::optional m_view = std::nullopt; + + // Counts *performed* manipulations, a manipulation with 0 delta is not counted! + size_t m_counter = {}; - // Represents the camera's orthonormal basis - // https://en.wikipedia.org/wiki/Orthonormal_basis - float32_t3x3 m_orthonormal; + // Records manipulation state + bool m_isManipulating = false; }; ICameraController() {} - // override controller keys map, it binds a key code to a virtual event - void updateKeysToEvent(const std::vector& codes, VirtualEventType event) + // Binds key codes to virtual events, the mapKeys lambda will be executed with controller CVirtualGimbalEvent::keys_to_virtual_events_t table + void updateKeysToEvent(const std::function& mapKeys) { - m_keysToEvent[event] = std::move(codes); + mapKeys(m_keysToVirtualEvents); } - // start controller manipulation session - virtual void begin(std::chrono::microseconds nextPresentationTimeStamp) - { - m_nextPresentationTimeStamp = nextPresentationTimeStamp; - return; - } + // Manipulates camera with view gimbal & virtual events + virtual void manipulate(std::span virtualEvents) = 0; - // manipulate camera with gimbal & virtual events, begin must be called before that! - virtual void manipulate(std::span virtualEvents) = 0; + // TODO: *maybe* would be good to have a class interface for virtual event generators, + // eg keyboard, mouse but maybe custom stuff too eg events from gimbal drag & drop - // finish controller manipulation session, call after last manipulate in the hot loop - void end(std::chrono::microseconds nextPresentationTimeStamp) + // Processes keyboard events to generate virtual manipulation events, note that it doesn't make the manipulation itself! + void processKeyboard(CVirtualGimbalEvent* output, uint32_t& count, std::span events) { - m_lastVirtualUpTimeStamp = nextPresentationTimeStamp; - } + if (!output) + { + count = CVirtualGimbalEvent::EventsCount; + return; + } - /* - // process keyboard to generate virtual manipulation events - // note that: - // - it doesn't make the manipulation itself! - */ - std::vector processKeyboard(std::span events) - { - if (events.empty()) - return {}; + count = 0u; - std::vector output; + if (events.empty()) + return; - constexpr auto NblVirtualKeys = std::to_array({ MoveForward, MoveBackward, MoveLeft, MoveRight, MoveUp, MoveDown, TiltUp, TiltDown, PanLeft, PanRight, RollLeft, RollRight, Reset }); - static_assert(NblVirtualKeys.size() == EventsCount); + const auto timestamp = getEventGenerationTimestamp(); - for (const auto virtualKey : NblVirtualKeys) + for (const auto virtualEventType : CVirtualGimbalEvent::VirtualEventsTypeTable) { - const auto code = m_keysToEvent[virtualKey]; - bool& keyDown = m_keysDown[virtualKey]; + const auto code = m_keysToVirtualEvents[virtualEventType]; + bool& keyDown = m_keysDown[virtualEventType]; using virtual_key_state_t = std::tuple; @@ -224,7 +301,7 @@ class ICameraController : virtual public core::IReferenceCounted else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) keyDown = false; - const auto dt = std::chrono::duration_cast(m_nextPresentationTimeStamp - ev.timeStamp).count(); + const auto dt = std::chrono::duration_cast(timestamp - ev.timeStamp).count(); assert(dt >= 0); state = std::make_tuple(code, keyDown, dt); @@ -239,21 +316,34 @@ class ICameraController : virtual public core::IReferenceCounted if (physicalKey != ui::E_KEY_CODE::EKC_NONE) if (isDown) - output.emplace_back(CVirtualEvent{ virtualKey, static_cast(dtAction) }); - } + { + auto* virtualEvent = output + count; + assert(virtualEvent); // TODO: maybe just log error and return 0 count - return output; + virtualEvent->type = virtualEventType; + virtualEvent->magnitude = static_cast(dtAction); + ++count; + } + } } - /* - // [OPTIONAL]: process mouse to generate virtual manipulation events - // note that: - // - all manipulations *may* be done with keyboard keys (if you have a touchpad or sth an ui:: event could be a code!) - // - it doesn't make the manipulation itself! - */ - std::vector processMouse(std::span events) const + // Processes mouse events to generate virtual manipulation events, note that it doesn't make the manipulation itself! + // Limited to Pan & Tilt rotation events, camera type implements how event magnitudes should be interpreted + void processMouse(CVirtualGimbalEvent* output, uint32_t& count, std::span events) { - double dPitch = {}, dYaw = {}; + if (!output) + { + count = 2u; + return; + } + + count = 0u; + + if (events.empty()) + return; + + const auto timestamp = getEventGenerationTimestamp(); + double dYaw = {}, dPitch = {}; for (const auto& ev : events) if (ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT) @@ -262,57 +352,38 @@ class ICameraController : virtual public core::IReferenceCounted dPitch += ev.movementEvent.relativeMovementY; } - std::vector output; - if (dPitch) { - auto& pitch = output.emplace_back(); - pitch.type = dPitch > 0.f ? TiltUp : TiltDown; - pitch.value = std::abs(dPitch); + auto* pitch = output + count; + assert(pitch); // TODO: maybe just log error and return 0 count + pitch->type = dPitch > 0.f ? CVirtualGimbalEvent::TiltUp : CVirtualGimbalEvent::TiltDown; + pitch->magnitude = std::abs(dPitch); + count++; } if (dYaw) { - auto& yaw = output.emplace_back(); - yaw.type = dYaw > 0.f ? PanRight : PanLeft; - yaw.value = std::abs(dYaw); + auto* yaw = output + count; + assert(yaw); // TODO: maybe just log error and return 0 count + yaw->type = dYaw > 0.f ? CVirtualGimbalEvent::PanRight : CVirtualGimbalEvent::PanLeft; + yaw->magnitude = std::abs(dYaw); + count++; } - - return output; } - inline void setMoveSpeed(const float moveSpeed) { m_moveSpeed = moveSpeed; } - inline void setRotateSpeed(const float rotateSpeed) { m_rotateSpeed = rotateSpeed; } - - inline const float getMoveSpeed() const { return m_moveSpeed; } - inline const float getRotateSpeed() const { return m_rotateSpeed; } - protected: - // controller can override default set of event map - virtual void initKeysToEvent() - { - m_keysToEvent[MoveForward] = ui::E_KEY_CODE::EKC_W ; - m_keysToEvent[MoveBackward] = ui::E_KEY_CODE::EKC_S ; - m_keysToEvent[MoveLeft] = ui::E_KEY_CODE::EKC_A ; - m_keysToEvent[MoveRight] = ui::E_KEY_CODE::EKC_D ; - m_keysToEvent[MoveUp] = ui::E_KEY_CODE::EKC_SPACE ; - m_keysToEvent[MoveDown] = ui::E_KEY_CODE::EKC_LEFT_SHIFT ; - m_keysToEvent[TiltUp] = ui::E_KEY_CODE::EKC_NONE ; - m_keysToEvent[TiltDown] = ui::E_KEY_CODE::EKC_NONE ; - m_keysToEvent[PanLeft] = ui::E_KEY_CODE::EKC_NONE ; - m_keysToEvent[PanRight] = ui::E_KEY_CODE::EKC_NONE ; - m_keysToEvent[RollLeft] = ui::E_KEY_CODE::EKC_NONE ; - m_keysToEvent[RollRight] = ui::E_KEY_CODE::EKC_NONE ; - m_keysToEvent[Reset] = ui::E_KEY_CODE::EKC_R ; - } + virtual void initKeysToEvent() = 0; - std::array m_keysToEvent = {}; - float m_moveSpeed = 1.f, m_rotateSpeed = 1.f; - bool m_keysDown[EventsCount] = {}; +private: + CVirtualGimbalEvent::keys_to_virtual_events_t m_keysToVirtualEvents = { { ui::E_KEY_CODE::EKC_NONE } }; + bool m_keysDown[CVirtualGimbalEvent::EventsCount] = {}; - std::chrono::microseconds m_nextPresentationTimeStamp = {}, m_lastVirtualUpTimeStamp = {}; + // exactly what our Nabla events do, actually I don't want users to pass timestamp since I know when it should be best to make a request -> just before generating events! + // TODO: need to think about this + inline std::chrono::microseconds getEventGenerationTimestamp() { return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); } }; +#if 0 // TOOD: update template concept GimbalRange = GeneralPurposeRange && requires { @@ -337,6 +408,7 @@ class IGimbalRange : public IRange // TODO NOTE: eg. "follow camera" should use GimbalRange::CGimbal, 2u>>, // one per camera itself and one for target it follows +#endif } // nbl::hlsl namespace From 2f44640ec4e823c5d97b427562b291fd0e3eb62f Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 3 Nov 2024 20:11:43 +0100 Subject: [PATCH 24/84] bad typo --- common/include/camera/ICameraControl.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index ff918faa4..94e612bf0 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -55,16 +55,16 @@ struct CVirtualGimbalEvent }; using manipulation_encode_t = float64_t; - using keys_to_virtual_events_t = std::array; + using keys_to_virtual_events_t = std::array; VirtualEventType type; manipulation_encode_t magnitude; static inline constexpr auto VirtualEventsTypeTable = []() { - std::array output; + std::array output; - for (uint16_t i = 0u; i < EventsCount - 1u; ++i) + for (uint16_t i = 0u; i < EventsCount; ++i) { output[i] = static_cast(i); } From 1ad781f9b5e6999485af3e1ddd643a5f0223cb19 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 6 Nov 2024 13:00:24 +0100 Subject: [PATCH 25/84] create CGeneralPurposeGimbal & IGimbal (which will apply to any kind of object), store `const uintptr_t m_id;` in gimbal intercae for imguizmo/imgui api, update sources --- 61_UI/main.cpp | 2 +- common/include/CFPSCamera.hpp | 5 +- common/include/ICamera.hpp | 8 +- common/include/camera/ICameraControl.hpp | 229 +++-------------------- common/include/camera/IGimbal.hpp | 193 +++++++++++++++++++ 5 files changed, 225 insertions(+), 212 deletions(-) create mode 100644 common/include/camera/IGimbal.hpp diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 1d6016830..e18185453 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -328,7 +328,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } imguizmoM16InOut; const auto& projectionMatrix = projection->getMatrix(); - const auto& view = camera->getGimbal().getView().value(); + const auto& view = camera->getGimbal().getView(); ImGuizmo::SetID(0u); imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(view.matrix)); diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 58e07e1c0..e3ee863a0 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -18,8 +18,8 @@ class CFPSCamera final : public ICamera using base_t = ICamera; using traits_t = typename base_t::Traits; - CFPSCamera(const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) - : base_t(), m_gimbal({ .position = position, .orientation = orientation, .withView = true }) + CFPSCamera(const vector& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + : base_t(), m_gimbal({ .position = position, .orientation = orientation }) { initKeysToEvent(); } @@ -92,6 +92,7 @@ class CFPSCamera final : public ICamera { m_gimbal.setOrientation(orientation); m_gimbal.move(accumulated.dMove); + m_gimbal.updateView(); } m_gimbal.end(); } diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index 4faebe536..4556a8097 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -5,12 +5,6 @@ #ifndef _I_CAMERA_HPP_ #define _I_CAMERA_HPP_ -#include -#include -#include -#include -#include - #include "camera/ICameraControl.hpp" namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE @@ -26,7 +20,7 @@ class ICamera : public ICameraController { using controller_t = base_t; using gimbal_t = typename controller_t::CGimbal; - using matrix_precision_t = typename T; // TODO: actually all vectors/scalars should have precision type T and because of projection matrix constraints allowed is only float32_t & float64_t + using precision_t = typename T; // TODO: actually all vectors/scalars should have precision type T and because of projection matrix constraints allowed is only float32_t & float64_t }; ICamera() : base_t() {} diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 94e612bf0..af1fc9fbe 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -1,217 +1,48 @@ #ifndef _NBL_I_CAMERA_CONTROLLER_HPP_ #define _NBL_I_CAMERA_CONTROLLER_HPP_ +#include +#include +#include +#include +#include + #include "IProjection.hpp" -#include "glm/glm/ext/matrix_transform.hpp" // TODO: TEMPORARY!!! whatever used will be moved to cpp -#include "glm/glm/gtc/quaternion.hpp" -#include "nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl" +#include "CGeneralPurposeGimbal.hpp" // TODO: DIFFERENT NAMESPACE namespace nbl::hlsl { -struct CVirtualGimbalEvent -{ - //! Virtual event representing a gimbal manipulation - enum VirtualEventType : uint8_t - { - // Strafe forward - MoveForward = 0, - - // Strafe backward - MoveBackward, - - // Strafe left - MoveLeft, - - // Strafe right - MoveRight, - - // Strafe up - MoveUp, - - // Strafe down - MoveDown, - - // Tilt the camera upward (pitch) - TiltUp, - - // Tilt the camera downward (pitch) - TiltDown, - - // Rotate the camera left around the vertical axis (yaw) - PanLeft, - - // Rotate the camera right around the vertical axis (yaw) - PanRight, - - // Roll the camera counterclockwise around the forward axis (roll) - RollLeft, - - // Roll the camera clockwise around the forward axis (roll) - RollRight, - - EventsCount - }; - - using manipulation_encode_t = float64_t; - using keys_to_virtual_events_t = std::array; - - VirtualEventType type; - manipulation_encode_t magnitude; - - static inline constexpr auto VirtualEventsTypeTable = []() - { - std::array output; - - for (uint16_t i = 0u; i < EventsCount; ++i) - { - output[i] = static_cast(i); - } - - return output; - }(); -}; - template class ICameraController : virtual public core::IReferenceCounted { public: - using matrix_precision_t = typename T; + using precision_t = typename T; - class CGimbal + // Gimbal with view parameters representing a camera in world space + class CGimbal : public IGimbal { public: - struct SCreationParameters - { - float32_t3 position; - glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); - bool withView = true; - }; + using base_t = IGimbal; + + CGimbal(typename base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) {} + ~CGimbal() = default; - // Gimbal's view matrix consists of an orthonormal basis (https://en.wikipedia.org/wiki/Orthonormal_basis) - // for orientation and a translation component that positions the world relative to the gimbal's position. - // Any camera type is supposed to manipulate a position & orientation of a gimbal - // with "virtual events" which model its view bound to the camera struct SView { - matrix matrix = {}; + matrix matrix = {}; bool isLeftHandSystem = true; }; - CGimbal(SCreationParameters&& parameters) - : m_position(parameters.position), m_orientation(parameters.orientation) - { - updateOrthonormalOrientationBase(); - - if (parameters.withView) - { - m_view = std::optional(SView{}); // RVO - updateView(); - } - } - - void begin() - { - m_isManipulating = true; - m_counter = 0u; - } - - inline void setPosition(const float32_t3& position) - { - assert(m_isManipulating); // TODO: log error and return without doing nothing - - if (m_position != position) - m_counter++; - - m_position = position; - } - - inline void setOrientation(const glm::quat& orientation) - { - assert(m_isManipulating); // TODO: log error and return without doing nothing - - if(m_orientation != orientation) - m_counter++; - - m_orientation = glm::normalize(orientation); - updateOrthonormalOrientationBase(); - } - - inline void rotate(const float32_t3& axis, float dRadians) - { - assert(m_isManipulating); // TODO: log error and return without doing nothing - - if(dRadians) - m_counter++; - - glm::quat dRotation = glm::angleAxis(dRadians, axis); - m_orientation = glm::normalize(dRotation * m_orientation); - updateOrthonormalOrientationBase(); - } - - inline void move(float32_t3 delta) - { - assert(m_isManipulating); // TODO: log error and return without doing nothing - - auto newPosition = m_position + delta; - - if (newPosition != m_position) - m_counter++; - - m_position = newPosition; - } - - void end() - { - m_isManipulating = false; - - if (m_counter > 0u) - updateView(); - - m_counter = 0u; - } - - // Position of gimbal - inline const float32_t3& getPosition() const { return m_position; } - - // Orientation of gimbal - inline const glm::quat& getOrientation() const { return m_orientation; } - - // Orthonormal [getXAxis(), getYAxis(), getZAxis()] orientation matrix - inline const float32_t3x3& getOrthonornalMatrix() const { return m_orthonormal; } - - // Base "right" vector in orthonormal orientation basis (X-axis) - inline const float32_t3& getXAxis() const { return m_orthonormal[0u]; } - - // Base "up" vector in orthonormal orientation basis (Y-axis) - inline const float32_t3& getYAxis() const { return m_orthonormal[1u]; } - - // Base "forward" vector in orthonormal orientation basis (Z-axis) - inline const float32_t3& getZAxis() const { return m_orthonormal[2u]; } - - // Optional view of a gimbal - inline const std::optional& getView() const { return m_view; } - - inline const size_t& getManipulationCounter() { return m_counter; } - inline bool isManipulating() const { return m_isManipulating; } - - private: - inline void updateOrthonormalOrientationBase() - { - m_orthonormal = matrix(glm::mat3_cast(glm::normalize(m_orientation))); - } - inline void updateView() { - if (m_view.has_value()) // TODO: this could be templated + constexpr actually if gimbal doesn't init this on runtime depending on sth + if (base_t::getManipulationCounter()) { - auto& view = m_view.value(); - const auto& gRight = getXAxis(), gUp = getYAxis(), gForward = getZAxis(); + const auto& gRight = base_t::getXAxis(), gUp = base_t::getYAxis(), gForward = base_t::getZAxis(); - // TODO: I think I will provide convert utility allowing to go from one hand system to another, its just a matter to take care of m_view->matrix[2u] to perform a LH/RH flip - // in general this should not know about projections which are now supposed to be independent and store reference to a camera (or own it) - view.isLeftHandSystem = hlsl::determinant(m_orthonormal) < 0.0f; + // TODO: I think this should be set as request state, depending on the value we do m_view.matrix[2u] flip accordingly + // m_view.isLeftHandSystem; auto isNormalized = [](const auto& v, float epsilon) -> bool { @@ -231,24 +62,18 @@ class ICameraController : virtual public core::IReferenceCounted assert(isOrthoBase(gRight, gUp, gForward)); - view.matrix[0u] = vector(gRight, -glm::dot(gRight, m_position)); - view.matrix[1u] = vector(gUp, -glm::dot(gUp, m_position)); - view.matrix[2u] = vector(gForward, -glm::dot(gForward, m_position)); + const auto& position = base_t::getPosition(); + m_view.matrix[0u] = vector(gRight, -glm::dot(gRight, position)); + m_view.matrix[1u] = vector(gUp, -glm::dot(gUp, position)); + m_view.matrix[2u] = vector(gForward, -glm::dot(gForward, position)); } } - float32_t3 m_position; - glm::quat m_orientation; - matrix m_orthonormal; + // Getter for gimbal's view + inline const SView& getView() const { return m_view; } - // For a camera implementation at least one gimbal models its view but not all gimbals (if multiple) are expected to do so - std::optional m_view = std::nullopt; - - // Counts *performed* manipulations, a manipulation with 0 delta is not counted! - size_t m_counter = {}; - - // Records manipulation state - bool m_isManipulating = false; + private: + SView m_view; }; ICameraController() {} diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp new file mode 100644 index 000000000..38d99ecf0 --- /dev/null +++ b/common/include/camera/IGimbal.hpp @@ -0,0 +1,193 @@ +#ifndef _NBL_IGIMBAL_HPP_ +#define _NBL_IGIMBAL_HPP_ + +#include "glm/glm/ext/matrix_transform.hpp" // TODO: TEMPORARY!!! whatever used will be moved to cpp +#include "glm/glm/gtc/quaternion.hpp" +#include "nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl" + +// TODO: DIFFERENT NAMESPACE +namespace nbl::hlsl +{ + struct CVirtualGimbalEvent + { + //! Virtual event representing a gimbal manipulation + enum VirtualEventType : uint8_t + { + // Strafe forward + MoveForward = 0, + + // Strafe backward + MoveBackward, + + // Strafe left + MoveLeft, + + // Strafe right + MoveRight, + + // Strafe up + MoveUp, + + // Strafe down + MoveDown, + + // Tilt the camera upward (pitch) + TiltUp, + + // Tilt the camera downward (pitch) + TiltDown, + + // Rotate the camera left around the vertical axis (yaw) + PanLeft, + + // Rotate the camera right around the vertical axis (yaw) + PanRight, + + // Roll the camera counterclockwise around the forward axis (roll) + RollLeft, + + // Roll the camera clockwise around the forward axis (roll) + RollRight, + + // TODO: scale events + + EventsCount + }; + + using manipulation_encode_t = float64_t; + using keys_to_virtual_events_t = std::array; + + VirtualEventType type; + manipulation_encode_t magnitude; + + static inline constexpr auto VirtualEventsTypeTable = []() + { + std::array output; + + for (uint16_t i = 0u; i < EventsCount; ++i) + output[i] = static_cast(i); + + return output; + }(); + }; + + template + requires is_any_of_v + class IGimbal + { + public: + using precision_t = T; + + struct SCreationParameters + { + vector position; + glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + }; + + IGimbal(SCreationParameters&& parameters) + : m_position(parameters.position), m_orientation(parameters.orientation), m_id(reinterpret_cast(this)) + { + updateOrthonormalOrientationBase(); + } + + inline const uintptr_t getID() const { return m_id; } + + void begin() + { + m_isManipulating = true; + m_counter = 0u; + } + + inline void setPosition(const vector& position) + { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if (m_position != position) + m_counter++; + + m_position = position; + } + + inline void setOrientation(const glm::quat& orientation) + { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if(m_orientation != orientation) + m_counter++; + + m_orientation = glm::normalize(orientation); + updateOrthonormalOrientationBase(); + } + + inline void rotate(const vector& axis, float dRadians) + { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if(dRadians) + m_counter++; + + glm::quat dRotation = glm::angleAxis(dRadians, axis); + m_orientation = glm::normalize(dRotation * m_orientation); + updateOrthonormalOrientationBase(); + } + + inline void move(vector delta) + { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + auto newPosition = m_position + delta; + + if (newPosition != m_position) + m_counter++; + + m_position = newPosition; + } + + void end() + { + m_isManipulating = false; + } + + // Position of gimbal + inline const auto& getPosition() const { return m_position; } + + // Orientation of gimbal + inline const auto& getOrientation() const { return m_orientation; } + + // Orthonormal [getXAxis(), getYAxis(), getZAxis()] orientation matrix + inline const auto& getOrthonornalMatrix() const { return m_orthonormal; } + + // Base "right" vector in orthonormal orientation basis (X-axis) + inline const auto& getXAxis() const { return m_orthonormal[0u]; } + + // Base "up" vector in orthonormal orientation basis (Y-axis) + inline const auto& getYAxis() const { return m_orthonormal[1u]; } + + // Base "forward" vector in orthonormal orientation basis (Z-axis) + inline const auto& getZAxis() const { return m_orthonormal[2u]; } + + inline const auto& getManipulationCounter() { return m_counter; } + inline bool isManipulating() const { return m_isManipulating; } + + private: + inline void updateOrthonormalOrientationBase() + { + m_orthonormal = matrix(glm::mat3_cast(glm::normalize(m_orientation))); + } + + vector m_position; + glm::quat m_orientation; // TODO: precision + matrix m_orthonormal; + + // Counts *performed* manipulations, a manipulation with 0 delta is not counted! + size_t m_counter = {}; + + // Records manipulation state + bool m_isManipulating = false; + + // the fact ImGUIZMO has global context I don't like, however for IDs we can do a life-tracking trick and cast addresses which are unique & we don't need any global associative container to track them! + const uintptr_t m_id; + }; +} // namespace nbl::hlsl + +#endif // _NBL_IGIMBAL_HPP_ \ No newline at end of file From 4428dbb6c4b03ea80aae746b3f7ba51ed040f027 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 6 Nov 2024 17:57:56 +0100 Subject: [PATCH 26/84] change keys lookup table, add scale events to CVirtualGimbalEvent::VirtualEventType + change it to bitmask, create IGimbal::VirtualImpulse & corresponding template accumulate method which may be useful as general purpose utility for various camera types, update CFPSCamera to use it, update sources --- common/include/CFPSCamera.hpp | 68 ++----- .../include/camera/CGeneralPurposeGimbal.hpp | 19 ++ common/include/camera/ICameraControl.hpp | 54 +++--- common/include/camera/IGimbal.hpp | 178 ++++++++++++++---- 4 files changed, 192 insertions(+), 127 deletions(-) create mode 100644 common/include/camera/CGeneralPurposeGimbal.hpp diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index e3ee863a0..5a367dfef 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -32,66 +32,30 @@ class CFPSCamera final : public ICamera virtual void manipulate(std::span virtualEvents) override { + constexpr auto AllowedEvents = CVirtualGimbalEvent::MoveForward | CVirtualGimbalEvent::MoveBackward | CVirtualGimbalEvent::MoveRight | CVirtualGimbalEvent::MoveLeft | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; + constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; + if (!virtualEvents.size()) return; - constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; + const auto impulse = m_gimbal.accumulate(virtualEvents); const auto& gForward = m_gimbal.getZAxis(), gRight = m_gimbal.getXAxis(); - struct - { - float dPitch = 0.f, dYaw = 0.f; - float32_t3 dMove = { 0.f, 0.f, 0.f }; - } accumulated; - - for (const auto& event : virtualEvents) - { - const float moveScalar = event.magnitude * MoveSpeedScale; - const float rotateScalar = event.magnitude * RotateSpeedScale; - - switch (event.type) - { - case CVirtualGimbalEvent::MoveForward: - accumulated.dMove += gForward * moveScalar; - break; - case CVirtualGimbalEvent::MoveBackward: - accumulated.dMove -= gForward * moveScalar; - break; - case CVirtualGimbalEvent::MoveRight: - accumulated.dMove += gRight * moveScalar; - break; - case CVirtualGimbalEvent::MoveLeft: - accumulated.dMove -= gRight * moveScalar; - break; - case CVirtualGimbalEvent::TiltUp: - accumulated.dPitch += rotateScalar; - break; - case CVirtualGimbalEvent::TiltDown: - accumulated.dPitch -= rotateScalar; - break; - case CVirtualGimbalEvent::PanRight: - accumulated.dYaw += rotateScalar; - break; - case CVirtualGimbalEvent::PanLeft: - accumulated.dYaw -= rotateScalar; - break; - default: - break; - } - } - float currentPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(); float currentYaw = atan2(gForward.x, gForward.z); - currentPitch = std::clamp(currentPitch + accumulated.dPitch, MinVerticalAngle, MaxVerticalAngle); - currentYaw += accumulated.dYaw; + // adjust the current pitch and yaw + currentPitch = std::clamp(currentPitch + impulse.dVirtualRotation.x * RotateSpeedScale, MinVerticalAngle, MaxVerticalAngle); + currentYaw += impulse.dVirtualRotation.y * RotateSpeedScale; + // create new orientation based on accumulated pitch and yaw from a virtual impulse glm::quat orientation = glm::quat(glm::vec3(currentPitch, currentYaw, 0.0f)); + // manipulate view gimbal m_gimbal.begin(); { m_gimbal.setOrientation(orientation); - m_gimbal.move(accumulated.dMove); + m_gimbal.move(impulse.dVirtualTranslate * MoveSpeedScale); m_gimbal.updateView(); } m_gimbal.end(); @@ -100,14 +64,12 @@ class CFPSCamera final : public ICamera private: void initKeysToEvent() override { - traits_t::controller_t::updateKeysToEvent([](CVirtualGimbalEvent::keys_to_virtual_events_t& keys) + traits_t::controller_t::updateKeysToEvent([](traits_t::controller_t::keys_to_virtual_events_t& keys) { - keys[CVirtualGimbalEvent::MoveForward] = ui::E_KEY_CODE::EKC_W; - keys[CVirtualGimbalEvent::MoveBackward] = ui::E_KEY_CODE::EKC_S; - keys[CVirtualGimbalEvent::MoveLeft] = ui::E_KEY_CODE::EKC_A; - keys[CVirtualGimbalEvent::MoveRight] = ui::E_KEY_CODE::EKC_D; - keys[CVirtualGimbalEvent::MoveUp] = ui::E_KEY_CODE::EKC_SPACE; - keys[CVirtualGimbalEvent::MoveDown] = ui::E_KEY_CODE::EKC_LEFT_SHIFT; + keys[ui::E_KEY_CODE::EKC_W] = CVirtualGimbalEvent::MoveForward; + keys[ui::E_KEY_CODE::EKC_S] = CVirtualGimbalEvent::MoveBackward; + keys[ui::E_KEY_CODE::EKC_A] = CVirtualGimbalEvent::MoveLeft; + keys[ui::E_KEY_CODE::EKC_D] = CVirtualGimbalEvent::MoveRight; }); } diff --git a/common/include/camera/CGeneralPurposeGimbal.hpp b/common/include/camera/CGeneralPurposeGimbal.hpp new file mode 100644 index 000000000..9a66d5712 --- /dev/null +++ b/common/include/camera/CGeneralPurposeGimbal.hpp @@ -0,0 +1,19 @@ +#ifndef _NBL_CGENERAL_PURPOSE_GIMBAL_HPP_ +#define _NBL_CGENERAL_PURPOSE_GIMBAL_HPP_ + +#include "IGimbal.hpp" + +// TODO: DIFFERENT NAMESPACE +namespace nbl::hlsl +{ + template + class CGeneralPurposeGimbal : public IGimbal + { + using base_t = IGimbal; + + CGeneralPurposeGimbal(base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) {} + ~CGeneralPurposeGimbal() = default; + }; +} + +#endif // _NBL_IGIMBAL_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index af1fc9fbe..da40d04ac 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -19,6 +19,7 @@ class ICameraController : virtual public core::IReferenceCounted { public: using precision_t = typename T; + using keys_to_virtual_events_t = std::unordered_map; // Gimbal with view parameters representing a camera in world space class CGimbal : public IGimbal @@ -78,8 +79,8 @@ class ICameraController : virtual public core::IReferenceCounted ICameraController() {} - // Binds key codes to virtual events, the mapKeys lambda will be executed with controller CVirtualGimbalEvent::keys_to_virtual_events_t table - void updateKeysToEvent(const std::function& mapKeys) + // Binds key codes to virtual events, the mapKeys lambda will be executed with controller keys_to_virtual_events_t table + void updateKeysToEvent(const std::function& mapKeys) { mapKeys(m_keysToVirtualEvents); } @@ -95,7 +96,7 @@ class ICameraController : virtual public core::IReferenceCounted { if (!output) { - count = CVirtualGimbalEvent::EventsCount; + count = events.size(); return; } @@ -106,49 +107,36 @@ class ICameraController : virtual public core::IReferenceCounted const auto timestamp = getEventGenerationTimestamp(); - for (const auto virtualEventType : CVirtualGimbalEvent::VirtualEventsTypeTable) + for (const auto& keyboardEvent : events) { - const auto code = m_keysToVirtualEvents[virtualEventType]; - bool& keyDown = m_keysDown[virtualEventType]; + auto request = m_keysToVirtualEvents.find(keyboardEvent.keyCode); + bool isKeyMapped = request != std::end(m_keysToVirtualEvents); - using virtual_key_state_t = std::tuple; - - auto updateVirtualState = [&]() -> virtual_key_state_t + if (isKeyMapped) { - virtual_key_state_t state = { ui::E_KEY_CODE::EKC_NONE, false, 0.f }; + auto& key = request->first; auto& info = request->second; - for (const auto& ev : events) // TODO: improve the search + if (keyboardEvent.keyCode == key) { - if (ev.keyCode == code) - { - if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !keyDown) - keyDown = true; - else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - keyDown = false; - - const auto dt = std::chrono::duration_cast(timestamp - ev.timeStamp).count(); - assert(dt >= 0); - - state = std::make_tuple(code, keyDown, dt); - break; - } + if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !info.active) + info.active = true; + else if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) + info.active = false; } - return state; - }; - - const auto&& [physicalKey, isDown, dtAction] = updateVirtualState(); - - if (physicalKey != ui::E_KEY_CODE::EKC_NONE) - if (isDown) + if (info.active) { + const auto dtAction = std::chrono::duration_cast(timestamp - keyboardEvent.timeStamp).count(); + assert(dtAction >= 0); + auto* virtualEvent = output + count; assert(virtualEvent); // TODO: maybe just log error and return 0 count - virtualEvent->type = virtualEventType; + virtualEvent->type = info.type; virtualEvent->magnitude = static_cast(dtAction); ++count; } + } } } @@ -200,7 +188,7 @@ class ICameraController : virtual public core::IReferenceCounted virtual void initKeysToEvent() = 0; private: - CVirtualGimbalEvent::keys_to_virtual_events_t m_keysToVirtualEvents = { { ui::E_KEY_CODE::EKC_NONE } }; + keys_to_virtual_events_t m_keysToVirtualEvents; bool m_keysDown[CVirtualGimbalEvent::EventsCount] = {}; // exactly what our Nabla events do, actually I don't want users to pass timestamp since I know when it should be best to make a request -> just before generating events! diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index 38d99ecf0..db108ed7b 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -10,53 +10,50 @@ namespace nbl::hlsl { struct CVirtualGimbalEvent { - //! Virtual event representing a gimbal manipulation - enum VirtualEventType : uint8_t + enum VirtualEventType : uint32_t { - // Strafe forward - MoveForward = 0, - - // Strafe backward - MoveBackward, - - // Strafe left - MoveLeft, - - // Strafe right - MoveRight, - - // Strafe up - MoveUp, - - // Strafe down - MoveDown, - - // Tilt the camera upward (pitch) - TiltUp, - - // Tilt the camera downward (pitch) - TiltDown, - - // Rotate the camera left around the vertical axis (yaw) - PanLeft, - - // Rotate the camera right around the vertical axis (yaw) - PanRight, - - // Roll the camera counterclockwise around the forward axis (roll) - RollLeft, - - // Roll the camera clockwise around the forward axis (roll) - RollRight, + None = 0, + + // Individual events + MoveForward = core::createBitmask({ 0 }), + MoveBackward = core::createBitmask({ 1 }), + MoveLeft = core::createBitmask({ 2 }), + MoveRight = core::createBitmask({ 3 }), + MoveUp = core::createBitmask({ 4 }), + MoveDown = core::createBitmask({ 5 }), + TiltUp = core::createBitmask({ 6 }), + TiltDown = core::createBitmask({ 7 }), + PanLeft = core::createBitmask({ 8 }), + PanRight = core::createBitmask({ 9 }), + RollLeft = core::createBitmask({ 10 }), + RollRight = core::createBitmask({ 11 }), + ScaleXInc = core::createBitmask({ 12 }), + ScaleXDec = core::createBitmask({ 13 }), + ScaleYInc = core::createBitmask({ 14 }), + ScaleYDec = core::createBitmask({ 15 }), + ScaleZInc = core::createBitmask({ 16 }), + ScaleZDec = core::createBitmask({ 17 }), + + EventsCount = 18, + + // Grouped bitmasks + Translate = MoveForward | MoveBackward | MoveLeft | MoveRight | MoveUp | MoveDown, + Rotate = TiltUp | TiltDown | PanLeft | PanRight | RollLeft | RollRight, + Scale = ScaleXInc | ScaleXDec | ScaleYInc | ScaleYDec | ScaleZInc | ScaleZDec + }; - // TODO: scale events + struct CRequestInfo + { + CRequestInfo() : type(None) {} + CRequestInfo(VirtualEventType _type) : type(_type) {} + ~CRequestInfo() = default; - EventsCount + VirtualEventType type; + bool active = false; }; using manipulation_encode_t = float64_t; - using keys_to_virtual_events_t = std::array; - + VirtualEventType type; manipulation_encode_t magnitude; @@ -78,6 +75,105 @@ namespace nbl::hlsl public: using precision_t = T; + struct VirtualImpulse + { + vector dVirtualTranslate { 0.0f }, dVirtualRotation { 0.0f }, dVirtualScale { 0.0f }; + }; + + template + VirtualImpulse accumulate(std::span virtualEvents, const vector& gRightOverride, const vector& gUpOverride, const vector& gForwardOverride) + { + VirtualImpulse impulse; + + const auto& gRight = gRightOverride, gUp = gUpOverride, gForward = gForwardOverride; + + for (const auto& event : virtualEvents) + { + // translation events + if constexpr (AllowedEvents & CVirtualGimbalEvent::MoveForward) + if (event.type == CVirtualGimbalEvent::MoveForward) + impulse.dVirtualTranslate += gForward * static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::MoveBackward) + if (event.type == CVirtualGimbalEvent::MoveBackward) + impulse.dVirtualTranslate -= gForward * static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::MoveRight) + if (event.type == CVirtualGimbalEvent::MoveRight) + impulse.dVirtualTranslate += gRight * static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::MoveLeft) + if (event.type == CVirtualGimbalEvent::MoveLeft) + impulse.dVirtualTranslate -= gRight * static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::MoveUp) + if (event.type == CVirtualGimbalEvent::MoveUp) + impulse.dVirtualTranslate += gUp * static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::MoveDown) + if (event.type == CVirtualGimbalEvent::MoveDown) + impulse.dVirtualTranslate -= gUp * static_cast(event.magnitude); + + // rotation events + if constexpr (AllowedEvents & CVirtualGimbalEvent::TiltUp) + if (event.type == CVirtualGimbalEvent::TiltUp) + impulse.dVirtualRotation.x += static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::TiltDown) + if (event.type == CVirtualGimbalEvent::TiltDown) + impulse.dVirtualRotation.x -= static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::PanRight) + if (event.type == CVirtualGimbalEvent::PanRight) + impulse.dVirtualRotation.y += static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::PanLeft) + if (event.type == CVirtualGimbalEvent::PanLeft) + impulse.dVirtualRotation.y -= static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::RollRight) + if (event.type == CVirtualGimbalEvent::RollRight) + impulse.dVirtualRotation.z += static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::RollLeft) + if (event.type == CVirtualGimbalEvent::RollLeft) + impulse.dVirtualRotation.z -= static_cast(event.magnitude); + + // scaling events + if constexpr (AllowedEvents & CVirtualGimbalEvent::ScaleXInc) + if (event.type == CVirtualGimbalEvent::ScaleXInc) + impulse.dVirtualScale.x += static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::ScaleXDec) + if (event.type == CVirtualGimbalEvent::ScaleXDec) + impulse.dVirtualScale.x -= static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::ScaleYInc) + if (event.type == CVirtualGimbalEvent::ScaleYInc) + impulse.dVirtualScale.y += static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::ScaleYDec) + if (event.type == CVirtualGimbalEvent::ScaleYDec) + impulse.dVirtualScale.y -= static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::ScaleZInc) + if (event.type == CVirtualGimbalEvent::ScaleZInc) + impulse.dVirtualScale.z += static_cast(event.magnitude); + + if constexpr (AllowedEvents & CVirtualGimbalEvent::ScaleZDec) + if (event.type == CVirtualGimbalEvent::ScaleZDec) + impulse.dVirtualScale.z -= static_cast(event.magnitude); + } + + return impulse; + } + + template + VirtualImpulse accumulate(std::span virtualEvents) + { + return accumulate(virtualEvents, getXAxis(), getYAxis(), getZAxis()); + } + struct SCreationParameters { vector position; From 32d89b8bbacdb71f61f106d4e39bfefcb1c0dc1c Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 6 Nov 2024 18:28:09 +0100 Subject: [PATCH 27/84] lets call manipulate only one by putting all virtual events into single vector & offsetting --- 61_UI/main.cpp | 9 ++++----- common/include/CFPSCamera.hpp | 16 ++++------------ 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index e18185453..41d25c8ea 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -750,14 +750,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (move) { - static std::vector virtualMouseEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size()), virtualKeyboardEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size()); + static std::vector virtualEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size() * 2); uint32_t vEventsMouseCount, vEventsKeyboardCount; - camera->processMouse(virtualMouseEvents.data(), vEventsMouseCount, params.mouseEvents); - camera->processKeyboard(virtualKeyboardEvents.data(), vEventsKeyboardCount, params.keyboardEvents); + camera->processMouse(virtualEvents.data(), vEventsMouseCount, params.mouseEvents); + camera->processKeyboard(virtualEvents.data() + vEventsMouseCount, vEventsKeyboardCount, params.keyboardEvents); - camera->manipulate({ virtualMouseEvents.data(), vEventsMouseCount }); - camera->manipulate({ virtualKeyboardEvents.data(), vEventsKeyboardCount }); + camera->manipulate({ virtualEvents.data(), vEventsMouseCount + vEventsKeyboardCount }); } pass.ui.manager->update(params); diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 5a367dfef..486063dab 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -39,22 +39,14 @@ class CFPSCamera final : public ICamera return; const auto impulse = m_gimbal.accumulate(virtualEvents); - const auto& gForward = m_gimbal.getZAxis(), gRight = m_gimbal.getXAxis(); - float currentPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(); - float currentYaw = atan2(gForward.x, gForward.z); + const auto& gForward = m_gimbal.getZAxis(); + const float currentPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(), currentYaw = atan2(gForward.x, gForward.z); + const auto newPitch = std::clamp(currentPitch + impulse.dVirtualRotation.x * RotateSpeedScale, MinVerticalAngle, MaxVerticalAngle), newYaw = currentYaw + impulse.dVirtualRotation.y * RotateSpeedScale; - // adjust the current pitch and yaw - currentPitch = std::clamp(currentPitch + impulse.dVirtualRotation.x * RotateSpeedScale, MinVerticalAngle, MaxVerticalAngle); - currentYaw += impulse.dVirtualRotation.y * RotateSpeedScale; - - // create new orientation based on accumulated pitch and yaw from a virtual impulse - glm::quat orientation = glm::quat(glm::vec3(currentPitch, currentYaw, 0.0f)); - - // manipulate view gimbal m_gimbal.begin(); { - m_gimbal.setOrientation(orientation); + m_gimbal.setOrientation(glm::quat(glm::vec3(newPitch, newYaw, 0.0f))); m_gimbal.move(impulse.dVirtualTranslate * MoveSpeedScale); m_gimbal.updateView(); } From 58099cff266a1d0884799b5d64e7239df81d2d1f Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 7 Nov 2024 09:56:44 +0100 Subject: [PATCH 28/84] Add key-mapping UI window. Virtual events handling fixed! I was not excepting filtering some events out could cause my issues which lead to glitches. Timestamps may not strictly follow order in which they were generated, filtering based on previous timestamp is actually not quite correct I think because it may lost some events such as in my case - release events when a lot of events from mouse & keyboard and generated at the same time. --- 61_UI/main.cpp | 57 +++++++++---- common/include/camera/ICameraControl.hpp | 100 +++++++++++++++-------- common/include/camera/IGimbal.hpp | 41 +++++++--- 3 files changed, 139 insertions(+), 59 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 41d25c8ea..334cc416a 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -493,6 +493,39 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } + { + ImGui::Begin("Key Mappings & Virtual States"); + + ImGui::Text("Key Mappings"); + ImGui::Separator(); + + const auto& keysToVirtualEvents = camera->getKeysToVirtualEvents(); + + for (const auto& [key, info] : keysToVirtualEvents) + { + const char physicalChar = ui::keyCodeToChar(key, true); + const auto eventName = CVirtualGimbalEvent::virtualEventToString(info.type); + + ImGui::Text("Key: %s", &physicalChar); + ImGui::SameLine(); + ImGui::Text("Virtual Event: %s", eventName.data()); + ImGui::SameLine(); + + if (info.active) + { + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Active"); + ImGui::SameLine(); + ImGui::Text("Delta Time: %.2f ms", info.dtAction); + } + else + { + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Inactive"); + } + } + + ImGui::End(); + } + ImGui::End(); } ); @@ -687,8 +720,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication inline void update() { - static std::chrono::microseconds previousEventTimestamp{}; - m_inputSystem->getDefaultMouse(&mouse); m_inputSystem->getDefaultKeyboard(&keyboard); @@ -707,18 +738,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication struct { - std::vector mouse{}; - std::vector keyboard{}; + std::vector mouse {}; + std::vector keyboard {}; } capturedEvents; mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void { - for (const auto& e : events) // here capture + for (const auto& e : events) { - if (e.timeStamp < previousEventTimestamp) - continue; - - previousEventTimestamp = e.timeStamp; capturedEvents.mouse.emplace_back(e); if (e.type == nbl::ui::SMouseEvent::EET_SCROLL) @@ -728,12 +755,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void { - for (const auto& e : events) // here capture + for (const auto& e : events) { - if (e.timeStamp < previousEventTimestamp) - continue; - - previousEventTimestamp = e.timeStamp; capturedEvents.keyboard.emplace_back(e); } }, m_logger.get()); @@ -753,8 +776,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication static std::vector virtualEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size() * 2); uint32_t vEventsMouseCount, vEventsKeyboardCount; - camera->processMouse(virtualEvents.data(), vEventsMouseCount, params.mouseEvents); - camera->processKeyboard(virtualEvents.data() + vEventsMouseCount, vEventsKeyboardCount, params.keyboardEvents); + camera->beginInputProcessing(nextPresentationTimestamp); + camera->processKeyboard(virtualEvents.data(), vEventsKeyboardCount, params.keyboardEvents); + camera->processMouse(virtualEvents.data() + vEventsKeyboardCount, vEventsMouseCount, params.mouseEvents); + camera->endInputProcessing(); camera->manipulate({ virtualEvents.data(), vEventsMouseCount + vEventsKeyboardCount }); } diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index da40d04ac..71e27773e 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -19,7 +19,19 @@ class ICameraController : virtual public core::IReferenceCounted { public: using precision_t = typename T; - using keys_to_virtual_events_t = std::unordered_map; + + struct CRequestInfo + { + CRequestInfo() {} + CRequestInfo(CVirtualGimbalEvent::VirtualEventType _type) : type(_type) {} + ~CRequestInfo() = default; + + CVirtualGimbalEvent::VirtualEventType type = CVirtualGimbalEvent::VirtualEventType::None; + bool active = false; + float64_t dtAction = {}; + }; + + using keys_to_virtual_events_t = std::unordered_map; // Gimbal with view parameters representing a camera in world space class CGimbal : public IGimbal @@ -91,51 +103,70 @@ class ICameraController : virtual public core::IReferenceCounted // TODO: *maybe* would be good to have a class interface for virtual event generators, // eg keyboard, mouse but maybe custom stuff too eg events from gimbal drag & drop + void beginInputProcessing(const std::chrono::microseconds _nextPresentationTimeStamp) + { + nextPresentationTimeStamp = _nextPresentationTimeStamp; + } + // Processes keyboard events to generate virtual manipulation events, note that it doesn't make the manipulation itself! void processKeyboard(CVirtualGimbalEvent* output, uint32_t& count, std::span events) { + count = 0u; + if (!output) { - count = events.size(); + count = m_keysToVirtualEvents.size(); return; } - count = 0u; + const auto frameDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); + assert(frameDeltaTime >= 0.f); - if (events.empty()) - return; + for (auto& [key, info] : m_keysToVirtualEvents) + { + info.dtAction = 0.f; - const auto timestamp = getEventGenerationTimestamp(); + /* + if a key was already being held down from previous frames we compute with this + assumption that the key will be held down for this whole frame as well and its + delta action time is simply frame delta time + */ + + if (info.active) + info.dtAction = static_cast(frameDeltaTime); + } for (const auto& keyboardEvent : events) { auto request = m_keysToVirtualEvents.find(keyboardEvent.keyCode); - bool isKeyMapped = request != std::end(m_keysToVirtualEvents); - - if (isKeyMapped) + if (request != std::end(m_keysToVirtualEvents)) { - auto& key = request->first; auto& info = request->second; + auto& info = request->second; - if (keyboardEvent.keyCode == key) + if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_PRESSED) { - if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !info.active) + if (!info.active) + { + const auto keyDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - keyboardEvent.timeStamp).count(); + assert(keyDeltaTime >= 0); + info.active = true; - else if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - info.active = false; + info.dtAction = keyDeltaTime; + } } + else if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) + info.active = false; + } + } - if (info.active) - { - const auto dtAction = std::chrono::duration_cast(timestamp - keyboardEvent.timeStamp).count(); - assert(dtAction >= 0); - - auto* virtualEvent = output + count; - assert(virtualEvent); // TODO: maybe just log error and return 0 count - - virtualEvent->type = info.type; - virtualEvent->magnitude = static_cast(dtAction); - ++count; - } + for (const auto& [key, info] : m_keysToVirtualEvents) + { + if (info.active) + { + auto* virtualEvent = output + count; + virtualEvent->type = info.type; + virtualEvent->magnitude = info.dtAction; + ++count; } } } @@ -144,18 +175,17 @@ class ICameraController : virtual public core::IReferenceCounted // Limited to Pan & Tilt rotation events, camera type implements how event magnitudes should be interpreted void processMouse(CVirtualGimbalEvent* output, uint32_t& count, std::span events) { + count = 0u; + if (!output) { count = 2u; return; } - count = 0u; - if (events.empty()) return; - const auto timestamp = getEventGenerationTimestamp(); double dYaw = {}, dPitch = {}; for (const auto& ev : events) @@ -184,16 +214,20 @@ class ICameraController : virtual public core::IReferenceCounted } } + void endInputProcessing() + { + lastVirtualUpTimeStamp = nextPresentationTimeStamp; + } + + inline const keys_to_virtual_events_t& getKeysToVirtualEvents() { return m_keysToVirtualEvents; } + protected: virtual void initKeysToEvent() = 0; private: keys_to_virtual_events_t m_keysToVirtualEvents; bool m_keysDown[CVirtualGimbalEvent::EventsCount] = {}; - - // exactly what our Nabla events do, actually I don't want users to pass timestamp since I know when it should be best to make a request -> just before generating events! - // TODO: need to think about this - inline std::chrono::microseconds getEventGenerationTimestamp() { return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); } + std::chrono::microseconds nextPresentationTimeStamp, lastVirtualUpTimeStamp; }; #if 0 // TOOD: update diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index db108ed7b..4f84e6013 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -42,21 +42,42 @@ namespace nbl::hlsl Scale = ScaleXInc | ScaleXDec | ScaleYInc | ScaleYDec | ScaleZInc | ScaleZDec }; - struct CRequestInfo - { - CRequestInfo() : type(None) {} - CRequestInfo(VirtualEventType _type) : type(_type) {} - ~CRequestInfo() = default; - - VirtualEventType type; - bool active = false; - }; - using manipulation_encode_t = float64_t; VirtualEventType type; manipulation_encode_t magnitude; + static constexpr std::string_view virtualEventToString(VirtualEventType event) + { + switch (event) + { + case MoveForward: return "MoveForward"; + case MoveBackward: return "MoveBackward"; + case MoveLeft: return "MoveLeft"; + case MoveRight: return "MoveRight"; + case MoveUp: return "MoveUp"; + case MoveDown: return "MoveDown"; + case TiltUp: return "TiltUp"; + case TiltDown: return "TiltDown"; + case PanLeft: return "PanLeft"; + case PanRight: return "PanRight"; + case RollLeft: return "RollLeft"; + case RollRight: return "RollRight"; + case ScaleXInc: return "ScaleXInc"; + case ScaleXDec: return "ScaleXDec"; + case ScaleYInc: return "ScaleYInc"; + case ScaleYDec: return "ScaleYDec"; + case ScaleZInc: return "ScaleZInc"; + case ScaleZDec: return "ScaleZDec"; + case Translate: return "Translate"; + case Rotate: return "Rotate"; + case Scale: return "Scale"; + case None: return "None"; + default: return "Unknown"; + } + } + + static inline constexpr auto VirtualEventsTypeTable = []() { std::array output; From 1ed826f6ac0990b89ded5a4d9610ad0232e47a3b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 7 Nov 2024 20:59:07 +0100 Subject: [PATCH 29/84] create keysmapping.hpp with displayKeyMappingsAndVirtualStates utility for a camera, update CVirtualGimbalEvent::VirtualEventType, fix a bitmask bug, add pure virtual getAllowedVirtualEvents to ICamera interface, update sources --- 61_UI/include/common.hpp | 4 + 61_UI/include/keysmapping.hpp | 205 +++++++++++++++++++++++ 61_UI/main.cpp | 35 +--- common/include/CFPSCamera.hpp | 13 +- common/include/ICamera.hpp | 3 + common/include/camera/ICameraControl.hpp | 82 ++++----- common/include/camera/IGimbal.hpp | 7 +- 7 files changed, 272 insertions(+), 77 deletions(-) create mode 100644 61_UI/include/keysmapping.hpp diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index cf8afeea8..eb1a58c14 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -3,6 +3,8 @@ #include +#include + // common api #include "CFPSCamera.hpp" #include "SimpleWindowedApplication.hpp" @@ -22,4 +24,6 @@ using namespace video; using namespace scene; using namespace geometrycreator; +using matrix_precision_t = float32_t; + #endif // __NBL_THIS_EXAMPLE_COMMON_H_INCLUDED__ \ No newline at end of file diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp new file mode 100644 index 000000000..c2466853e --- /dev/null +++ b/61_UI/include/keysmapping.hpp @@ -0,0 +1,205 @@ +#ifndef __NBL_KEYSMAPPING_H_INCLUDED__ +#define __NBL_KEYSMAPPING_H_INCLUDED__ + +#include "common.hpp" + +template +void displayKeyMappingsAndVirtualStates(ICamera* camera) +{ + static bool addMode = false; + static bool pendingChanges = false; + static std::unordered_map> tempKeyMappings; + static CVirtualGimbalEvent::VirtualEventType selectedEventType = CVirtualGimbalEvent::VirtualEventType::MoveForward; + static ui::E_KEY_CODE newKey = ui::E_KEY_CODE::EKC_A; + + const uint32_t allowedEventsMask = camera->getAllowedVirtualEvents(); + + std::vector allowedEvents; + for (const auto& eventType : CVirtualGimbalEvent::VirtualEventsTypeTable) + if ((eventType & allowedEventsMask)) + allowedEvents.push_back(eventType); + + ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(600, 69000)); + + ImGui::Begin("Key Mappings & Virtual States", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysVerticalScrollbar); + + ImVec2 windowPadding = ImGui::GetStyle().WindowPadding; + float verticalPadding = ImGui::GetStyle().FramePadding.y; + + if (ImGui::Button("Add key", ImVec2(100, 30))) + addMode = !addMode; + + ImGui::Separator(); + + ImGui::BeginTable("KeyMappingsTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); + ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Key(s)", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Active Status", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Delta Time (ms)", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableHeadersRow(); + + const auto& keysToVirtualEvents = camera->getKeysToVirtualEvents(); + for (const auto& eventType : allowedEvents) + { + auto it = std::find_if(keysToVirtualEvents.begin(), keysToVirtualEvents.end(), [eventType](const auto& pair) + { + return pair.second.type == eventType; + }); + + if (it != keysToVirtualEvents.end()) + { + ImGui::TableNextRow(); + + const char* eventName = CVirtualGimbalEvent::virtualEventToString(eventType).data(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(eventName).x) * 0.5f); + ImGui::TextWrapped("%s", eventName); + + ImGui::TableSetColumnIndex(1); + std::vector mappedKeys; + for (const auto& [key, info] : keysToVirtualEvents) + if (info.type == eventType) + mappedKeys.push_back(key); + + if (!mappedKeys.empty()) + { + std::string concatenatedKeys; + for (size_t i = 0; i < mappedKeys.size(); ++i) + { + if (i > 0) + concatenatedKeys += " | "; + if (keysToVirtualEvents.at(mappedKeys[i]).active) + concatenatedKeys += "[" + std::string(1, ui::keyCodeToChar(mappedKeys[i], true)) + "]"; + else + concatenatedKeys += std::string(1, ui::keyCodeToChar(mappedKeys[i], true)); + } + ImGui::AlignTextToFramePadding(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(concatenatedKeys.c_str()).x) * 0.5f); + ImGui::TextWrapped("%s", concatenatedKeys.c_str()); + } + + ImGui::TableSetColumnIndex(2); + ImVec4 statusColor = it->second.active ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + const char* statusText = it->second.active ? "Active" : "Inactive"; + ImGui::AlignTextToFramePadding(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(statusText).x) * 0.5f); + ImGui::TextColored(statusColor, "%s", statusText); + + ImGui::TableSetColumnIndex(3); + float accumulatedDelta = 0.0f; + for (const auto& [key, info] : keysToVirtualEvents) + if (info.type == eventType) + accumulatedDelta += info.dtAction; + + char deltaText[16]; + snprintf(deltaText, 16, "%.2f", accumulatedDelta); + ImGui::AlignTextToFramePadding(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(deltaText).x) * 0.5f); + ImGui::TextWrapped("%s", deltaText); + + ImGui::TableSetColumnIndex(4); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(5, 5)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - 80) * 0.5f); + if (ImGui::Button(std::string("Delete##deleteKey" + std::to_string(static_cast(eventType))).c_str(), ImVec2(80, 30))) + { + camera->updateKeysToEvent([&](auto& keys) + { + for (auto it = keys.begin(); it != keys.end();) + { + if (it->second.type == eventType) + it = keys.erase(it); + else + ++it; + } + }); + pendingChanges = true; + } + ImGui::PopStyleVar(); + } + } + ImGui::EndTable(); + + if (addMode) + { + ImGui::Separator(); + + ImGui::BeginTable("AddKeyMappingTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); + ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.33f); + ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthStretch, 0.33f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.33f); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + verticalPadding); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 2 * ImGui::GetStyle().FramePadding.x); + if (ImGui::BeginCombo("##selectEvent", CVirtualGimbalEvent::virtualEventToString(selectedEventType).data(), ImGuiComboFlags_None)) + { + for (const auto& eventType : allowedEvents) + { + bool isSelected = (selectedEventType == eventType); + if (ImGui::Selectable(CVirtualGimbalEvent::virtualEventToString(eventType).data(), isSelected)) + selectedEventType = eventType; + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + + ImGui::TableSetColumnIndex(1); + ImGui::AlignTextToFramePadding(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + verticalPadding); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 2 * ImGui::GetStyle().FramePadding.x); + char newKeyDisplay[2] = { ui::keyCodeToChar(newKey, true), '\0' }; + if (ImGui::BeginCombo("##selectKey", newKeyDisplay, ImGuiComboFlags_None)) + { + for (int i = ui::E_KEY_CODE::EKC_A; i <= ui::E_KEY_CODE::EKC_Z; ++i) + { + bool isSelected = (newKey == static_cast(i)); + char label[2] = { ui::keyCodeToChar(static_cast(i), true), '\0' }; + if (ImGui::Selectable(label, isSelected)) + { + auto duplicateKey = std::find_if(tempKeyMappings[selectedEventType].begin(), tempKeyMappings[selectedEventType].end(), + [i](const auto& key) { + return key == static_cast(i); + }); + + if (duplicateKey == tempKeyMappings[selectedEventType].end()) + newKey = static_cast(i); + } + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + + ImGui::TableSetColumnIndex(2); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(5, 5)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - 100) * 0.5f); + if (ImGui::Button("Confirm Add", ImVec2(100, 30))) + { + tempKeyMappings[selectedEventType].push_back(newKey); + pendingChanges = true; + addMode = false; + + camera->updateKeysToEvent([&](auto& keys) + { + keys.emplace(newKey, selectedEventType); + }); + } + ImGui::PopStyleVar(); + + ImGui::EndTable(); + } + + ImGui::Dummy(ImVec2(0.0f, verticalPadding)); + ImGui::End(); +} + +#endif // __NBL_KEYSMAPPING_H_INCLUDED__ \ No newline at end of file diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 334cc416a..e7a4f3305 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -3,11 +3,11 @@ // For conditions of distribution and use, see copyright notice in nabla.h #include "common.hpp" +#include "keysmapping.hpp" #include "camera/CCubeProjection.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING // FPS Camera, TESTS -using matrix_precision_t = float32_t; using camera_t = CFPSCamera; using projection_t = IProjection>; // TODO: temporary -> projections will own/reference cameras @@ -493,38 +493,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } - { - ImGui::Begin("Key Mappings & Virtual States"); - - ImGui::Text("Key Mappings"); - ImGui::Separator(); - - const auto& keysToVirtualEvents = camera->getKeysToVirtualEvents(); - - for (const auto& [key, info] : keysToVirtualEvents) - { - const char physicalChar = ui::keyCodeToChar(key, true); - const auto eventName = CVirtualGimbalEvent::virtualEventToString(info.type); - - ImGui::Text("Key: %s", &physicalChar); - ImGui::SameLine(); - ImGui::Text("Virtual Event: %s", eventName.data()); - ImGui::SameLine(); - - if (info.active) - { - ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Active"); - ImGui::SameLine(); - ImGui::Text("Delta Time: %.2f ms", info.dtAction); - } - else - { - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Inactive"); - } - } - - ImGui::End(); - } + displayKeyMappingsAndVirtualStates(camera.get()); ImGui::End(); } diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 486063dab..37024b6dd 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -32,13 +32,12 @@ class CFPSCamera final : public ICamera virtual void manipulate(std::span virtualEvents) override { - constexpr auto AllowedEvents = CVirtualGimbalEvent::MoveForward | CVirtualGimbalEvent::MoveBackward | CVirtualGimbalEvent::MoveRight | CVirtualGimbalEvent::MoveLeft | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; if (!virtualEvents.size()) return; - const auto impulse = m_gimbal.accumulate(virtualEvents); + const auto impulse = m_gimbal.accumulate(virtualEvents); const auto& gForward = m_gimbal.getZAxis(); const float currentPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(), currentYaw = atan2(gForward.x, gForward.z); @@ -53,6 +52,11 @@ class CFPSCamera final : public ICamera m_gimbal.end(); } + virtual const uint32_t getAllowedVirtualEvents() override + { + return AllowedVirtualEvents; + } + private: void initKeysToEvent() override { @@ -62,10 +66,15 @@ class CFPSCamera final : public ICamera keys[ui::E_KEY_CODE::EKC_S] = CVirtualGimbalEvent::MoveBackward; keys[ui::E_KEY_CODE::EKC_A] = CVirtualGimbalEvent::MoveLeft; keys[ui::E_KEY_CODE::EKC_D] = CVirtualGimbalEvent::MoveRight; + keys[ui::E_KEY_CODE::EKC_I] = CVirtualGimbalEvent::TiltDown; + keys[ui::E_KEY_CODE::EKC_K] = CVirtualGimbalEvent::TiltUp; + keys[ui::E_KEY_CODE::EKC_J] = CVirtualGimbalEvent::PanLeft; + keys[ui::E_KEY_CODE::EKC_L] = CVirtualGimbalEvent::PanRight; }); } traits_t::gimbal_t m_gimbal; + static inline constexpr auto AllowedVirtualEvents = CVirtualGimbalEvent::MoveForward | CVirtualGimbalEvent::MoveBackward | CVirtualGimbalEvent::MoveRight | CVirtualGimbalEvent::MoveLeft | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; }; } diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index 4556a8097..e681ff26b 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -28,6 +28,9 @@ class ICamera : public ICameraController // Returns a gimbal which *models the camera view*, note that a camera type implementation may have multiple gimbals under the hood virtual const Traits::gimbal_t& getGimbal() = 0u; + + // VirtualEventType bitmask for a camera view gimbal manipulation requests filtering + virtual const uint32_t getAllowedVirtualEvents() = 0u; }; } diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index 71e27773e..f017eff14 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -112,61 +112,66 @@ class ICameraController : virtual public core::IReferenceCounted void processKeyboard(CVirtualGimbalEvent* output, uint32_t& count, std::span events) { count = 0u; + const auto mappedVirtualEventsCount = m_keysToVirtualEvents.size(); if (!output) { - count = m_keysToVirtualEvents.size(); + count = mappedVirtualEventsCount; return; } - const auto frameDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); - assert(frameDeltaTime >= 0.f); - - for (auto& [key, info] : m_keysToVirtualEvents) + if (mappedVirtualEventsCount) { - info.dtAction = 0.f; + const auto frameDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); + assert(frameDeltaTime >= 0.f); - /* - if a key was already being held down from previous frames we compute with this - assumption that the key will be held down for this whole frame as well and its - delta action time is simply frame delta time - */ + for (auto& [key, info] : m_keysToVirtualEvents) + { + info.dtAction = 0.f; - if (info.active) - info.dtAction = static_cast(frameDeltaTime); - } + /* + if a key was already being held down from previous frames we compute with this + assumption that the key will be held down for this whole frame as well and its + delta action is simply frame delta time + */ - for (const auto& keyboardEvent : events) - { - auto request = m_keysToVirtualEvents.find(keyboardEvent.keyCode); - if (request != std::end(m_keysToVirtualEvents)) - { - auto& info = request->second; + if (info.active) + info.dtAction = frameDeltaTime; + } - if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_PRESSED) + for (const auto& keyboardEvent : events) + { + auto request = m_keysToVirtualEvents.find(keyboardEvent.keyCode); + if (request != std::end(m_keysToVirtualEvents)) { - if (!info.active) - { - const auto keyDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - keyboardEvent.timeStamp).count(); - assert(keyDeltaTime >= 0); + auto& info = request->second; - info.active = true; - info.dtAction = keyDeltaTime; + if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_PRESSED) + { + if (!info.active) + { + // and if a key has been pressed after its last release event then first delta action is the key delta + const auto keyDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - keyboardEvent.timeStamp).count(); + assert(keyDeltaTime >= 0); + + info.active = true; + info.dtAction = keyDeltaTime; + } } + else if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) + info.active = false; } - else if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - info.active = false; } - } - for (const auto& [key, info] : m_keysToVirtualEvents) - { - if (info.active) + for (const auto& [key, info] : m_keysToVirtualEvents) { - auto* virtualEvent = output + count; - virtualEvent->type = info.type; - virtualEvent->magnitude = info.dtAction; - ++count; + if (info.active) + { + auto* virtualEvent = output + count; + virtualEvent->type = info.type; + virtualEvent->magnitude = info.dtAction; + ++count; + } } } } @@ -198,7 +203,6 @@ class ICameraController : virtual public core::IReferenceCounted if (dPitch) { auto* pitch = output + count; - assert(pitch); // TODO: maybe just log error and return 0 count pitch->type = dPitch > 0.f ? CVirtualGimbalEvent::TiltUp : CVirtualGimbalEvent::TiltDown; pitch->magnitude = std::abs(dPitch); count++; @@ -227,7 +231,7 @@ class ICameraController : virtual public core::IReferenceCounted private: keys_to_virtual_events_t m_keysToVirtualEvents; bool m_keysDown[CVirtualGimbalEvent::EventsCount] = {}; - std::chrono::microseconds nextPresentationTimeStamp, lastVirtualUpTimeStamp; + std::chrono::microseconds nextPresentationTimeStamp = {}, lastVirtualUpTimeStamp = {}; }; #if 0 // TOOD: update diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index 4f84e6013..fed8f9f7a 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -39,7 +39,9 @@ namespace nbl::hlsl // Grouped bitmasks Translate = MoveForward | MoveBackward | MoveLeft | MoveRight | MoveUp | MoveDown, Rotate = TiltUp | TiltDown | PanLeft | PanRight | RollLeft | RollRight, - Scale = ScaleXInc | ScaleXDec | ScaleYInc | ScaleYDec | ScaleZInc | ScaleZDec + Scale = ScaleXInc | ScaleXDec | ScaleYInc | ScaleYDec | ScaleZInc | ScaleZDec, + + All = Translate | Rotate | Scale }; using manipulation_encode_t = float64_t; @@ -77,13 +79,12 @@ namespace nbl::hlsl } } - static inline constexpr auto VirtualEventsTypeTable = []() { std::array output; for (uint16_t i = 0u; i < EventsCount; ++i) - output[i] = static_cast(i); + output[i] = static_cast(core::createBitmask({ i })); return output; }(); From 4d32863a11a727b255d9dfaa8592336edb4151b9 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 8 Nov 2024 16:26:30 +0100 Subject: [PATCH 30/84] create CCameraController, update ICameraController, have mouse controller which all codes can be mapped to virtual keys, update virtual mapping state utility to display & map controllers to virtual events (mouse & keyboard), remove controllers from camera interface --- 61_UI/include/common.hpp | 1 + 61_UI/include/keysmapping.hpp | 338 +++++++++++--------- 61_UI/main.cpp | 45 ++- common/include/CFPSCamera.hpp | 27 +- common/include/ICamera.hpp | 74 ++++- common/include/camera/CCameraController.hpp | 260 +++++++++++++++ common/include/camera/ICameraControl.hpp | 202 ++---------- common/include/camera/ICameraController.hpp | 68 ++++ common/include/camera/IGimbal.hpp | 4 +- 9 files changed, 638 insertions(+), 381 deletions(-) create mode 100644 common/include/camera/CCameraController.hpp create mode 100644 common/include/camera/ICameraController.hpp diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index eb1a58c14..b509031cf 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -7,6 +7,7 @@ // common api #include "CFPSCamera.hpp" +#include "camera/CCameraController.hpp" #include "SimpleWindowedApplication.hpp" #include "CEventCallback.hpp" diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp index c2466853e..d30fa6b9c 100644 --- a/61_UI/include/keysmapping.hpp +++ b/61_UI/include/keysmapping.hpp @@ -2,203 +2,225 @@ #define __NBL_KEYSMAPPING_H_INCLUDED__ #include "common.hpp" +#include "camera/CCameraController.hpp" + +template +void handleAddMapping(const char* tableID, CCameraController* controller, ICameraController::ControllerType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) +{ + ImGui::BeginTable(tableID, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); + ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.33f); + ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthStretch, 0.33f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.33f); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + if (ImGui::BeginCombo("##selectEvent", CVirtualGimbalEvent::virtualEventToString(selectedEventType).data())) + { + for (const auto& eventType : CVirtualGimbalEvent::VirtualEventsTypeTable) + { + bool isSelected = (selectedEventType == eventType); + if (ImGui::Selectable(CVirtualGimbalEvent::virtualEventToString(eventType).data(), isSelected)) + selectedEventType = eventType; + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + ImGui::TableSetColumnIndex(1); + if (activeController == ICameraController::Keyboard) + { + char newKeyDisplay[2] = { ui::keyCodeToChar(newKey, true), '\0' }; + if (ImGui::BeginCombo("##selectKey", newKeyDisplay)) + { + for (int i = ui::E_KEY_CODE::EKC_A; i <= ui::E_KEY_CODE::EKC_Z; ++i) + { + bool isSelected = (newKey == static_cast(i)); + char label[2] = { ui::keyCodeToChar(static_cast(i), true), '\0' }; + if (ImGui::Selectable(label, isSelected)) + newKey = static_cast(i); + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + else + { + if (ImGui::BeginCombo("##selectMouseKey", ui::mouseCodeToString(newMouseCode).data())) + { + for (int i = ui::EMC_LEFT_BUTTON; i < ui::EMC_COUNT; ++i) + { + bool isSelected = (newMouseCode == static_cast(i)); + if (ImGui::Selectable(ui::mouseCodeToString(static_cast(i)).data(), isSelected)) + newMouseCode = static_cast(i); + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + + ImGui::TableSetColumnIndex(2); + if (ImGui::Button("Confirm Add", ImVec2(100, 30))) + { + if (activeController == ICameraController::Keyboard) + controller->updateKeyboardMapping([&](auto& keys) { keys[newKey] = selectedEventType; }); + else + controller->updateMouseMapping([&](auto& mouse) { mouse[newMouseCode] = selectedEventType; }); + addMode = false; + } + + ImGui::EndTable(); +} template -void displayKeyMappingsAndVirtualStates(ICamera* camera) +void displayKeyMappingsAndVirtualStates(CCameraController* controller) { - static bool addMode = false; - static bool pendingChanges = false; - static std::unordered_map> tempKeyMappings; + static bool addMode = false, pendingChanges = false; static CVirtualGimbalEvent::VirtualEventType selectedEventType = CVirtualGimbalEvent::VirtualEventType::MoveForward; static ui::E_KEY_CODE newKey = ui::E_KEY_CODE::EKC_A; + static ui::E_MOUSE_CODE newMouseCode = ui::EMC_LEFT_BUTTON; + static ICameraController::ControllerType activeController = ICameraController::Keyboard; - const uint32_t allowedEventsMask = camera->getAllowedVirtualEvents(); - - std::vector allowedEvents; - for (const auto& eventType : CVirtualGimbalEvent::VirtualEventsTypeTable) - if ((eventType & allowedEventsMask)) - allowedEvents.push_back(eventType); + const auto& keyboardMappings = controller->getKeyboardVirtualEventMap(); + const auto& mouseMappings = controller->getMouseVirtualEventMap(); ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(600, 69000)); - ImGui::Begin("Key Mappings & Virtual States", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysVerticalScrollbar); + ImGui::Begin("Controller Mappings & Virtual States", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysVerticalScrollbar); - ImVec2 windowPadding = ImGui::GetStyle().WindowPadding; - float verticalPadding = ImGui::GetStyle().FramePadding.y; + if (ImGui::BeginTabBar("ControllersTabBar")) + { + if (ImGui::BeginTabItem("Keyboard")) + { + activeController = ICameraController::Keyboard; + ImGui::Separator(); - if (ImGui::Button("Add key", ImVec2(100, 30))) - addMode = !addMode; + if (ImGui::Button("Add key", ImVec2(100, 30))) + addMode = !addMode; - ImGui::Separator(); + ImGui::Separator(); - ImGui::BeginTable("KeyMappingsTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); - ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.2f); - ImGui::TableSetupColumn("Key(s)", ImGuiTableColumnFlags_WidthStretch, 0.2f); - ImGui::TableSetupColumn("Active Status", ImGuiTableColumnFlags_WidthStretch, 0.2f); - ImGui::TableSetupColumn("Delta Time (ms)", ImGuiTableColumnFlags_WidthStretch, 0.2f); - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.2f); - ImGui::TableHeadersRow(); + ImGui::BeginTable("KeyboardMappingsTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); + ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Key(s)", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Active Status", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Magnitude", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableHeadersRow(); - const auto& keysToVirtualEvents = camera->getKeysToVirtualEvents(); - for (const auto& eventType : allowedEvents) - { - auto it = std::find_if(keysToVirtualEvents.begin(), keysToVirtualEvents.end(), [eventType](const auto& pair) - { - return pair.second.type == eventType; - }); + for (const auto& [keyboardCode, hash] : keyboardMappings) + { + ImGui::TableNextRow(); - if (it != keysToVirtualEvents.end()) - { - ImGui::TableNextRow(); + const char* eventName = CVirtualGimbalEvent::virtualEventToString(hash.event.type).data(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", eventName); - const char* eventName = CVirtualGimbalEvent::virtualEventToString(eventType).data(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(eventName).x) * 0.5f); - ImGui::TextWrapped("%s", eventName); + ImGui::TableSetColumnIndex(1); + std::string keyString(1, ui::keyCodeToChar(keyboardCode, true)); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", keyString.c_str()); - ImGui::TableSetColumnIndex(1); - std::vector mappedKeys; - for (const auto& [key, info] : keysToVirtualEvents) - if (info.type == eventType) - mappedKeys.push_back(key); + ImGui::TableSetColumnIndex(2); + bool isActive = (hash.event.magnitude > 0); + ImVec4 statusColor = isActive ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + ImGui::TextColored(statusColor, "%s", isActive ? "Active" : "Inactive"); - if (!mappedKeys.empty()) - { - std::string concatenatedKeys; - for (size_t i = 0; i < mappedKeys.size(); ++i) - { - if (i > 0) - concatenatedKeys += " | "; - if (keysToVirtualEvents.at(mappedKeys[i]).active) - concatenatedKeys += "[" + std::string(1, ui::keyCodeToChar(mappedKeys[i], true)) + "]"; - else - concatenatedKeys += std::string(1, ui::keyCodeToChar(mappedKeys[i], true)); - } + ImGui::TableSetColumnIndex(3); + std::array deltaText; + snprintf(deltaText.data(), deltaText.size(), "%.2f", hash.event.magnitude); ImGui::AlignTextToFramePadding(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(concatenatedKeys.c_str()).x) * 0.5f); - ImGui::TextWrapped("%s", concatenatedKeys.c_str()); - } + ImGui::TextWrapped("%s", deltaText.data()); - ImGui::TableSetColumnIndex(2); - ImVec4 statusColor = it->second.active ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ImVec4(1.0f, 0.0f, 0.0f, 1.0f); - const char* statusText = it->second.active ? "Active" : "Inactive"; - ImGui::AlignTextToFramePadding(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(statusText).x) * 0.5f); - ImGui::TextColored(statusColor, "%s", statusText); - - ImGui::TableSetColumnIndex(3); - float accumulatedDelta = 0.0f; - for (const auto& [key, info] : keysToVirtualEvents) - if (info.type == eventType) - accumulatedDelta += info.dtAction; - - char deltaText[16]; - snprintf(deltaText, 16, "%.2f", accumulatedDelta); - ImGui::AlignTextToFramePadding(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(deltaText).x) * 0.5f); - ImGui::TextWrapped("%s", deltaText); - - ImGui::TableSetColumnIndex(4); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(5, 5)); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - 80) * 0.5f); - if (ImGui::Button(std::string("Delete##deleteKey" + std::to_string(static_cast(eventType))).c_str(), ImVec2(80, 30))) - { - camera->updateKeysToEvent([&](auto& keys) + ImGui::TableSetColumnIndex(4); + if (ImGui::Button(("Delete##deleteKey" + std::to_string(static_cast(hash.event.type))).c_str())) { - for (auto it = keys.begin(); it != keys.end();) - { - if (it->second.type == eventType) - it = keys.erase(it); - else - ++it; - } - }); - pendingChanges = true; + controller->updateKeyboardMapping([&](auto& keys) { keys.erase(keyboardCode); }); + pendingChanges = true; + break; + } } - ImGui::PopStyleVar(); - } - } - ImGui::EndTable(); + ImGui::EndTable(); - if (addMode) - { - ImGui::Separator(); - - ImGui::BeginTable("AddKeyMappingTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); - ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.33f); - ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthStretch, 0.33f); - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.33f); - ImGui::TableHeadersRow(); - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + verticalPadding); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 2 * ImGui::GetStyle().FramePadding.x); - if (ImGui::BeginCombo("##selectEvent", CVirtualGimbalEvent::virtualEventToString(selectedEventType).data(), ImGuiComboFlags_None)) - { - for (const auto& eventType : allowedEvents) + if (addMode) { - bool isSelected = (selectedEventType == eventType); - if (ImGui::Selectable(CVirtualGimbalEvent::virtualEventToString(eventType).data(), isSelected)) - selectedEventType = eventType; - if (isSelected) - ImGui::SetItemDefaultFocus(); + ImGui::Separator(); + handleAddMapping("AddKeyboardMappingTable", controller, activeController, selectedEventType, newKey, newMouseCode, addMode); } - ImGui::EndCombo(); + + ImGui::EndTabItem(); } - ImGui::PopItemWidth(); - ImGui::TableSetColumnIndex(1); - ImGui::AlignTextToFramePadding(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + verticalPadding); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 2 * ImGui::GetStyle().FramePadding.x); - char newKeyDisplay[2] = { ui::keyCodeToChar(newKey, true), '\0' }; - if (ImGui::BeginCombo("##selectKey", newKeyDisplay, ImGuiComboFlags_None)) + if (ImGui::BeginTabItem("Mouse")) { - for (int i = ui::E_KEY_CODE::EKC_A; i <= ui::E_KEY_CODE::EKC_Z; ++i) + activeController = ICameraController::Mouse; + ImGui::Separator(); + + if (ImGui::Button("Add key", ImVec2(100, 30))) + addMode = !addMode; + + ImGui::Separator(); + + ImGui::BeginTable("MouseMappingsTable", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); + ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Mouse Button(s)", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Active Status", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Magnitude", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch, 0.2f); + ImGui::TableHeadersRow(); + + for (const auto& [mouseCode, hash] : mouseMappings) { - bool isSelected = (newKey == static_cast(i)); - char label[2] = { ui::keyCodeToChar(static_cast(i), true), '\0' }; - if (ImGui::Selectable(label, isSelected)) - { - auto duplicateKey = std::find_if(tempKeyMappings[selectedEventType].begin(), tempKeyMappings[selectedEventType].end(), - [i](const auto& key) { - return key == static_cast(i); - }); + ImGui::TableNextRow(); + + const char* eventName = CVirtualGimbalEvent::virtualEventToString(hash.event.type).data(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", eventName); + + ImGui::TableSetColumnIndex(1); + const char* mouseButtonName = ui::mouseCodeToString(mouseCode).data(); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", mouseButtonName); - if (duplicateKey == tempKeyMappings[selectedEventType].end()) - newKey = static_cast(i); + ImGui::TableSetColumnIndex(2); + bool isActive = (hash.event.magnitude > 0); + ImVec4 statusColor = isActive ? ImVec4(0.0f, 1.0f, 0.0f, 1.0f) : ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + ImGui::TextColored(statusColor, "%s", isActive ? "Active" : "Inactive"); + + ImGui::TableSetColumnIndex(3); + std::array deltaText; + snprintf(deltaText.data(), deltaText.size(), "%.2f", hash.event.magnitude); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", deltaText.data()); + + ImGui::TableSetColumnIndex(4); + if (ImGui::Button(("Delete##deleteMouse" + std::to_string(static_cast(hash.event.type))).c_str())) + { + controller->updateMouseMapping([&](auto& mouse) { mouse.erase(mouseCode); }); + pendingChanges = true; + break; } - if (isSelected) - ImGui::SetItemDefaultFocus(); } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); + ImGui::EndTable(); - ImGui::TableSetColumnIndex(2); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(5, 5)); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - 100) * 0.5f); - if (ImGui::Button("Confirm Add", ImVec2(100, 30))) - { - tempKeyMappings[selectedEventType].push_back(newKey); - pendingChanges = true; - addMode = false; - - camera->updateKeysToEvent([&](auto& keys) + if (addMode) { - keys.emplace(newKey, selectedEventType); - }); + ImGui::Separator(); + handleAddMapping("AddMouseMappingTable", controller, activeController, selectedEventType, newKey, newMouseCode, addMode); + } + ImGui::EndTabItem(); } - ImGui::PopStyleVar(); - ImGui::EndTable(); + ImGui::EndTabBar(); } - ImGui::Dummy(ImVec2(0.0f, verticalPadding)); ImGui::End(); } diff --git a/61_UI/main.cpp b/61_UI/main.cpp index e7a4f3305..b57c0b5d9 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -9,6 +9,7 @@ // FPS Camera, TESTS using camera_t = CFPSCamera; +using controller_t = CCameraController; using projection_t = IProjection>; // TODO: temporary -> projections will own/reference cameras /* @@ -493,7 +494,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } - displayKeyMappingsAndVirtualStates(camera.get()); + displayKeyMappingsAndVirtualStates(controller.get()); ImGui::End(); } @@ -511,6 +512,29 @@ class UISampleApp final : public examples::SimpleWindowedApplication const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); camera = make_smart_refctd_ptr(position); + controller = make_smart_refctd_ptr(core::smart_refctd_ptr(camera)); + + // init keyboard map + controller->updateKeyboardMapping([](auto& keys) + { + keys[ui::E_KEY_CODE::EKC_W] = CVirtualGimbalEvent::MoveForward; + keys[ui::E_KEY_CODE::EKC_S] = CVirtualGimbalEvent::MoveBackward; + keys[ui::E_KEY_CODE::EKC_A] = CVirtualGimbalEvent::MoveLeft; + keys[ui::E_KEY_CODE::EKC_D] = CVirtualGimbalEvent::MoveRight; + keys[ui::E_KEY_CODE::EKC_I] = CVirtualGimbalEvent::TiltDown; + keys[ui::E_KEY_CODE::EKC_K] = CVirtualGimbalEvent::TiltUp; + keys[ui::E_KEY_CODE::EKC_J] = CVirtualGimbalEvent::PanLeft; + keys[ui::E_KEY_CODE::EKC_L] = CVirtualGimbalEvent::PanRight; + }); + + // init mouse map + controller->updateMouseMapping([](auto& keys) + { + keys[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_X] = CVirtualGimbalEvent::PanRight; + keys[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_X] = CVirtualGimbalEvent::PanLeft; + keys[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_Y] = CVirtualGimbalEvent::TiltUp; + keys[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_Y] = CVirtualGimbalEvent::TiltDown; + }); return true; } @@ -742,15 +766,19 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (move) { - static std::vector virtualEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size() * 2); - uint32_t vEventsMouseCount, vEventsKeyboardCount; + static std::vector virtualEvents(0x45); + uint32_t vCount; - camera->beginInputProcessing(nextPresentationTimestamp); - camera->processKeyboard(virtualEvents.data(), vEventsKeyboardCount, params.keyboardEvents); - camera->processMouse(virtualEvents.data() + vEventsKeyboardCount, vEventsMouseCount, params.mouseEvents); - camera->endInputProcessing(); + controller->beginInputProcessing(nextPresentationTimestamp); + controller->process(nullptr, vCount); - camera->manipulate({ virtualEvents.data(), vEventsMouseCount + vEventsKeyboardCount }); + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); + + controller->process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); + controller->endInputProcessing(); + + controller->getCamera()->manipulate({ virtualEvents.data(), vCount }); } pass.ui.manager->update(params); @@ -796,6 +824,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication smart_refctd_ptr projection = make_smart_refctd_ptr(); // TMP! core::smart_refctd_ptr> camera; + core::smart_refctd_ptr controller; video::CDumbPresentationOracle oracle; uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 37024b6dd..3f95de0dc 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -16,16 +16,12 @@ class CFPSCamera final : public ICamera { public: using base_t = ICamera; - using traits_t = typename base_t::Traits; - CFPSCamera(const vector& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) - : base_t(), m_gimbal({ .position = position, .orientation = orientation }) - { - initKeysToEvent(); - } + CFPSCamera(const vector& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + : base_t(), m_gimbal({ .position = position, .orientation = orientation }) {} ~CFPSCamera() = default; - const typename traits_t::gimbal_t& getGimbal() override + const typename base_t::CGimbal& getGimbal() override { return m_gimbal; } @@ -58,22 +54,7 @@ class CFPSCamera final : public ICamera } private: - void initKeysToEvent() override - { - traits_t::controller_t::updateKeysToEvent([](traits_t::controller_t::keys_to_virtual_events_t& keys) - { - keys[ui::E_KEY_CODE::EKC_W] = CVirtualGimbalEvent::MoveForward; - keys[ui::E_KEY_CODE::EKC_S] = CVirtualGimbalEvent::MoveBackward; - keys[ui::E_KEY_CODE::EKC_A] = CVirtualGimbalEvent::MoveLeft; - keys[ui::E_KEY_CODE::EKC_D] = CVirtualGimbalEvent::MoveRight; - keys[ui::E_KEY_CODE::EKC_I] = CVirtualGimbalEvent::TiltDown; - keys[ui::E_KEY_CODE::EKC_K] = CVirtualGimbalEvent::TiltUp; - keys[ui::E_KEY_CODE::EKC_J] = CVirtualGimbalEvent::PanLeft; - keys[ui::E_KEY_CODE::EKC_L] = CVirtualGimbalEvent::PanRight; - }); - } - - traits_t::gimbal_t m_gimbal; + typename base_t::CGimbal m_gimbal; static inline constexpr auto AllowedVirtualEvents = CVirtualGimbalEvent::MoveForward | CVirtualGimbalEvent::MoveBackward | CVirtualGimbalEvent::MoveRight | CVirtualGimbalEvent::MoveLeft | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; }; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index e681ff26b..1e6f8cc3b 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -5,29 +5,81 @@ #ifndef _I_CAMERA_HPP_ #define _I_CAMERA_HPP_ -#include "camera/ICameraControl.hpp" +#include "camera/IGimbal.hpp" namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { template -class ICamera : public ICameraController +class ICamera : virtual public core::IReferenceCounted { public: - using base_t = typename ICameraController; + using precision_t = T; - struct Traits - { - using controller_t = base_t; - using gimbal_t = typename controller_t::CGimbal; - using precision_t = typename T; // TODO: actually all vectors/scalars should have precision type T and because of projection matrix constraints allowed is only float32_t & float64_t - }; + // Gimbal with view parameters representing a camera in world space + class CGimbal : public IGimbal + { + public: + using base_t = IGimbal; - ICamera() : base_t() {} + CGimbal(typename base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) {} + ~CGimbal() = default; + + struct SView + { + matrix matrix = {}; + bool isLeftHandSystem = true; + }; + + inline void updateView() + { + if (base_t::getManipulationCounter()) + { + const auto& gRight = base_t::getXAxis(), gUp = base_t::getYAxis(), gForward = base_t::getZAxis(); + + // TODO: I think this should be set as request state, depending on the value we do m_view.matrix[2u] flip accordingly + // m_view.isLeftHandSystem; + + auto isNormalized = [](const auto& v, float epsilon) -> bool + { + return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); + }; + + auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool + { + return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); + }; + + auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool + { + return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && + isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); + }; + + assert(isOrthoBase(gRight, gUp, gForward)); + + const auto& position = base_t::getPosition(); + m_view.matrix[0u] = vector(gRight, -glm::dot(gRight, position)); + m_view.matrix[1u] = vector(gUp, -glm::dot(gUp, position)); + m_view.matrix[2u] = vector(gForward, -glm::dot(gForward, position)); + } + } + + // Getter for gimbal's view + inline const SView& getView() const { return m_view; } + + private: + SView m_view; + }; + + ICamera() {} ~ICamera() = default; // Returns a gimbal which *models the camera view*, note that a camera type implementation may have multiple gimbals under the hood - virtual const Traits::gimbal_t& getGimbal() = 0u; + virtual const CGimbal& getGimbal() = 0u; + + // Manipulates camera with view gimbal & virtual events + virtual void manipulate(std::span virtualEvents) = 0; // VirtualEventType bitmask for a camera view gimbal manipulation requests filtering virtual const uint32_t getAllowedVirtualEvents() = 0u; diff --git a/common/include/camera/CCameraController.hpp b/common/include/camera/CCameraController.hpp new file mode 100644 index 000000000..0dad4e2f4 --- /dev/null +++ b/common/include/camera/CCameraController.hpp @@ -0,0 +1,260 @@ +#ifndef _NBL_C_CAMERA_CONTROLLER_HPP_ +#define _NBL_C_CAMERA_CONTROLLER_HPP_ + +#include "ICameraController.hpp" +#include "ICamera.hpp" + +// TODO: DIFFERENT NAMESPACE +namespace nbl::hlsl +{ + +template +class CCameraController : public ICameraController +{ +public: + using ICameraController::ICameraController; + using interface_camera_t = ICamera; + + using keyboard_to_virtual_events_t = std::unordered_map; + using mouse_to_virtual_events_t = std::unordered_map; + + CCameraController(core::smart_refctd_ptr camera) : m_camera(core::smart_refctd_ptr(camera)) {} + + // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller mouse_to_virtual_events_t table + void updateKeyboardMapping(const std::function& mapKeys) + { + mapKeys(m_keyboardVirtualEventMap); + } + + // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller mouse_to_virtual_events_t table + void updateMouseMapping(const std::function& mapKeys) + { + mapKeys(m_mouseVirtualEventMap); + } + + struct SUpdateParameters + { + std::span keyboardEvents = {}; + std::span mouseEvents = {}; + }; + + // Processes SUpdateParameters events to generate virtual manipulation events + void process(CVirtualGimbalEvent* output, uint32_t& count, const SUpdateParameters parameters = {}) + { + count = 0u; + uint32_t vKeyboardEventsCount, vMouseEventsCount; + + if (output) + { + processKeyboard(output, vKeyboardEventsCount, parameters.keyboardEvents); + processMouse(output + vKeyboardEventsCount, vMouseEventsCount, parameters.mouseEvents); + } + else + { + processKeyboard(nullptr, vKeyboardEventsCount, {}); + processMouse(nullptr, vMouseEventsCount, {}); + } + + count = vKeyboardEventsCount + vMouseEventsCount; + } + + inline const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() { return m_keyboardVirtualEventMap; } + inline const mouse_to_virtual_events_t& getMouseVirtualEventMap() { return m_mouseVirtualEventMap; } + inline interface_camera_t* getCamera() { return m_camera.get(); } + +private: + // Processes keyboard events to generate virtual manipulation events + void processKeyboard(CVirtualGimbalEvent* output, uint32_t& count, std::span events) + { + count = 0u; + const auto mappedVirtualEventsCount = m_keyboardVirtualEventMap.size(); + + if (!output) + { + count = mappedVirtualEventsCount; + return; + } + + if (mappedVirtualEventsCount) + { + const auto frameDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); + assert(frameDeltaTime >= 0.f); + + for (auto& [key, hash] : m_keyboardVirtualEventMap) + { + hash.event.magnitude = 0.f; + + /* + if a key was already being held down from previous frames we compute with this + assumption that the key will be held down for this whole frame as well and its + delta action is simply frame delta time + */ + + if (hash.active) + hash.event.magnitude = frameDeltaTime; + } + + for (const auto& keyboardEvent : events) + { + auto request = m_keyboardVirtualEventMap.find(keyboardEvent.keyCode); + if (request != std::end(m_keyboardVirtualEventMap)) + { + auto& hash = request->second; + + if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_PRESSED) + { + if (!hash.active) + { + // and if a key has been pressed after its last release event then first delta action is the key delta + const auto keyDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - keyboardEvent.timeStamp).count(); + assert(keyDeltaTime >= 0); + + hash.active = true; + hash.event.magnitude = keyDeltaTime; + } + } + else if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) + hash.active = false; + } + } + + for (const auto& [key, hash] : m_keyboardVirtualEventMap) + { + if (hash.event.magnitude) + { + auto* virtualEvent = output + count; + virtualEvent->type = hash.event.type; + virtualEvent->magnitude = hash.event.magnitude; + ++count; + } + } + } + } + + // Processes mouse events to generate virtual manipulation events + void processMouse(CVirtualGimbalEvent* output, uint32_t& count, std::span events) + { + count = 0u; + const auto mappedVirtualEventsCount = m_mouseVirtualEventMap.size(); + + if (!output) + { + count = mappedVirtualEventsCount; + return; + } + + if (mappedVirtualEventsCount) + { + const auto frameDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); + assert(frameDeltaTime >= 0.f); + + for (auto& [key, hash] : m_mouseVirtualEventMap) + { + hash.event.magnitude = 0.f; + if (hash.active) + hash.event.magnitude = frameDeltaTime; + } + + for (const auto& mouseEvent : events) + { + ui::E_MOUSE_CODE mouseCode = ui::EMC_NONE; + + switch (mouseEvent.type) + { + case ui::SMouseEvent::EET_CLICK: + { + switch (mouseEvent.clickEvent.mouseButton) + { + case ui::EMB_LEFT_BUTTON: mouseCode = ui::EMC_LEFT_BUTTON; break; + case ui::EMB_RIGHT_BUTTON: mouseCode = ui::EMC_RIGHT_BUTTON; break; + case ui::EMB_MIDDLE_BUTTON: mouseCode = ui::EMC_MIDDLE_BUTTON; break; + case ui::EMB_BUTTON_4: mouseCode = ui::EMC_BUTTON_4; break; + case ui::EMB_BUTTON_5: mouseCode = ui::EMC_BUTTON_5; break; + default: continue; + } + + auto request = m_mouseVirtualEventMap.find(mouseCode); + if (request != std::end(m_mouseVirtualEventMap)) + { + auto& hash = request->second; + + if (mouseEvent.clickEvent.action == ui::SMouseEvent::SClickEvent::EA_PRESSED) + { + if (!hash.active) + { + const auto keyDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - mouseEvent.timeStamp).count(); + assert(keyDeltaTime >= 0); + + hash.active = true; + hash.event.magnitude += keyDeltaTime; + } + } + else if (mouseEvent.clickEvent.action == ui::SMouseEvent::SClickEvent::EA_RELEASED) + hash.active = false; + } + } break; + + case ui::SMouseEvent::EET_SCROLL: + { + if (mouseEvent.scrollEvent.verticalScroll != 0) + { + mouseCode = (mouseEvent.scrollEvent.verticalScroll > 0) ? ui::EMC_VERTICAL_POSITIVE_SCROLL : ui::EMC_VERTICAL_NEGATIVE_SCROLL; + auto request = m_mouseVirtualEventMap.find(mouseCode); + if (request != std::end(m_mouseVirtualEventMap)) + request->second.event.magnitude += std::abs(mouseEvent.scrollEvent.verticalScroll); + } + + if (mouseEvent.scrollEvent.horizontalScroll != 0) + { + mouseCode = (mouseEvent.scrollEvent.horizontalScroll > 0) ? ui::EMC_HORIZONTAL_POSITIVE_SCROLL : ui::EMC_HORIZONTAL_NEGATIVE_SCROLL; + auto request = m_mouseVirtualEventMap.find(mouseCode); + if (request != std::end(m_mouseVirtualEventMap)) + request->second.event.magnitude += std::abs(mouseEvent.scrollEvent.horizontalScroll); + } + } break; + + case ui::SMouseEvent::EET_MOVEMENT: + { + if (mouseEvent.movementEvent.relativeMovementX != 0) + { + mouseCode = (mouseEvent.movementEvent.relativeMovementX > 0) ? ui::EMC_RELATIVE_POSITIVE_MOVEMENT_X : ui::EMC_RELATIVE_NEGATIVE_MOVEMENT_X; + auto request = m_mouseVirtualEventMap.find(mouseCode); + if (request != std::end(m_mouseVirtualEventMap)) + request->second.event.magnitude += std::abs(mouseEvent.movementEvent.relativeMovementX); + } + + if (mouseEvent.movementEvent.relativeMovementY != 0) + { + mouseCode = (mouseEvent.movementEvent.relativeMovementY > 0) ? ui::EMC_RELATIVE_POSITIVE_MOVEMENT_Y : ui::EMC_RELATIVE_NEGATIVE_MOVEMENT_Y; + auto request = m_mouseVirtualEventMap.find(mouseCode); + if (request != std::end(m_mouseVirtualEventMap)) + request->second.event.magnitude += std::abs(mouseEvent.movementEvent.relativeMovementY); + } + } break; + + default: + break; + } + } + + for (const auto& [key, hash] : m_mouseVirtualEventMap) + { + if (hash.event.magnitude) + { + auto* virtualEvent = output + count; + virtualEvent->type = hash.event.type; + virtualEvent->magnitude = hash.event.magnitude; + ++count; + } + } + } + } + + core::smart_refctd_ptr m_camera; + keyboard_to_virtual_events_t m_keyboardVirtualEventMap; + mouse_to_virtual_events_t m_mouseVirtualEventMap; +}; + +} // nbl::hlsl namespace + +#endif // _NBL_C_CAMERA_CONTROLLER_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp index f017eff14..56c3b289d 100644 --- a/common/include/camera/ICameraControl.hpp +++ b/common/include/camera/ICameraControl.hpp @@ -14,81 +14,44 @@ namespace nbl::hlsl { -template class ICameraController : virtual public core::IReferenceCounted { public: - using precision_t = typename T; - - struct CRequestInfo + enum ControllerType : uint8_t { - CRequestInfo() {} - CRequestInfo(CVirtualGimbalEvent::VirtualEventType _type) : type(_type) {} - ~CRequestInfo() = default; + Keyboard = 0, + Mouse = 1, - CVirtualGimbalEvent::VirtualEventType type = CVirtualGimbalEvent::VirtualEventType::None; - bool active = false; - float64_t dtAction = {}; + Count }; - using keys_to_virtual_events_t = std::unordered_map; - - // Gimbal with view parameters representing a camera in world space - class CGimbal : public IGimbal + struct CKeyInfo { - public: - using base_t = IGimbal; - - CGimbal(typename base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) {} - ~CGimbal() = default; - - struct SView + union { - matrix matrix = {}; - bool isLeftHandSystem = true; + ui::E_KEY_CODE keyboardCode; + ui::E_MOUSE_CODE mouseCode; }; - inline void updateView() - { - if (base_t::getManipulationCounter()) - { - const auto& gRight = base_t::getXAxis(), gUp = base_t::getYAxis(), gForward = base_t::getZAxis(); - - // TODO: I think this should be set as request state, depending on the value we do m_view.matrix[2u] flip accordingly - // m_view.isLeftHandSystem; - - auto isNormalized = [](const auto& v, float epsilon) -> bool - { - return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); - }; - - auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool - { - return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); - }; - - auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool - { - return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && - isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); - }; - - assert(isOrthoBase(gRight, gUp, gForward)); + CKeyInfo(ControllerType _type, ui::E_KEY_CODE _keyboardCode) : keyboardCode(_keyboardCode), type(_type) {} + CKeyInfo(ControllerType _type, ui::E_MOUSE_CODE _mouseCode) : mouseCode(_mouseCode), type(_type) {} - const auto& position = base_t::getPosition(); - m_view.matrix[0u] = vector(gRight, -glm::dot(gRight, position)); - m_view.matrix[1u] = vector(gUp, -glm::dot(gUp, position)); - m_view.matrix[2u] = vector(gForward, -glm::dot(gForward, position)); - } - } + ControllerType type; + }; - // Getter for gimbal's view - inline const SView& getView() const { return m_view; } + struct CHashInfo + { + CHashInfo() {} + CHashInfo(CVirtualGimbalEvent::VirtualEventType _type) : type(_type) {} + ~CHashInfo() = default; - private: - SView m_view; + CVirtualGimbalEvent::VirtualEventType type = CVirtualGimbalEvent::VirtualEventType::None; + bool active = false; + float64_t dtAction = {}; }; + using keys_to_virtual_events_t = std::unordered_map; + ICameraController() {} // Binds key codes to virtual events, the mapKeys lambda will be executed with controller keys_to_virtual_events_t table @@ -97,140 +60,21 @@ class ICameraController : virtual public core::IReferenceCounted mapKeys(m_keysToVirtualEvents); } - // Manipulates camera with view gimbal & virtual events - virtual void manipulate(std::span virtualEvents) = 0; + inline const keys_to_virtual_events_t& getKeysToVirtualEvents() { return m_keysToVirtualEvents; } - // TODO: *maybe* would be good to have a class interface for virtual event generators, - // eg keyboard, mouse but maybe custom stuff too eg events from gimbal drag & drop void beginInputProcessing(const std::chrono::microseconds _nextPresentationTimeStamp) { nextPresentationTimeStamp = _nextPresentationTimeStamp; } - // Processes keyboard events to generate virtual manipulation events, note that it doesn't make the manipulation itself! - void processKeyboard(CVirtualGimbalEvent* output, uint32_t& count, std::span events) - { - count = 0u; - const auto mappedVirtualEventsCount = m_keysToVirtualEvents.size(); - - if (!output) - { - count = mappedVirtualEventsCount; - return; - } - - if (mappedVirtualEventsCount) - { - const auto frameDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); - assert(frameDeltaTime >= 0.f); - - for (auto& [key, info] : m_keysToVirtualEvents) - { - info.dtAction = 0.f; - - /* - if a key was already being held down from previous frames we compute with this - assumption that the key will be held down for this whole frame as well and its - delta action is simply frame delta time - */ - - if (info.active) - info.dtAction = frameDeltaTime; - } - - for (const auto& keyboardEvent : events) - { - auto request = m_keysToVirtualEvents.find(keyboardEvent.keyCode); - if (request != std::end(m_keysToVirtualEvents)) - { - auto& info = request->second; - - if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_PRESSED) - { - if (!info.active) - { - // and if a key has been pressed after its last release event then first delta action is the key delta - const auto keyDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - keyboardEvent.timeStamp).count(); - assert(keyDeltaTime >= 0); - - info.active = true; - info.dtAction = keyDeltaTime; - } - } - else if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - info.active = false; - } - } - - for (const auto& [key, info] : m_keysToVirtualEvents) - { - if (info.active) - { - auto* virtualEvent = output + count; - virtualEvent->type = info.type; - virtualEvent->magnitude = info.dtAction; - ++count; - } - } - } - } - - // Processes mouse events to generate virtual manipulation events, note that it doesn't make the manipulation itself! - // Limited to Pan & Tilt rotation events, camera type implements how event magnitudes should be interpreted - void processMouse(CVirtualGimbalEvent* output, uint32_t& count, std::span events) - { - count = 0u; - - if (!output) - { - count = 2u; - return; - } - - if (events.empty()) - return; - - double dYaw = {}, dPitch = {}; - - for (const auto& ev : events) - if (ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT) - { - dYaw += ev.movementEvent.relativeMovementX; - dPitch += ev.movementEvent.relativeMovementY; - } - - if (dPitch) - { - auto* pitch = output + count; - pitch->type = dPitch > 0.f ? CVirtualGimbalEvent::TiltUp : CVirtualGimbalEvent::TiltDown; - pitch->magnitude = std::abs(dPitch); - count++; - } - - if (dYaw) - { - auto* yaw = output + count; - assert(yaw); // TODO: maybe just log error and return 0 count - yaw->type = dYaw > 0.f ? CVirtualGimbalEvent::PanRight : CVirtualGimbalEvent::PanLeft; - yaw->magnitude = std::abs(dYaw); - count++; - } - } - void endInputProcessing() { lastVirtualUpTimeStamp = nextPresentationTimeStamp; } - inline const keys_to_virtual_events_t& getKeysToVirtualEvents() { return m_keysToVirtualEvents; } - -protected: - virtual void initKeysToEvent() = 0; - private: keys_to_virtual_events_t m_keysToVirtualEvents; - bool m_keysDown[CVirtualGimbalEvent::EventsCount] = {}; std::chrono::microseconds nextPresentationTimeStamp = {}, lastVirtualUpTimeStamp = {}; }; diff --git a/common/include/camera/ICameraController.hpp b/common/include/camera/ICameraController.hpp new file mode 100644 index 000000000..6df41d709 --- /dev/null +++ b/common/include/camera/ICameraController.hpp @@ -0,0 +1,68 @@ +#ifndef _NBL_I_CAMERA_CONTROLLER_HPP_ +#define _NBL_I_CAMERA_CONTROLLER_HPP_ + +#include +#include +#include +#include +#include + +#include "IProjection.hpp" +#include "CGeneralPurposeGimbal.hpp" + +// TODO: DIFFERENT NAMESPACE +namespace nbl::hlsl +{ + +class ICameraController : virtual public core::IReferenceCounted +{ +public: + enum ControllerType : uint8_t + { + Keyboard, + Mouse, + + Count + }; + + struct CKeyInfo + { + union + { + ui::E_KEY_CODE keyboardCode; + ui::E_MOUSE_CODE mouseCode; + }; + + CKeyInfo(ui::E_KEY_CODE _keyboardCode) : keyboardCode(_keyboardCode), type(Keyboard) {} + CKeyInfo(ui::E_MOUSE_CODE _mouseCode) : mouseCode(_mouseCode), type(Mouse) {} + + ControllerType type; + }; + + struct CHashInfo + { + CHashInfo() {} + CHashInfo(CVirtualGimbalEvent::VirtualEventType _type) : event({ .type = _type }) {} + ~CHashInfo() = default; + + CVirtualGimbalEvent event = {}; + bool active = false; + }; + + void beginInputProcessing(const std::chrono::microseconds _nextPresentationTimeStamp) + { + nextPresentationTimeStamp = _nextPresentationTimeStamp; + } + + void endInputProcessing() + { + lastVirtualUpTimeStamp = nextPresentationTimeStamp; + } + +protected: + std::chrono::microseconds nextPresentationTimeStamp = {}, lastVirtualUpTimeStamp = {}; +}; + +} // nbl::hlsl namespace + +#endif // _NBL_I_CAMERA_CONTROLLER_HPP_ \ No newline at end of file diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index fed8f9f7a..fcc316ddd 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -46,8 +46,8 @@ namespace nbl::hlsl using manipulation_encode_t = float64_t; - VirtualEventType type; - manipulation_encode_t magnitude; + VirtualEventType type = None; + manipulation_encode_t magnitude = {}; static constexpr std::string_view virtualEventToString(VirtualEventType event) { From c991edf6fdee8a721285cc8940e6b4111fd6427b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 11 Nov 2024 18:20:22 +0100 Subject: [PATCH 31/84] rename + refactor ICameraController to more abstract IGimbalController, unify controller API, add ImGuizmo controller (untested yet), add controller key map preset to ICamera interface for view gimbal, create CCameraController which will be capable of both manipulating camera view & target gimbals with *any controller type*, write down my thoughts & TODOs in comments, polish code, update sources, adjust to changes, eliminate caught bugs --- 61_UI/include/keysmapping.hpp | 12 +- 61_UI/main.cpp | 39 +- common/include/CFPSCamera.hpp | 100 ++++- common/include/ICamera.hpp | 25 +- common/include/camera/CCameraController.hpp | 290 +++----------- .../include/camera/CGeneralPurposeGimbal.hpp | 3 +- common/include/camera/ICameraControl.hpp | 110 ------ common/include/camera/ICameraController.hpp | 68 ---- common/include/camera/IGimbal.hpp | 55 ++- common/include/camera/IGimbalController.hpp | 374 ++++++++++++++++++ 10 files changed, 607 insertions(+), 469 deletions(-) delete mode 100644 common/include/camera/ICameraControl.hpp delete mode 100644 common/include/camera/ICameraController.hpp create mode 100644 common/include/camera/IGimbalController.hpp diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp index d30fa6b9c..25bba3e68 100644 --- a/61_UI/include/keysmapping.hpp +++ b/61_UI/include/keysmapping.hpp @@ -5,7 +5,7 @@ #include "camera/CCameraController.hpp" template -void handleAddMapping(const char* tableID, CCameraController* controller, ICameraController::ControllerType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) +void handleAddMapping(const char* tableID, CCameraController* controller, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) { ImGui::BeginTable(tableID, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.33f); @@ -30,7 +30,7 @@ void handleAddMapping(const char* tableID, CCameraController* controller, ICa } ImGui::TableSetColumnIndex(1); - if (activeController == ICameraController::Keyboard) + if (activeController == IGimbalManipulateEncoder::Keyboard) { char newKeyDisplay[2] = { ui::keyCodeToChar(newKey, true), '\0' }; if (ImGui::BeginCombo("##selectKey", newKeyDisplay)) @@ -66,7 +66,7 @@ void handleAddMapping(const char* tableID, CCameraController* controller, ICa ImGui::TableSetColumnIndex(2); if (ImGui::Button("Confirm Add", ImVec2(100, 30))) { - if (activeController == ICameraController::Keyboard) + if (activeController == IGimbalManipulateEncoder::Keyboard) controller->updateKeyboardMapping([&](auto& keys) { keys[newKey] = selectedEventType; }); else controller->updateMouseMapping([&](auto& mouse) { mouse[newMouseCode] = selectedEventType; }); @@ -83,7 +83,7 @@ void displayKeyMappingsAndVirtualStates(CCameraController* controller) static CVirtualGimbalEvent::VirtualEventType selectedEventType = CVirtualGimbalEvent::VirtualEventType::MoveForward; static ui::E_KEY_CODE newKey = ui::E_KEY_CODE::EKC_A; static ui::E_MOUSE_CODE newMouseCode = ui::EMC_LEFT_BUTTON; - static ICameraController::ControllerType activeController = ICameraController::Keyboard; + static IGimbalManipulateEncoder::EncoderType activeController = IGimbalManipulateEncoder::Keyboard; const auto& keyboardMappings = controller->getKeyboardVirtualEventMap(); const auto& mouseMappings = controller->getMouseVirtualEventMap(); @@ -97,7 +97,7 @@ void displayKeyMappingsAndVirtualStates(CCameraController* controller) { if (ImGui::BeginTabItem("Keyboard")) { - activeController = ICameraController::Keyboard; + activeController = IGimbalManipulateEncoder::Keyboard; ImGui::Separator(); if (ImGui::Button("Add key", ImVec2(100, 30))) @@ -159,7 +159,7 @@ void displayKeyMappingsAndVirtualStates(CCameraController* controller) if (ImGui::BeginTabItem("Mouse")) { - activeController = ICameraController::Mouse; + activeController = IGimbalManipulateEncoder::Mouse; ImGui::Separator(); if (ImGui::Button("Add key", ImVec2(100, 30))) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index b57c0b5d9..3851d7177 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -515,25 +515,21 @@ class UISampleApp final : public examples::SimpleWindowedApplication controller = make_smart_refctd_ptr(core::smart_refctd_ptr(camera)); // init keyboard map - controller->updateKeyboardMapping([](auto& keys) + controller->updateKeyboardMapping([&](auto& keys) { - keys[ui::E_KEY_CODE::EKC_W] = CVirtualGimbalEvent::MoveForward; - keys[ui::E_KEY_CODE::EKC_S] = CVirtualGimbalEvent::MoveBackward; - keys[ui::E_KEY_CODE::EKC_A] = CVirtualGimbalEvent::MoveLeft; - keys[ui::E_KEY_CODE::EKC_D] = CVirtualGimbalEvent::MoveRight; - keys[ui::E_KEY_CODE::EKC_I] = CVirtualGimbalEvent::TiltDown; - keys[ui::E_KEY_CODE::EKC_K] = CVirtualGimbalEvent::TiltUp; - keys[ui::E_KEY_CODE::EKC_J] = CVirtualGimbalEvent::PanLeft; - keys[ui::E_KEY_CODE::EKC_L] = CVirtualGimbalEvent::PanRight; + keys = controller->getCamera()->getKeyboardMappingPreset(); }); // init mouse map - controller->updateMouseMapping([](auto& keys) + controller->updateMouseMapping([&](auto& keys) { - keys[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_X] = CVirtualGimbalEvent::PanRight; - keys[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_X] = CVirtualGimbalEvent::PanLeft; - keys[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_Y] = CVirtualGimbalEvent::TiltUp; - keys[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_Y] = CVirtualGimbalEvent::TiltDown; + keys = controller->getCamera()->getMouseMappingPreset(); + }); + + // init imguizmo map + controller->updateImguizmoMapping([&](auto& keys) + { + keys = controller->getCamera()->getImguizmoMappingPreset(); }); return true; @@ -766,19 +762,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (move) { - static std::vector virtualEvents(0x45); - uint32_t vCount; - - controller->beginInputProcessing(nextPresentationTimestamp); - controller->process(nullptr, vCount); - - if (virtualEvents.size() < vCount) - virtualEvents.resize(vCount); - - controller->process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); - controller->endInputProcessing(); - - controller->getCamera()->manipulate({ virtualEvents.data(), vCount }); + // TODO: testing + controller->manipulateViewGimbal({ params.keyboardEvents, params.mouseEvents }, nextPresentationTimestamp); } pass.ui.manager->update(params); diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 3f95de0dc..e0f9dec53 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -21,41 +21,119 @@ class CFPSCamera final : public ICamera : base_t(), m_gimbal({ .position = position, .orientation = orientation }) {} ~CFPSCamera() = default; + const base_t::keyboard_to_virtual_events_t& getKeyboardMappingPreset() const override { return m_keyboard_to_virtual_events_preset; } + const base_t::mouse_to_virtual_events_t& getMouseMappingPreset() const override { return m_mouse_to_virtual_events_preset; } + const base_t::imguizmo_to_virtual_events_t& getImguizmoMappingPreset() const override { return m_imguizmo_to_virtual_events_preset; } + const typename base_t::CGimbal& getGimbal() override { return m_gimbal; } - virtual void manipulate(std::span virtualEvents) override + virtual bool manipulate(std::span virtualEvents, base_t::ManipulationMode mode) override { constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; if (!virtualEvents.size()) - return; - - const auto impulse = m_gimbal.accumulate(virtualEvents); + return false; const auto& gForward = m_gimbal.getZAxis(); - const float currentPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(), currentYaw = atan2(gForward.x, gForward.z); - const auto newPitch = std::clamp(currentPitch + impulse.dVirtualRotation.x * RotateSpeedScale, MinVerticalAngle, MaxVerticalAngle), newYaw = currentYaw + impulse.dVirtualRotation.y * RotateSpeedScale; + const float gPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(), gYaw = atan2(gForward.x, gForward.z); + + glm::quat newOrientation; vector newPosition; + + switch (mode) + { + case base_t::Local: + { + const auto impulse = m_gimbal.accumulate(virtualEvents); + const auto newPitch = std::clamp(gPitch + impulse.dVirtualRotation.x * RotateSpeedScale, MinVerticalAngle, MaxVerticalAngle), newYaw = gYaw + impulse.dVirtualRotation.y * RotateSpeedScale; + newOrientation = glm::quat(glm::vec3(newPitch, newYaw, 0.0f)); newPosition = m_gimbal.getPosition() + impulse.dVirtualTranslate * MoveSpeedScale; + } break; + + case base_t::World: + { + const auto transform = m_gimbal.accumulate(virtualEvents); + const auto newPitch = std::clamp(transform.dVirtualRotation.x, MinVerticalAngle, MaxVerticalAngle), newYaw = transform.dVirtualRotation.y; + newOrientation = glm::quat(glm::vec3(newPitch, newYaw, 0.0f)); newPosition = transform.dVirtualTranslate; + } break; + + default: return false; + } + + bool manipulated = true; m_gimbal.begin(); { - m_gimbal.setOrientation(glm::quat(glm::vec3(newPitch, newYaw, 0.0f))); - m_gimbal.move(impulse.dVirtualTranslate * MoveSpeedScale); + m_gimbal.setOrientation(newOrientation); + m_gimbal.setPosition(newPosition); m_gimbal.updateView(); + manipulated &= bool(m_gimbal.getManipulationCounter()); } m_gimbal.end(); + + return manipulated; } - virtual const uint32_t getAllowedVirtualEvents() override + virtual const uint32_t getAllowedVirtualEvents(base_t::ManipulationMode mode) override { - return AllowedVirtualEvents; + switch (mode) + { + case base_t::Local: return AllowedLocalVirtualEvents; + case base_t::World: return AllowedWorldVirtualEvents; + default: return CVirtualGimbalEvent::None; + } } private: typename base_t::CGimbal m_gimbal; - static inline constexpr auto AllowedVirtualEvents = CVirtualGimbalEvent::MoveForward | CVirtualGimbalEvent::MoveBackward | CVirtualGimbalEvent::MoveRight | CVirtualGimbalEvent::MoveLeft | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; + + static inline constexpr auto AllowedLocalVirtualEvents = CVirtualGimbalEvent::MoveForward | CVirtualGimbalEvent::MoveBackward | CVirtualGimbalEvent::MoveRight | CVirtualGimbalEvent::MoveLeft | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; + static inline constexpr auto AllowedWorldVirtualEvents = AllowedLocalVirtualEvents | CVirtualGimbalEvent::Translate; + + static inline const auto m_keyboard_to_virtual_events_preset = []() + { + typename base_t::keyboard_to_virtual_events_t preset; + + preset[ui::E_KEY_CODE::EKC_W] = CVirtualGimbalEvent::MoveForward; + preset[ui::E_KEY_CODE::EKC_S] = CVirtualGimbalEvent::MoveBackward; + preset[ui::E_KEY_CODE::EKC_A] = CVirtualGimbalEvent::MoveLeft; + preset[ui::E_KEY_CODE::EKC_D] = CVirtualGimbalEvent::MoveRight; + preset[ui::E_KEY_CODE::EKC_I] = CVirtualGimbalEvent::TiltDown; + preset[ui::E_KEY_CODE::EKC_K] = CVirtualGimbalEvent::TiltUp; + preset[ui::E_KEY_CODE::EKC_J] = CVirtualGimbalEvent::PanLeft; + preset[ui::E_KEY_CODE::EKC_L] = CVirtualGimbalEvent::PanRight; + + return preset; + }(); + + static inline const auto m_mouse_to_virtual_events_preset = []() + { + typename base_t::mouse_to_virtual_events_t preset; + + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_X] = CVirtualGimbalEvent::PanRight; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_X] = CVirtualGimbalEvent::PanLeft; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_Y] = CVirtualGimbalEvent::TiltUp; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_Y] = CVirtualGimbalEvent::TiltDown; + + return preset; + }(); + + static inline const auto m_imguizmo_to_virtual_events_preset = []() + { + typename base_t::imguizmo_to_virtual_events_t preset; + + preset[CVirtualGimbalEvent::MoveForward] = CVirtualGimbalEvent::MoveForward; + preset[CVirtualGimbalEvent::MoveBackward] = CVirtualGimbalEvent::MoveBackward; + preset[CVirtualGimbalEvent::MoveLeft] = CVirtualGimbalEvent::MoveLeft; + preset[CVirtualGimbalEvent::MoveRight] = CVirtualGimbalEvent::MoveRight; + preset[CVirtualGimbalEvent::TiltDown] = CVirtualGimbalEvent::TiltDown; + preset[CVirtualGimbalEvent::TiltUp] = CVirtualGimbalEvent::TiltUp; + preset[CVirtualGimbalEvent::PanLeft] = CVirtualGimbalEvent::PanLeft; + preset[CVirtualGimbalEvent::PanRight] = CVirtualGimbalEvent::PanRight; + + return preset; + }(); }; } diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index 1e6f8cc3b..fbbb5a863 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -5,17 +5,29 @@ #ifndef _I_CAMERA_HPP_ #define _I_CAMERA_HPP_ -#include "camera/IGimbal.hpp" +#include "camera/IGimbalController.hpp" namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { template -class ICamera : virtual public core::IReferenceCounted +class ICamera : public IGimbalManipulateEncoder, virtual public core::IReferenceCounted { public: + using IGimbalManipulateEncoder::IGimbalManipulateEncoder; using precision_t = T; + //! Manipulation mode for virtual events + //! TODO: this should belong to IObjectTransform or something + enum ManipulationMode + { + // Interpret virtual events as accumulated impulse representing relative manipulation with respect to view gimbal base + Local, + + // Interpret virtual events as accumulated absolute manipulation with respect to world base + World + }; + // Gimbal with view parameters representing a camera in world space class CGimbal : public IGimbal { @@ -75,14 +87,15 @@ class ICamera : virtual public core::IReferenceCounted ICamera() {} ~ICamera() = default; - // Returns a gimbal which *models the camera view*, note that a camera type implementation may have multiple gimbals under the hood + // Returns a gimbal which *models the camera view* virtual const CGimbal& getGimbal() = 0u; - // Manipulates camera with view gimbal & virtual events - virtual void manipulate(std::span virtualEvents) = 0; + // Manipulates camera with virtual events, returns true if *any* manipulation happens, it may fail partially or fully because each camera type has certain constraints which determine how it actually works + // TODO: this really needs to be moved to more abstract interface, eg. IObjectTransform or something and ICamera should inherit it (its also an object!) + virtual bool manipulate(std::span virtualEvents, ManipulationMode mode) = 0; // VirtualEventType bitmask for a camera view gimbal manipulation requests filtering - virtual const uint32_t getAllowedVirtualEvents() = 0u; + virtual const uint32_t getAllowedVirtualEvents(ManipulationMode mode) = 0u; }; } diff --git a/common/include/camera/CCameraController.hpp b/common/include/camera/CCameraController.hpp index 0dad4e2f4..c858c00ee 100644 --- a/common/include/camera/CCameraController.hpp +++ b/common/include/camera/CCameraController.hpp @@ -1,260 +1,90 @@ #ifndef _NBL_C_CAMERA_CONTROLLER_HPP_ #define _NBL_C_CAMERA_CONTROLLER_HPP_ -#include "ICameraController.hpp" +#include "IGimbalController.hpp" +#include "CGeneralPurposeGimbal.hpp" #include "ICamera.hpp" // TODO: DIFFERENT NAMESPACE -namespace nbl::hlsl +namespace nbl::hlsl { - -template -class CCameraController : public ICameraController -{ -public: - using ICameraController::ICameraController; - using interface_camera_t = ICamera; - - using keyboard_to_virtual_events_t = std::unordered_map; - using mouse_to_virtual_events_t = std::unordered_map; - - CCameraController(core::smart_refctd_ptr camera) : m_camera(core::smart_refctd_ptr(camera)) {} - - // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller mouse_to_virtual_events_t table - void updateKeyboardMapping(const std::function& mapKeys) - { - mapKeys(m_keyboardVirtualEventMap); - } - - // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller mouse_to_virtual_events_t table - void updateMouseMapping(const std::function& mapKeys) + //! Controls any type of camera with available controllers using virtual gimbal events in Local mode + template + class CCameraController final : public IGimbalController { - mapKeys(m_mouseVirtualEventMap); - } - - struct SUpdateParameters - { - std::span keyboardEvents = {}; - std::span mouseEvents = {}; - }; - - // Processes SUpdateParameters events to generate virtual manipulation events - void process(CVirtualGimbalEvent* output, uint32_t& count, const SUpdateParameters parameters = {}) - { - count = 0u; - uint32_t vKeyboardEventsCount, vMouseEventsCount; - - if (output) + public: + using IGimbalController::IGimbalController; + using precision_t = T; + + using interface_camera_t = ICamera; + using interface_gimbal_t = IGimbal; + + CCameraController(core::smart_refctd_ptr camera) + : m_camera(std::move(camera)), m_target(interface_gimbal_t::SCreationParameters({ .position = m_camera->getGimbal().getWorldTarget() })) {} + ~CCameraController() {} + + const keyboard_to_virtual_events_t& getKeyboardMappingPreset() const override { return m_camera->getKeyboardMappingPreset(); } + const mouse_to_virtual_events_t& getMouseMappingPreset() const override { return m_camera->getMouseMappingPreset(); } + const imguizmo_to_virtual_events_t& getImguizmoMappingPreset() const override { return m_camera->getImguizmoMappingPreset(); } + + //! Manipulate the camera view gimbal by requesting a manipulation to its world target represented by a target gimbal, + //! on success it may change both the camera view gimbal & target gimbal + bool manipulateTargetGimbal(SUpdateParameters parameters, std::chrono::microseconds nextPresentationTimestamp) { - processKeyboard(output, vKeyboardEventsCount, parameters.keyboardEvents); - processMouse(output + vKeyboardEventsCount, vMouseEventsCount, parameters.mouseEvents); + // TODO & note to self: + // thats a little bit tricky -> a request of m_target manipulation which represents camera world target is a step where we consider only change of its + // position and translate that onto virtual orientation events we fire camera->manipulate with. Note that *we can fail* the manipulation because each + // camera type has some constraints on how it works right, however.. if any manipulation happens it means "target vector" changes and it doesn't matter + // what camera type is bound to the camera controller! If so, on success we can simply update m_target gimbal accordingly and represent it nicely on the + // screen with gizmo (as we do for camera view gimbal in Drag & Drop mode) or whatever, otherwise we do nothing because it means we failed the gimbal view + // manipulation hence "target vector" did not change (its really the orientation which changes right, but an orientation change means target vector changes) + + // and whats nice is we can do it with ANY controller now } - else - { - processKeyboard(nullptr, vKeyboardEventsCount, {}); - processMouse(nullptr, vMouseEventsCount, {}); - } - - count = vKeyboardEventsCount + vMouseEventsCount; - } - - inline const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() { return m_keyboardVirtualEventMap; } - inline const mouse_to_virtual_events_t& getMouseVirtualEventMap() { return m_mouseVirtualEventMap; } - inline interface_camera_t* getCamera() { return m_camera.get(); } - -private: - // Processes keyboard events to generate virtual manipulation events - void processKeyboard(CVirtualGimbalEvent* output, uint32_t& count, std::span events) - { - count = 0u; - const auto mappedVirtualEventsCount = m_keyboardVirtualEventMap.size(); - if (!output) + //! Manipulate the camera view gimbal directly, + //! on success it may change both the camera view gimbal & target gimbal + bool manipulateViewGimbal(SUpdateParameters parameters, std::chrono::microseconds nextPresentationTimestamp) { - count = mappedVirtualEventsCount; - return; - } - - if (mappedVirtualEventsCount) - { - const auto frameDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); - assert(frameDeltaTime >= 0.f); + // TODO & note to self: + // and here there is a small difference because we don't map any target gimbal position change to virtual orientation events to request camera view + // gimbal manipulation but we directly try to manipulate the view gimbal of our camera and if we success then we simply update our m_target gimbal accordingly - for (auto& [key, hash] : m_keyboardVirtualEventMap) - { - hash.event.magnitude = 0.f; - - /* - if a key was already being held down from previous frames we compute with this - assumption that the key will be held down for this whole frame as well and its - delta action is simply frame delta time - */ + // and whats nice is we can do it with ANY controller now - if (hash.active) - hash.event.magnitude = frameDeltaTime; - } + std::vector virtualEvents(0x45); + uint32_t vCount; - for (const auto& keyboardEvent : events) + beginInputProcessing(nextPresentationTimestamp); { - auto request = m_keyboardVirtualEventMap.find(keyboardEvent.keyCode); - if (request != std::end(m_keyboardVirtualEventMap)) - { - auto& hash = request->second; + process(nullptr, vCount); - if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_PRESSED) - { - if (!hash.active) - { - // and if a key has been pressed after its last release event then first delta action is the key delta - const auto keyDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - keyboardEvent.timeStamp).count(); - assert(keyDeltaTime >= 0); + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); - hash.active = true; - hash.event.magnitude = keyDeltaTime; - } - } - else if (keyboardEvent.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - hash.active = false; - } + process(virtualEvents.data(), vCount, parameters); } + endInputProcessing(); - for (const auto& [key, hash] : m_keyboardVirtualEventMap) - { - if (hash.event.magnitude) - { - auto* virtualEvent = output + count; - virtualEvent->type = hash.event.type; - virtualEvent->magnitude = hash.event.magnitude; - ++count; - } - } - } - } + bool manipulated = m_camera->manipulate({ virtualEvents.data(), vCount }, interface_camera_t::Local); - // Processes mouse events to generate virtual manipulation events - void processMouse(CVirtualGimbalEvent* output, uint32_t& count, std::span events) - { - count = 0u; - const auto mappedVirtualEventsCount = m_mouseVirtualEventMap.size(); - - if (!output) - { - count = mappedVirtualEventsCount; - return; - } - - if (mappedVirtualEventsCount) - { - const auto frameDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); - assert(frameDeltaTime >= 0.f); - - for (auto& [key, hash] : m_mouseVirtualEventMap) + if (manipulated) { - hash.event.magnitude = 0.f; - if (hash.active) - hash.event.magnitude = frameDeltaTime; + // TODO: *any* manipulate success? -> update m_target } - for (const auto& mouseEvent : events) - { - ui::E_MOUSE_CODE mouseCode = ui::EMC_NONE; - - switch (mouseEvent.type) - { - case ui::SMouseEvent::EET_CLICK: - { - switch (mouseEvent.clickEvent.mouseButton) - { - case ui::EMB_LEFT_BUTTON: mouseCode = ui::EMC_LEFT_BUTTON; break; - case ui::EMB_RIGHT_BUTTON: mouseCode = ui::EMC_RIGHT_BUTTON; break; - case ui::EMB_MIDDLE_BUTTON: mouseCode = ui::EMC_MIDDLE_BUTTON; break; - case ui::EMB_BUTTON_4: mouseCode = ui::EMC_BUTTON_4; break; - case ui::EMB_BUTTON_5: mouseCode = ui::EMC_BUTTON_5; break; - default: continue; - } - - auto request = m_mouseVirtualEventMap.find(mouseCode); - if (request != std::end(m_mouseVirtualEventMap)) - { - auto& hash = request->second; - - if (mouseEvent.clickEvent.action == ui::SMouseEvent::SClickEvent::EA_PRESSED) - { - if (!hash.active) - { - const auto keyDeltaTime = std::chrono::duration_cast(nextPresentationTimeStamp - mouseEvent.timeStamp).count(); - assert(keyDeltaTime >= 0); - - hash.active = true; - hash.event.magnitude += keyDeltaTime; - } - } - else if (mouseEvent.clickEvent.action == ui::SMouseEvent::SClickEvent::EA_RELEASED) - hash.active = false; - } - } break; - - case ui::SMouseEvent::EET_SCROLL: - { - if (mouseEvent.scrollEvent.verticalScroll != 0) - { - mouseCode = (mouseEvent.scrollEvent.verticalScroll > 0) ? ui::EMC_VERTICAL_POSITIVE_SCROLL : ui::EMC_VERTICAL_NEGATIVE_SCROLL; - auto request = m_mouseVirtualEventMap.find(mouseCode); - if (request != std::end(m_mouseVirtualEventMap)) - request->second.event.magnitude += std::abs(mouseEvent.scrollEvent.verticalScroll); - } - - if (mouseEvent.scrollEvent.horizontalScroll != 0) - { - mouseCode = (mouseEvent.scrollEvent.horizontalScroll > 0) ? ui::EMC_HORIZONTAL_POSITIVE_SCROLL : ui::EMC_HORIZONTAL_NEGATIVE_SCROLL; - auto request = m_mouseVirtualEventMap.find(mouseCode); - if (request != std::end(m_mouseVirtualEventMap)) - request->second.event.magnitude += std::abs(mouseEvent.scrollEvent.horizontalScroll); - } - } break; - - case ui::SMouseEvent::EET_MOVEMENT: - { - if (mouseEvent.movementEvent.relativeMovementX != 0) - { - mouseCode = (mouseEvent.movementEvent.relativeMovementX > 0) ? ui::EMC_RELATIVE_POSITIVE_MOVEMENT_X : ui::EMC_RELATIVE_NEGATIVE_MOVEMENT_X; - auto request = m_mouseVirtualEventMap.find(mouseCode); - if (request != std::end(m_mouseVirtualEventMap)) - request->second.event.magnitude += std::abs(mouseEvent.movementEvent.relativeMovementX); - } - - if (mouseEvent.movementEvent.relativeMovementY != 0) - { - mouseCode = (mouseEvent.movementEvent.relativeMovementY > 0) ? ui::EMC_RELATIVE_POSITIVE_MOVEMENT_Y : ui::EMC_RELATIVE_NEGATIVE_MOVEMENT_Y; - auto request = m_mouseVirtualEventMap.find(mouseCode); - if (request != std::end(m_mouseVirtualEventMap)) - request->second.event.magnitude += std::abs(mouseEvent.movementEvent.relativeMovementY); - } - } break; + return manipulated; + } - default: - break; - } - } + inline const interface_camera_t* getCamera() { return m_camera.get(); } - for (const auto& [key, hash] : m_mouseVirtualEventMap) - { - if (hash.event.magnitude) - { - auto* virtualEvent = output + count; - virtualEvent->type = hash.event.type; - virtualEvent->magnitude = hash.event.magnitude; - ++count; - } - } - } - } + private: + core::smart_refctd_ptr m_camera; - core::smart_refctd_ptr m_camera; - keyboard_to_virtual_events_t m_keyboardVirtualEventMap; - mouse_to_virtual_events_t m_mouseVirtualEventMap; -}; + //! Represents the camera world target vector as gimbal we can manipulate + CGeneralPurposeGimbal m_target; + }; } // nbl::hlsl namespace -#endif // _NBL_C_CAMERA_CONTROLLER_HPP_ \ No newline at end of file +#endif // _NBL_C_CAMERA_CONTROLLER_HPP_ diff --git a/common/include/camera/CGeneralPurposeGimbal.hpp b/common/include/camera/CGeneralPurposeGimbal.hpp index 9a66d5712..7e1c07096 100644 --- a/common/include/camera/CGeneralPurposeGimbal.hpp +++ b/common/include/camera/CGeneralPurposeGimbal.hpp @@ -9,9 +9,10 @@ namespace nbl::hlsl template class CGeneralPurposeGimbal : public IGimbal { + public: using base_t = IGimbal; - CGeneralPurposeGimbal(base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) {} + CGeneralPurposeGimbal(typename base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) {} ~CGeneralPurposeGimbal() = default; }; } diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp deleted file mode 100644 index 56c3b289d..000000000 --- a/common/include/camera/ICameraControl.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#ifndef _NBL_I_CAMERA_CONTROLLER_HPP_ -#define _NBL_I_CAMERA_CONTROLLER_HPP_ - -#include -#include -#include -#include -#include - -#include "IProjection.hpp" -#include "CGeneralPurposeGimbal.hpp" - -// TODO: DIFFERENT NAMESPACE -namespace nbl::hlsl -{ - -class ICameraController : virtual public core::IReferenceCounted -{ -public: - enum ControllerType : uint8_t - { - Keyboard = 0, - Mouse = 1, - - Count - }; - - struct CKeyInfo - { - union - { - ui::E_KEY_CODE keyboardCode; - ui::E_MOUSE_CODE mouseCode; - }; - - CKeyInfo(ControllerType _type, ui::E_KEY_CODE _keyboardCode) : keyboardCode(_keyboardCode), type(_type) {} - CKeyInfo(ControllerType _type, ui::E_MOUSE_CODE _mouseCode) : mouseCode(_mouseCode), type(_type) {} - - ControllerType type; - }; - - struct CHashInfo - { - CHashInfo() {} - CHashInfo(CVirtualGimbalEvent::VirtualEventType _type) : type(_type) {} - ~CHashInfo() = default; - - CVirtualGimbalEvent::VirtualEventType type = CVirtualGimbalEvent::VirtualEventType::None; - bool active = false; - float64_t dtAction = {}; - }; - - using keys_to_virtual_events_t = std::unordered_map; - - ICameraController() {} - - // Binds key codes to virtual events, the mapKeys lambda will be executed with controller keys_to_virtual_events_t table - void updateKeysToEvent(const std::function& mapKeys) - { - mapKeys(m_keysToVirtualEvents); - } - - inline const keys_to_virtual_events_t& getKeysToVirtualEvents() { return m_keysToVirtualEvents; } - - - void beginInputProcessing(const std::chrono::microseconds _nextPresentationTimeStamp) - { - nextPresentationTimeStamp = _nextPresentationTimeStamp; - } - - void endInputProcessing() - { - lastVirtualUpTimeStamp = nextPresentationTimeStamp; - } - -private: - keys_to_virtual_events_t m_keysToVirtualEvents; - std::chrono::microseconds nextPresentationTimeStamp = {}, lastVirtualUpTimeStamp = {}; -}; - -#if 0 // TOOD: update -template -concept GimbalRange = GeneralPurposeRange && requires -{ - requires ProjectionMatrix::projection_t>; - requires std::same_as, typename ICameraController::projection_t>::CGimbal>; -}; - -template -class IGimbalRange : public IRange -{ -public: - using base_t = IRange; - using range_t = typename base_t::range_t; - using gimbal_t = typename base_t::range_value_t; - - IGimbalRange(range_t&& gimbals) : base_t(std::move(gimbals)) {} - inline const range_t& getGimbals() const { return base_t::m_range; } - -protected: - inline range_t& getGimbals() const { return base_t::m_range; } -}; - -// TODO NOTE: eg. "follow camera" should use GimbalRange::CGimbal, 2u>>, -// one per camera itself and one for target it follows -#endif - -} // nbl::hlsl namespace - -#endif // _NBL_I_CAMERA_CONTROLLER_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICameraController.hpp b/common/include/camera/ICameraController.hpp deleted file mode 100644 index 6df41d709..000000000 --- a/common/include/camera/ICameraController.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef _NBL_I_CAMERA_CONTROLLER_HPP_ -#define _NBL_I_CAMERA_CONTROLLER_HPP_ - -#include -#include -#include -#include -#include - -#include "IProjection.hpp" -#include "CGeneralPurposeGimbal.hpp" - -// TODO: DIFFERENT NAMESPACE -namespace nbl::hlsl -{ - -class ICameraController : virtual public core::IReferenceCounted -{ -public: - enum ControllerType : uint8_t - { - Keyboard, - Mouse, - - Count - }; - - struct CKeyInfo - { - union - { - ui::E_KEY_CODE keyboardCode; - ui::E_MOUSE_CODE mouseCode; - }; - - CKeyInfo(ui::E_KEY_CODE _keyboardCode) : keyboardCode(_keyboardCode), type(Keyboard) {} - CKeyInfo(ui::E_MOUSE_CODE _mouseCode) : mouseCode(_mouseCode), type(Mouse) {} - - ControllerType type; - }; - - struct CHashInfo - { - CHashInfo() {} - CHashInfo(CVirtualGimbalEvent::VirtualEventType _type) : event({ .type = _type }) {} - ~CHashInfo() = default; - - CVirtualGimbalEvent event = {}; - bool active = false; - }; - - void beginInputProcessing(const std::chrono::microseconds _nextPresentationTimeStamp) - { - nextPresentationTimeStamp = _nextPresentationTimeStamp; - } - - void endInputProcessing() - { - lastVirtualUpTimeStamp = nextPresentationTimeStamp; - } - -protected: - std::chrono::microseconds nextPresentationTimeStamp = {}, lastVirtualUpTimeStamp = {}; -}; - -} // nbl::hlsl namespace - -#endif // _NBL_I_CAMERA_CONTROLLER_HPP_ \ No newline at end of file diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index fcc316ddd..334ebbbb8 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -202,6 +202,11 @@ namespace nbl::hlsl glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); }; + IGimbal(const IGimbal&) = default; + IGimbal(IGimbal&&) noexcept = default; + IGimbal& operator=(const IGimbal&) = default; + IGimbal& operator=(IGimbal&&) noexcept = default; + IGimbal(SCreationParameters&& parameters) : m_position(parameters.position), m_orientation(parameters.orientation), m_id(reinterpret_cast(this)) { @@ -261,30 +266,54 @@ namespace nbl::hlsl m_position = newPosition; } + inline void strafe(precision_t distance) + { + move(getXAxis() * distance); + } + + inline void climb(precision_t distance) + { + move(getYAxis() * distance); + } + + inline void advance(precision_t distance) + { + move(getZAxis() * distance); + } + void end() { m_isManipulating = false; } - // Position of gimbal + //! Position of gimbal in world space inline const auto& getPosition() const { return m_position; } - // Orientation of gimbal + //! Orientation of gimbal inline const auto& getOrientation() const { return m_orientation; } - // Orthonormal [getXAxis(), getYAxis(), getZAxis()] orientation matrix + //! Orthonormal [getXAxis(), getYAxis(), getZAxis()] orientation matrix inline const auto& getOrthonornalMatrix() const { return m_orthonormal; } - // Base "right" vector in orthonormal orientation basis (X-axis) + //! Base "right" vector in orthonormal orientation basis (X-axis) inline const auto& getXAxis() const { return m_orthonormal[0u]; } - // Base "up" vector in orthonormal orientation basis (Y-axis) + //! Base "up" vector in orthonormal orientation basis (Y-axis) inline const auto& getYAxis() const { return m_orthonormal[1u]; } - // Base "forward" vector in orthonormal orientation basis (Z-axis) + //! Base "forward" vector in orthonormal orientation basis (Z-axis) inline const auto& getZAxis() const { return m_orthonormal[2u]; } + //! Target vector in local space, alias for getZAxis() + inline const auto getLocalTarget() const { return getZAxis(); } + + //! Target vector in world space + inline const auto getWorldTarget() const { return getPosition() + getLocalTarget(); } + + //! Counts how many times a valid manipulation has been performed, the counter resets when begin() is called inline const auto& getManipulationCounter() { return m_counter; } + + //! Returns true if gimbal records a manipulation inline bool isManipulating() const { return m_isManipulating; } private: @@ -293,17 +322,23 @@ namespace nbl::hlsl m_orthonormal = matrix(glm::mat3_cast(glm::normalize(m_orientation))); } + //! Position of a gimbal in world space vector m_position; - glm::quat m_orientation; // TODO: precision + + //! Normalized orientation of gimbal + //! TODO: precision + replace with our "quat at home" + glm::quat m_orientation; + + //! Orthonormal base composed from "m_orientation" representing gimbal's "forward", "up" & "right" vectors in local space matrix m_orthonormal; - // Counts *performed* manipulations, a manipulation with 0 delta is not counted! + //! Counter that increments for each performed manipulation, resets with each begin() call size_t m_counter = {}; - // Records manipulation state + //! Tracks whether gimbal is currently in manipulation mode bool m_isManipulating = false; - // the fact ImGUIZMO has global context I don't like, however for IDs we can do a life-tracking trick and cast addresses which are unique & we don't need any global associative container to track them! + //! The fact ImGUIZMO has global context I don't like, however for IDs we can do a life-tracking trick and cast addresses which are unique & we don't need any global associative container to track them! const uintptr_t m_id; }; } // namespace nbl::hlsl diff --git a/common/include/camera/IGimbalController.hpp b/common/include/camera/IGimbalController.hpp new file mode 100644 index 000000000..a1201a7c1 --- /dev/null +++ b/common/include/camera/IGimbalController.hpp @@ -0,0 +1,374 @@ +#ifndef _NBL_I_CAMERA_CONTROLLER_HPP_ +#define _NBL_I_CAMERA_CONTROLLER_HPP_ + +///////////////////////// +// TODO: TEMPORARY!!! +#include "common.hpp" +namespace ImGuizmo +{ + void DecomposeMatrixToComponents(const float*, float*, float*, float*); +} +///////////////////////// + +#include "IProjection.hpp" +#include "IGimbal.hpp" + +// TODO: DIFFERENT NAMESPACE +namespace nbl::hlsl +{ + +struct IGimbalManipulateEncoder +{ + IGimbalManipulateEncoder() {} + virtual ~IGimbalManipulateEncoder() {} + + //! output of any controller process method + using gimbal_event_t = CVirtualGimbalEvent; + + //! encode keyboard code used to translate to gimbal_event_t event + using encode_keyboard_code_t = ui::E_KEY_CODE; + + //! encode mouse code used to translate to gimbal_event_t event + using encode_mouse_code_t = ui::E_MOUSE_CODE; + + //! encode ImGuizmo code used to translate to gimbal_event_t event + using encode_imguizmo_code_t = gimbal_event_t::VirtualEventType; + + //! Encoder types, a controller takes encoder type events and outputs gimbal_event_t events + enum EncoderType : uint8_t + { + Keyboard, + Mouse, + Imguizmo, + + Count + }; + + //! A key in a hash map which is "encode__code_t" as union with information about EncoderType the encode value got produced from + struct CKeyInfo + { + union + { + encode_keyboard_code_t keyboardCode; + encode_mouse_code_t mouseCode; + encode_imguizmo_code_t imguizmoCode; + }; + + CKeyInfo(encode_keyboard_code_t code) : keyboardCode(code), type(Keyboard) {} + CKeyInfo(encode_mouse_code_t code) : mouseCode(code), type(Mouse) {} + CKeyInfo(encode_imguizmo_code_t code) : imguizmoCode(code), type(Imguizmo) {} + + EncoderType type; + }; + + //! Hash value in hash map which is gimbal_event_t & state + struct CHashInfo + { + CHashInfo() {} + CHashInfo(gimbal_event_t::VirtualEventType _type) : event({ .type = _type }) {} + ~CHashInfo() = default; + + gimbal_event_t event = {}; + bool active = false; + }; + + using keyboard_to_virtual_events_t = std::unordered_map; + using mouse_to_virtual_events_t = std::unordered_map; + using imguizmo_to_virtual_events_t = std::unordered_map; + + //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map + virtual const keyboard_to_virtual_events_t& getKeyboardMappingPreset() const = 0u; + + //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map + virtual const mouse_to_virtual_events_t& getMouseMappingPreset() const = 0u; + + //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map + virtual const imguizmo_to_virtual_events_t& getImguizmoMappingPreset() const = 0u; +}; + +class IGimbalController : public IGimbalManipulateEncoder, virtual public core::IReferenceCounted +{ +public: + using IGimbalManipulateEncoder::IGimbalManipulateEncoder; + + IGimbalController() {} + virtual ~IGimbalController() {} + + //! input of keyboard gimbal controller process utility - Nabla UI event handler produces ui::SKeyboardEvent events + using input_keyboard_event_t = ui::SKeyboardEvent; + + //! input of mouse gimbal controller process utility - Nabla UI event handler produces ui::SMouseEvent events + using input_mouse_event_t = ui::SMouseEvent; + + //! input of ImGuizmo gimbal controller process utility - ImGuizmo manipulate utility produces "delta (TRS) matrix" events + using input_imguizmo_event_t = float32_t4x4; + + void beginInputProcessing(const std::chrono::microseconds nextPresentationTimeStamp) + { + m_nextPresentationTimeStamp = nextPresentationTimeStamp; + m_frameDeltaTime = std::chrono::duration_cast(m_nextPresentationTimeStamp - m_lastVirtualUpTimeStamp).count(); + assert(m_frameDeltaTime >= 0.f); + } + + void endInputProcessing() + { + m_lastVirtualUpTimeStamp = m_nextPresentationTimeStamp; + } + + // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller keyboard_to_virtual_events_t table + void updateKeyboardMapping(const std::function& mapKeys) { mapKeys(m_keyboardVirtualEventMap); } + + // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller mouse_to_virtual_events_t table + void updateMouseMapping(const std::function& mapKeys) { mapKeys(m_mouseVirtualEventMap); } + + // Binds imguizmo key codes to virtual events, the mapKeys lambda will be executed with controller imguizmo_to_virtual_events_t table + void updateImguizmoMapping(const std::function& mapKeys) { mapKeys(m_imguizmoVirtualEventMap); } + + struct SUpdateParameters + { + std::span keyboardEvents = {}; + std::span mouseEvents = {}; + std::span imguizmoEvents = {}; + }; + + // Processes SUpdateParameters events to generate virtual manipulation events + void process(gimbal_event_t* output, uint32_t& count, const SUpdateParameters parameters = {}) + { + count = 0u; + uint32_t vKeyboardEventsCount = {}, vMouseEventsCount = {}, vImguizmoEventsCount = {}; + + if (output) + { + processKeyboard(output, vKeyboardEventsCount, parameters.keyboardEvents); output += vKeyboardEventsCount; + processMouse(output, vMouseEventsCount, parameters.mouseEvents); output += vMouseEventsCount; + processImguizmo(output, vImguizmoEventsCount, parameters.imguizmoEvents); + } + else + { + processKeyboard(nullptr, vKeyboardEventsCount, {}); + processMouse(nullptr, vMouseEventsCount, {}); + processImguizmo(nullptr, vImguizmoEventsCount, {}); + } + + count = vKeyboardEventsCount + vMouseEventsCount + vImguizmoEventsCount; + } + + inline const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() { return m_keyboardVirtualEventMap; } + inline const mouse_to_virtual_events_t& getMouseVirtualEventMap() { return m_mouseVirtualEventMap; } + inline const imguizmo_to_virtual_events_t& getImguizmoVirtualEventMap() { return m_imguizmoVirtualEventMap; } + +private: + // Processes keyboard events to generate virtual manipulation events + void processKeyboard(gimbal_event_t* output, uint32_t& count, std::span events) + { + count = 0u; + const auto mappedVirtualEventsCount = m_keyboardVirtualEventMap.size(); + + if (!output) + { + count = mappedVirtualEventsCount; + return; + } + + if (mappedVirtualEventsCount) + { + preprocess(m_keyboardVirtualEventMap); + + for (const auto& keyboardEvent : events) + { + auto request = m_keyboardVirtualEventMap.find(keyboardEvent.keyCode); + if (request != std::end(m_keyboardVirtualEventMap)) + { + auto& hash = request->second; + + if (keyboardEvent.action == input_keyboard_event_t::ECA_PRESSED) + { + if (!hash.active) + { + const auto keyDeltaTime = std::chrono::duration_cast(m_nextPresentationTimeStamp - keyboardEvent.timeStamp).count(); + assert(keyDeltaTime >= 0); + + hash.active = true; + hash.event.magnitude = keyDeltaTime; + } + } + else if (keyboardEvent.action == input_keyboard_event_t::ECA_RELEASED) + hash.active = false; + } + } + + postprocess(m_keyboardVirtualEventMap, output, count); + } + } + + // Processes mouse events to generate virtual manipulation events + void processMouse(gimbal_event_t* output, uint32_t& count, std::span events) + { + count = 0u; + const auto mappedVirtualEventsCount = m_mouseVirtualEventMap.size(); + + if (!output) + { + count = mappedVirtualEventsCount; + return; + } + + if (mappedVirtualEventsCount) + { + preprocess(m_mouseVirtualEventMap); + + for (const auto& mouseEvent : events) + { + ui::E_MOUSE_CODE mouseCode = ui::EMC_NONE; + + switch (mouseEvent.type) + { + case input_mouse_event_t::EET_CLICK: + { + switch (mouseEvent.clickEvent.mouseButton) + { + case ui::EMB_LEFT_BUTTON: mouseCode = ui::EMC_LEFT_BUTTON; break; + case ui::EMB_RIGHT_BUTTON: mouseCode = ui::EMC_RIGHT_BUTTON; break; + case ui::EMB_MIDDLE_BUTTON: mouseCode = ui::EMC_MIDDLE_BUTTON; break; + case ui::EMB_BUTTON_4: mouseCode = ui::EMC_BUTTON_4; break; + case ui::EMB_BUTTON_5: mouseCode = ui::EMC_BUTTON_5; break; + default: continue; + } + + auto request = m_mouseVirtualEventMap.find(mouseCode); + if (request != std::end(m_mouseVirtualEventMap)) + { + auto& hash = request->second; + + if (mouseEvent.clickEvent.action == input_mouse_event_t::SClickEvent::EA_PRESSED) + { + if (!hash.active) + { + const auto keyDeltaTime = std::chrono::duration_cast(m_nextPresentationTimeStamp - mouseEvent.timeStamp).count(); + assert(keyDeltaTime >= 0); + + hash.active = true; + hash.event.magnitude += keyDeltaTime; + } + } + else if (mouseEvent.clickEvent.action == input_mouse_event_t::SClickEvent::EA_RELEASED) + hash.active = false; + } + } break; + + case input_mouse_event_t::EET_SCROLL: + { + requestMagnitudeUpdateWithScalar(0.f, float(mouseEvent.scrollEvent.verticalScroll), float(std::abs(mouseEvent.scrollEvent.verticalScroll)), ui::EMC_VERTICAL_POSITIVE_SCROLL, ui::EMC_VERTICAL_NEGATIVE_SCROLL, m_mouseVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, mouseEvent.scrollEvent.horizontalScroll, std::abs(mouseEvent.scrollEvent.horizontalScroll), ui::EMC_HORIZONTAL_POSITIVE_SCROLL, ui::EMC_HORIZONTAL_NEGATIVE_SCROLL, m_mouseVirtualEventMap); + } break; + + case input_mouse_event_t::EET_MOVEMENT: + { + requestMagnitudeUpdateWithScalar(0.f, mouseEvent.movementEvent.relativeMovementX, std::abs(mouseEvent.movementEvent.relativeMovementX), ui::EMC_RELATIVE_POSITIVE_MOVEMENT_X, ui::EMC_RELATIVE_NEGATIVE_MOVEMENT_X, m_mouseVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, mouseEvent.movementEvent.relativeMovementY, std::abs(mouseEvent.movementEvent.relativeMovementY), ui::EMC_RELATIVE_POSITIVE_MOVEMENT_Y, ui::EMC_RELATIVE_NEGATIVE_MOVEMENT_Y, m_mouseVirtualEventMap); + } break; + + default: + break; + } + } + + postprocess(m_mouseVirtualEventMap, output, count); + } + } + + void processImguizmo(gimbal_event_t* output, uint32_t& count, std::span events) + { + count = 0u; + const auto mappedVirtualEventsCount = m_imguizmoVirtualEventMap.size(); + + if (!output) + { + count = mappedVirtualEventsCount; + return; + } + + if (mappedVirtualEventsCount) + { + preprocess(m_imguizmoVirtualEventMap); + + for (const auto& deltaMatrix : events) + { + std::array dTranslation, dRotation, dScale; + + // TODO: since I assume the delta matrix is from imguizmo I must follow its API (but this one I could actually steal & rewrite to Nabla to not call imguizmo stuff here) + ImGuizmo::DecomposeMatrixToComponents(&deltaMatrix[0][0], dTranslation.data(), dRotation.data(), dScale.data()); + + // note that translating imguizmo delta matrix to gimbal_event_t events is indeed hardcoded, but what + // *isn't* is those gimbal_event_t events are then translated according to m_imguizmoVirtualEventMap + // user defined map to + + // Delta translation impulse + requestMagnitudeUpdateWithScalar(0.f, dTranslation[0], dTranslation[0], gimbal_event_t::MoveRight, gimbal_event_t::MoveLeft, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, dTranslation[1], dTranslation[1], gimbal_event_t::MoveUp, gimbal_event_t::MoveDown, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, dTranslation[2], dTranslation[2], gimbal_event_t::MoveForward, gimbal_event_t::MoveBackward, m_imguizmoVirtualEventMap); + + // Delta rotation impulse + requestMagnitudeUpdateWithScalar(0.f, dRotation[0], dRotation[0], gimbal_event_t::PanRight, gimbal_event_t::PanLeft, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, dRotation[1], dRotation[1], gimbal_event_t::TiltUp, gimbal_event_t::TiltDown, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, dRotation[2], dRotation[2], gimbal_event_t::RollRight, gimbal_event_t::RollLeft, m_imguizmoVirtualEventMap); + + // Delta scale impulse + requestMagnitudeUpdateWithScalar(1.f, dScale[0], dScale[0], gimbal_event_t::ScaleXInc, gimbal_event_t::ScaleXDec, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(1.f, dScale[1], dScale[1], gimbal_event_t::ScaleYInc, gimbal_event_t::ScaleYDec, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(1.f, dScale[2], dScale[2], gimbal_event_t::ScaleZInc, gimbal_event_t::ScaleZDec, m_imguizmoVirtualEventMap); + } + + postprocess(m_imguizmoVirtualEventMap, output, count); + } + } + + //! helper utility, for any controller this should be called before any update of hash map + void preprocess(auto& map) + { + for (auto& [key, hash] : map) + { + hash.event.magnitude = 0.0f; + + if (hash.active) + hash.event.magnitude = m_frameDeltaTime; + } + } + + //! helper utility, for any controller this should be called after updating a hash map + void postprocess(const auto& map, gimbal_event_t* output, uint32_t& count) + { + for (const auto& [key, hash] : map) + if (hash.event.magnitude) + { + auto* virtualEvent = output + count; + virtualEvent->type = hash.event.type; + virtualEvent->magnitude = hash.event.magnitude; + ++count; + } + } + + //! helper utility, it *doesn't* assume we keep requested events alive but only increase their magnitude + template + void requestMagnitudeUpdateWithScalar(float signPivot, float dScalar, float dMagnitude, EncodeType positive, EncodeType negative, Map& map) + { + if (dScalar != signPivot) + { + auto code = (dScalar > signPivot) ? positive : negative; + auto request = map.find(code); + if (request != map.end()) + request->second.event.magnitude += dMagnitude; + } + } + + keyboard_to_virtual_events_t m_keyboardVirtualEventMap; + mouse_to_virtual_events_t m_mouseVirtualEventMap; + imguizmo_to_virtual_events_t m_imguizmoVirtualEventMap; + + size_t m_frameDeltaTime = {}; + std::chrono::microseconds m_nextPresentationTimeStamp = {}, m_lastVirtualUpTimeStamp = {}; +}; + +} // nbl::hlsl namespace + +#endif // _NBL_I_CAMERA_CONTROLLER_HPP_ \ No newline at end of file From 797ba40eacaf8c03417d9b20df8d051edc80413d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 12 Nov 2024 08:22:37 +0100 Subject: [PATCH 32/84] accumulate transform in FPS camera' base_t::World case with respect to standard base (write a note in comments), adjust UI's main.cpp to new frames in flight + a few small updates --- 61_UI/main.cpp | 30 ++++++++--------- common/include/CFPSCamera.hpp | 11 +++++-- common/include/ICamera.hpp | 55 ++++++++++++++----------------- common/include/camera/IGimbal.hpp | 1 + 4 files changed, 48 insertions(+), 49 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 3851d7177..520682545 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -12,6 +12,9 @@ using camera_t = CFPSCamera; using controller_t = CCameraController; using projection_t = IProjection>; // TODO: temporary -> projections will own/reference cameras +// Maximum frames which can be simultaneously submitted, used to cycle through our per-frame resources like command buffers +constexpr static inline uint32_t MaxFramesInFlight = 3u; + /* Renders scene texture to an offline framebuffer which color attachment @@ -117,16 +120,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (!m_surface || !m_surface->init(gQueue, std::move(scResources), swapchainParams.sharedParams)) return logFail("Could not create Window & Surface or initialize the Surface!"); - m_maxFramesInFlight = m_surface->getMaxFramesInFlight(); - if (FRAMES_IN_FLIGHT < m_maxFramesInFlight) - { - m_logger->log("Lowering frames in flight!", ILogger::ELL_WARNING); - m_maxFramesInFlight = FRAMES_IN_FLIGHT; - } - m_cmdPool = m_device->createCommandPool(gQueue->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); - for (auto i = 0u; i < m_maxFramesInFlight; i++) + for (auto i = 0u; i < MaxFramesInFlight; i++) { if (!m_cmdPool) return logFail("Couldn't create Command Pool!"); @@ -562,21 +558,26 @@ class UISampleApp final : public examples::SimpleWindowedApplication inline void workLoopBody() override { - const auto resourceIx = m_realFrameIx % m_maxFramesInFlight; - - if (m_realFrameIx >= m_maxFramesInFlight) + // framesInFlight: ensuring safe execution of command buffers and acquires, `framesInFlight` only affect semaphore waits, don't use this to index your resources because it can change with swapchain recreation. + const uint32_t framesInFlight = core::min(MaxFramesInFlight, m_surface->getMaxAcquiresInFlight()); + // We block for semaphores for 2 reasons here: + // A) Resource: Can't use resource like a command buffer BEFORE previous use is finished! [MaxFramesInFlight] + // B) Acquire: Can't have more acquires in flight than a certain threshold returned by swapchain or your surface helper class. [MaxAcquiresInFlight] + if (m_realFrameIx >= framesInFlight) { const ISemaphore::SWaitInfo cbDonePending[] = { { .semaphore = m_semaphore.get(), - .value = m_realFrameIx + 1 - m_maxFramesInFlight + .value = m_realFrameIx + 1 - framesInFlight } }; if (m_device->blockForSemaphores(cbDonePending) != ISemaphore::WAIT_RESULT::SUCCESS) return; } + const auto resourceIx = m_realFrameIx % MaxFramesInFlight; + // CPU events update(); @@ -775,9 +776,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication smart_refctd_ptr m_pipeline; smart_refctd_ptr m_semaphore; smart_refctd_ptr m_cmdPool; - uint64_t m_realFrameIx : 59 = 0; - uint64_t m_maxFramesInFlight : 5; - std::array, ISwapchain::MaxImages> m_cmdBufs; + uint64_t m_realFrameIx = 0; + std::array, MaxFramesInFlight> m_cmdBufs; ISimpleManagedSurface::SAcquireResult m_currentImageAcquire = {}; smart_refctd_ptr m_assetManager; diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index e0f9dec53..dc358ecfe 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -53,7 +53,9 @@ class CFPSCamera final : public ICamera case base_t::World: { - const auto transform = m_gimbal.accumulate(virtualEvents); + // TODO: I make assumption what world base is now (at least temporary), I need to think of this but maybe each ITransformObject should know what its world is + // in ideal scenario we would define this crazy enum with all possible standard bases but in Nabla we only really care about LH/RH coordinate systems + const auto transform = m_gimbal.accumulate(virtualEvents, { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }); const auto newPitch = std::clamp(transform.dVirtualRotation.x, MinVerticalAngle, MaxVerticalAngle), newYaw = transform.dVirtualRotation.y; newOrientation = glm::quat(glm::vec3(newPitch, newYaw, 0.0f)); newPosition = transform.dVirtualTranslate; } break; @@ -67,11 +69,14 @@ class CFPSCamera final : public ICamera { m_gimbal.setOrientation(newOrientation); m_gimbal.setPosition(newPosition); - m_gimbal.updateView(); - manipulated &= bool(m_gimbal.getManipulationCounter()); } m_gimbal.end(); + manipulated &= bool(m_gimbal.getManipulationCounter()); + + if(manipulated) + m_gimbal.updateView(); + return manipulated; } diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index fbbb5a863..630c7b624 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -40,41 +40,34 @@ class ICamera : public IGimbalManipulateEncoder, virtual public core::IReference struct SView { matrix matrix = {}; - bool isLeftHandSystem = true; }; inline void updateView() - { - if (base_t::getManipulationCounter()) + { + const auto& gRight = base_t::getXAxis(), gUp = base_t::getYAxis(), gForward = base_t::getZAxis(); + + auto isNormalized = [](const auto& v, float epsilon) -> bool + { + return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); + }; + + auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool { - const auto& gRight = base_t::getXAxis(), gUp = base_t::getYAxis(), gForward = base_t::getZAxis(); - - // TODO: I think this should be set as request state, depending on the value we do m_view.matrix[2u] flip accordingly - // m_view.isLeftHandSystem; - - auto isNormalized = [](const auto& v, float epsilon) -> bool - { - return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); - }; - - auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool - { - return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); - }; - - auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool - { - return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && - isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); - }; - - assert(isOrthoBase(gRight, gUp, gForward)); - - const auto& position = base_t::getPosition(); - m_view.matrix[0u] = vector(gRight, -glm::dot(gRight, position)); - m_view.matrix[1u] = vector(gUp, -glm::dot(gUp, position)); - m_view.matrix[2u] = vector(gForward, -glm::dot(gForward, position)); - } + return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); + }; + + auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool + { + return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && + isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); + }; + + assert(isOrthoBase(gRight, gUp, gForward)); + + const auto& position = base_t::getPosition(); + m_view.matrix[0u] = vector(gRight, -glm::dot(gRight, position)); + m_view.matrix[1u] = vector(gUp, -glm::dot(gUp, position)); + m_view.matrix[2u] = vector(gForward, -glm::dot(gForward, position)); } // Getter for gimbal's view diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index 334ebbbb8..341f55e17 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -102,6 +102,7 @@ namespace nbl::hlsl vector dVirtualTranslate { 0.0f }, dVirtualRotation { 0.0f }, dVirtualScale { 0.0f }; }; + // TODO: document it template VirtualImpulse accumulate(std::span virtualEvents, const vector& gRightOverride, const vector& gUpOverride, const vector& gForwardOverride) { From ae20180ba8f6dea8c7efd52c76cd0a80233e7165 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 14 Nov 2024 14:28:06 +0100 Subject: [PATCH 33/84] I need more space for more viewports, allow to resize the window --- 61_UI/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 520682545..a350ba107 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -51,7 +51,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication params.height = WIN_H; params.x = 32; params.y = 32; - params.flags = ui::IWindow::ECF_HIDDEN | IWindow::ECF_BORDERLESS | IWindow::ECF_RESIZABLE; + params.flags = ui::IWindow::ECF_HIDDEN | IWindow::ECF_BORDERLESS | IWindow::ECF_RESIZABLE | IWindow::ECF_CAN_RESIZE | IWindow::ECF_CAN_MAXIMIZE; params.windowCaption = "UISampleApp"; params.callback = windowCallback; const_cast&>(m_window) = m_winMgr->createWindow(std::move(params)); @@ -603,8 +603,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication viewport.maxDepth = 0.f; viewport.x = 0u; viewport.y = 0u; - viewport.width = WIN_W; - viewport.height = WIN_H; + viewport.width = m_window->getWidth(); + viewport.height = m_window->getHeight(); } cb->setViewport(0u, 1u, &viewport); From 6a39b3d47868f12dedd159b9545022d4a9dc0658 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 15 Nov 2024 09:23:48 +0100 Subject: [PATCH 34/84] set better initial position & orientation of camera, cleanup in GC shaders --- 61_UI/main.cpp | 20 ++++------ common/include/CFPSCamera.hpp | 2 +- common/src/geometry/creator/CMakeLists.txt | 4 -- .../creator/shaders/gc.basic.fragment.hlsl | 2 +- .../creator/shaders/gc.basic.vertex.hlsl | 13 +++--- .../creator/shaders/gc.cone.vertex.hlsl | 12 +++--- .../creator/shaders/gc.ico.vertex.hlsl | 12 +++--- .../creator/shaders/grid.fragment.hlsl | 12 ------ .../geometry/creator/shaders/grid.vertex.hlsl | 17 -------- .../template/gc.basic.vertex.input.hlsl | 16 -------- .../creator/shaders/template/gc.common.hlsl | 4 -- .../template/gc.cone.vertex.input.hlsl | 15 ------- .../shaders/template/gc.ico.vertex.input.hlsl | 15 ------- .../creator/shaders/template/gc.vertex.hlsl | 4 -- .../creator/shaders/template/grid.common.hlsl | 40 ------------------- 15 files changed, 31 insertions(+), 157 deletions(-) delete mode 100644 common/src/geometry/creator/shaders/grid.fragment.hlsl delete mode 100644 common/src/geometry/creator/shaders/grid.vertex.hlsl delete mode 100644 common/src/geometry/creator/shaders/template/gc.basic.vertex.input.hlsl delete mode 100644 common/src/geometry/creator/shaders/template/gc.cone.vertex.input.hlsl delete mode 100644 common/src/geometry/creator/shaders/template/gc.ico.vertex.input.hlsl delete mode 100644 common/src/geometry/creator/shaders/template/grid.common.hlsl diff --git a/61_UI/main.cpp b/61_UI/main.cpp index a350ba107..12c204d0f 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -51,7 +51,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication params.height = WIN_H; params.x = 32; params.y = 32; - params.flags = ui::IWindow::ECF_HIDDEN | IWindow::ECF_BORDERLESS | IWindow::ECF_RESIZABLE | IWindow::ECF_CAN_RESIZE | IWindow::ECF_CAN_MAXIMIZE; + params.flags = ui::IWindow::ECF_HIDDEN | IWindow::ECF_BORDERLESS | IWindow::ECF_MAXIMIZED; params.windowCaption = "UISampleApp"; params.callback = windowCallback; const_cast&>(m_window) = m_winMgr->createWindow(std::move(params)); @@ -407,16 +407,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto& orientation = camera->getGimbal().getOrthonornalMatrix(); - addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); - - addMatrixTable("Right", "OrientationRightVec", 1, 3, &camera->getGimbal().getXAxis()[0]); - addMatrixTable("Up", "OrientationUpVec", 1, 3, &camera->getGimbal().getYAxis()[0]); - addMatrixTable("Forward", "OrientationForwardVec", 1, 3, &camera->getGimbal().getZAxis()[0]); - addMatrixTable("Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); - - //addMatrixTable("Camera Gimbal Orientation Matrix", "OrientationMatrixTable", 3, 3, &orientation[0][0]); - addMatrixTable("Camera Gimbal View Matrix", "ViewMatrixTable", 3, 4, &view.matrix[0][0]); - addMatrixTable("Camera Gimbal Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); + addMatrixTable("Object's Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); + addMatrixTable("Camera's Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); + addMatrixTable("Camera's Orientation Quat", "OrientationQuatTable", 1, 4, &camera->getGimbal().getOrientation()[0]); + addMatrixTable("Camera's View Matrix", "ViewMatrixTable", 3, 4, &view.matrix[0][0]); + addMatrixTable("Bound Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); ImGui::End(); } @@ -505,9 +500,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication TESTS, TODO: remove all once finished work & integrate with the example properly */ - const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); - camera = make_smart_refctd_ptr(position); + camera = make_smart_refctd_ptr(float32_t3{ -1.958f, 0.697f, 0.881f }, glm::quat(0.092f, 0.851f, -0.159f, 0.492f)); controller = make_smart_refctd_ptr(core::smart_refctd_ptr(camera)); // init keyboard map diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index dc358ecfe..92943d01e 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -17,7 +17,7 @@ class CFPSCamera final : public ICamera public: using base_t = ICamera; - CFPSCamera(const vector& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + CFPSCamera(const vector& position, const glm::quat& orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) : base_t(), m_gimbal({ .position = position, .orientation = orientation }) {} ~CFPSCamera() = default; diff --git a/common/src/geometry/creator/CMakeLists.txt b/common/src/geometry/creator/CMakeLists.txt index 336d32fe5..f0e824ebe 100644 --- a/common/src/geometry/creator/CMakeLists.txt +++ b/common/src/geometry/creator/CMakeLists.txt @@ -12,10 +12,6 @@ set(NBL_THIS_EXAMPLE_INPUT_SHADERS "${NBL_THIS_EXAMPLE_INPUT_SHADERS_DIRECTORY}/gc.basic.vertex.hlsl" "${NBL_THIS_EXAMPLE_INPUT_SHADERS_DIRECTORY}/gc.cone.vertex.hlsl" "${NBL_THIS_EXAMPLE_INPUT_SHADERS_DIRECTORY}/gc.ico.vertex.hlsl" - - # grid - "${NBL_THIS_EXAMPLE_INPUT_SHADERS_DIRECTORY}/grid.vertex.hlsl" - "${NBL_THIS_EXAMPLE_INPUT_SHADERS_DIRECTORY}/grid.fragment.hlsl" ) file(GLOB_RECURSE NBL_THIS_EXAMPLE_INPUT_COMMONS CONFIGURE_DEPENDS "${NBL_THIS_EXAMPLE_INPUT_SHADERS_DIRECTORY}/template/*.hlsl") diff --git a/common/src/geometry/creator/shaders/gc.basic.fragment.hlsl b/common/src/geometry/creator/shaders/gc.basic.fragment.hlsl index 3dc9b9f1d..fd5cd4d34 100644 --- a/common/src/geometry/creator/shaders/gc.basic.fragment.hlsl +++ b/common/src/geometry/creator/shaders/gc.basic.fragment.hlsl @@ -3,4 +3,4 @@ float4 PSMain(PSInput input) : SV_Target0 { return input.color; -} \ No newline at end of file +} diff --git a/common/src/geometry/creator/shaders/gc.basic.vertex.hlsl b/common/src/geometry/creator/shaders/gc.basic.vertex.hlsl index 1afd468d9..4ca66e4ab 100644 --- a/common/src/geometry/creator/shaders/gc.basic.vertex.hlsl +++ b/common/src/geometry/creator/shaders/gc.basic.vertex.hlsl @@ -1,6 +1,9 @@ -#include "template/gc.basic.vertex.input.hlsl" -#include "template/gc.vertex.hlsl" +struct VSInput +{ + [[vk::location(0)]] float3 position : POSITION; + [[vk::location(1)]] float4 color : COLOR; + [[vk::location(2)]] float2 uv : TEXCOORD; + [[vk::location(3)]] float3 normal : NORMAL; +}; -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ +#include "template/gc.vertex.hlsl" diff --git a/common/src/geometry/creator/shaders/gc.cone.vertex.hlsl b/common/src/geometry/creator/shaders/gc.cone.vertex.hlsl index ee0c42431..0552842a5 100644 --- a/common/src/geometry/creator/shaders/gc.cone.vertex.hlsl +++ b/common/src/geometry/creator/shaders/gc.cone.vertex.hlsl @@ -1,6 +1,8 @@ -#include "template/gc.cone.vertex.input.hlsl" -#include "template/gc.vertex.hlsl" +struct VSInput +{ + [[vk::location(0)]] float3 position : POSITION; + [[vk::location(1)]] float4 color : COLOR; + [[vk::location(2)]] float3 normal : NORMAL; +}; -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ +#include "template/gc.vertex.hlsl" diff --git a/common/src/geometry/creator/shaders/gc.ico.vertex.hlsl b/common/src/geometry/creator/shaders/gc.ico.vertex.hlsl index d63fdc809..917bd2677 100644 --- a/common/src/geometry/creator/shaders/gc.ico.vertex.hlsl +++ b/common/src/geometry/creator/shaders/gc.ico.vertex.hlsl @@ -1,6 +1,8 @@ -#include "template/gc.ico.vertex.input.hlsl" -#include "template/gc.vertex.hlsl" +struct VSInput +{ + [[vk::location(0)]] float3 position : POSITION; + [[vk::location(1)]] float3 normal : NORMAL; + [[vk::location(2)]] float2 uv : TEXCOORD; +}; -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ +#include "template/gc.vertex.hlsl" diff --git a/common/src/geometry/creator/shaders/grid.fragment.hlsl b/common/src/geometry/creator/shaders/grid.fragment.hlsl deleted file mode 100644 index 4b4c1e691..000000000 --- a/common/src/geometry/creator/shaders/grid.fragment.hlsl +++ /dev/null @@ -1,12 +0,0 @@ -#include "template/grid.common.hlsl" - -float4 PSMain(PSInput input) : SV_Target0 -{ - float2 uv = (input.uv - float2(0.5, 0.5)) + 0.5 / 30.0; - float grid = gridTextureGradBox(uv, ddx(input.uv), ddy(input.uv)); - float4 fragColor = float4(1.0 - grid, 1.0 - grid, 1.0 - grid, 1.0); - fragColor *= 0.25; - fragColor *= 0.3 + 0.6 * smoothstep(0.0, 0.1, 1.0 - length(input.uv) / 5.5); - - return fragColor; -} \ No newline at end of file diff --git a/common/src/geometry/creator/shaders/grid.vertex.hlsl b/common/src/geometry/creator/shaders/grid.vertex.hlsl deleted file mode 100644 index 167b981d3..000000000 --- a/common/src/geometry/creator/shaders/grid.vertex.hlsl +++ /dev/null @@ -1,17 +0,0 @@ -#include "template/grid.common.hlsl" - -// set 1, binding 0 -[[vk::binding(0, 1)]] -cbuffer CameraData -{ - SBasicViewParameters params; -}; - -PSInput VSMain(VSInput input) -{ - PSInput output; - output.position = mul(params.MVP, float4(input.position, 1.0)); - output.uv = (input.uv - float2(0.5, 0.5)) * abs(input.position.xy); - - return output; -} \ No newline at end of file diff --git a/common/src/geometry/creator/shaders/template/gc.basic.vertex.input.hlsl b/common/src/geometry/creator/shaders/template/gc.basic.vertex.input.hlsl deleted file mode 100644 index d9e2fa172..000000000 --- a/common/src/geometry/creator/shaders/template/gc.basic.vertex.input.hlsl +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _THIS_EXAMPLE_GC_BASIC_VERTEX_INPUT_HLSL_ -#define _THIS_EXAMPLE_GC_BASIC_VERTEX_INPUT_HLSL_ - -struct VSInput -{ - [[vk::location(0)]] float3 position : POSITION; - [[vk::location(1)]] float4 color : COLOR; - [[vk::location(2)]] float2 uv : TEXCOORD; - [[vk::location(3)]] float3 normal : NORMAL; -}; - -#endif // _THIS_EXAMPLE_GC_BASIC_VERTEX_INPUT_HLSL_ - -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ diff --git a/common/src/geometry/creator/shaders/template/gc.common.hlsl b/common/src/geometry/creator/shaders/template/gc.common.hlsl index 4590cd4a3..d0f9aa116 100644 --- a/common/src/geometry/creator/shaders/template/gc.common.hlsl +++ b/common/src/geometry/creator/shaders/template/gc.common.hlsl @@ -12,7 +12,3 @@ #include "SBasicViewParameters.hlsl" #endif // _THIS_EXAMPLE_GC_COMMON_HLSL_ - -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ \ No newline at end of file diff --git a/common/src/geometry/creator/shaders/template/gc.cone.vertex.input.hlsl b/common/src/geometry/creator/shaders/template/gc.cone.vertex.input.hlsl deleted file mode 100644 index 66221fef1..000000000 --- a/common/src/geometry/creator/shaders/template/gc.cone.vertex.input.hlsl +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _THIS_EXAMPLE_GC_CONE_VERTEX_INPUT_HLSL_ -#define _THIS_EXAMPLE_GC_CONE_VERTEX_INPUT_HLSL_ - -struct VSInput -{ - [[vk::location(0)]] float3 position : POSITION; - [[vk::location(1)]] float4 color : COLOR; - [[vk::location(2)]] float3 normal : NORMAL; -}; - -#endif // _THIS_EXAMPLE_GC_CONE_VERTEX_INPUT_HLSL_ - -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ diff --git a/common/src/geometry/creator/shaders/template/gc.ico.vertex.input.hlsl b/common/src/geometry/creator/shaders/template/gc.ico.vertex.input.hlsl deleted file mode 100644 index 6b85486d9..000000000 --- a/common/src/geometry/creator/shaders/template/gc.ico.vertex.input.hlsl +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _THIS_EXAMPLE_GC_ICO_VERTEX_INPUT_HLSL_ -#define _THIS_EXAMPLE_GC_ICO_VERTEX_INPUT_HLSL_ - -struct VSInput -{ - [[vk::location(0)]] float3 position : POSITION; - [[vk::location(1)]] float3 normal : NORMAL; - [[vk::location(2)]] float2 uv : TEXCOORD; -}; - -#endif // _THIS_EXAMPLE_GC_ICO_VERTEX_INPUT_HLSL_ - -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ diff --git a/common/src/geometry/creator/shaders/template/gc.vertex.hlsl b/common/src/geometry/creator/shaders/template/gc.vertex.hlsl index 5a8f26722..783421613 100644 --- a/common/src/geometry/creator/shaders/template/gc.vertex.hlsl +++ b/common/src/geometry/creator/shaders/template/gc.vertex.hlsl @@ -16,7 +16,3 @@ PSInput VSMain(VSInput input) return output; } - -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ diff --git a/common/src/geometry/creator/shaders/template/grid.common.hlsl b/common/src/geometry/creator/shaders/template/grid.common.hlsl deleted file mode 100644 index bc6516600..000000000 --- a/common/src/geometry/creator/shaders/template/grid.common.hlsl +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _THIS_EXAMPLE_GRID_COMMON_HLSL_ -#define _THIS_EXAMPLE_GRID_COMMON_HLSL_ - -#ifdef __HLSL_VERSION - struct VSInput - { - [[vk::location(0)]] float3 position : POSITION; - [[vk::location(1)]] float4 color : COLOR; - [[vk::location(2)]] float2 uv : TEXCOORD; - [[vk::location(3)]] float3 normal : NORMAL; - }; - - struct PSInput - { - float4 position : SV_Position; - float2 uv : TEXCOORD0; - }; - - float gridTextureGradBox(float2 p, float2 ddx, float2 ddy) - { - float N = 30.0; // grid ratio - float2 w = max(abs(ddx), abs(ddy)) + 0.01; // filter kernel - - // analytic (box) filtering - float2 a = p + 0.5 * w; - float2 b = p - 0.5 * w; - float2 i = (floor(a) + min(frac(a) * N, 1.0) - floor(b) - min(frac(b) * N, 1.0)) / (N * w); - - // pattern - return (1.0 - i.x) * (1.0 - i.y); - } -#endif // __HLSL_VERSION - -#include "SBasicViewParameters.hlsl" - -#endif // _THIS_EXAMPLE_GRID_COMMON_HLSL_ - -/* - do not remove this text, WAVE is so bad that you can get errors if no proper ending xD -*/ \ No newline at end of file From bdcc4c4a51cbf9a694fd649f38112088e11ae652 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 15 Nov 2024 13:35:21 +0100 Subject: [PATCH 35/84] update the UI example to use TRI buffer & smooth resize surface -> I need better control over my window because I want to spam multiple view-ports in GUIs --- 08_HelloSwapchain/main.cpp | 1 + 61_UI/main.cpp | 1334 ++++++++++++++++++++++-------------- 2 files changed, 838 insertions(+), 497 deletions(-) diff --git a/08_HelloSwapchain/main.cpp b/08_HelloSwapchain/main.cpp index 9137fe77a..6c5e3ea82 100644 --- a/08_HelloSwapchain/main.cpp +++ b/08_HelloSwapchain/main.cpp @@ -160,6 +160,7 @@ class HelloSwapchainApp final : public examples::SimpleWindowedApplication auto window = m_winMgr->createWindow(std::move(params)); // uncomment for some nasty testing of swapchain creation! //m_winMgr->minimize(window.get()); + const_cast&>(m_surface) = CSmoothResizeSurface::create(CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api),move_and_static_cast(window))); } return {{m_surface->getSurface()/*,EQF_NONE*/}}; diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 50becd42f..0725bc98f 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -12,8 +12,168 @@ using camera_t = CFPSCamera; using controller_t = CCameraController; using projection_t = IProjection>; // TODO: temporary -> projections will own/reference cameras -// Maximum frames which can be simultaneously submitted, used to cycle through our per-frame resources like command buffers -constexpr static inline uint32_t MaxFramesInFlight = 3u; +constexpr IGPUImage::SSubresourceRange TripleBufferUsedSubresourceRange = +{ + .aspectMask = IGPUImage::EAF_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 +}; + +class CUIEventCallback : public nbl::video::ISmoothResizeSurface::ICallback // I cannot use common CEventCallback because I MUST inherit this callback in order to use smooth resize surface with window callback (for my input events) +{ +public: + CUIEventCallback(nbl::core::smart_refctd_ptr&& m_inputSystem, nbl::system::logger_opt_smart_ptr&& logger) : m_inputSystem(std::move(m_inputSystem)), m_logger(std::move(logger)) {} + CUIEventCallback() {} + + void setLogger(nbl::system::logger_opt_smart_ptr& logger) + { + m_logger = logger; + } + void setInputSystem(nbl::core::smart_refctd_ptr&& m_inputSystem) + { + m_inputSystem = std::move(m_inputSystem); + } +private: + + void onMouseConnected_impl(nbl::core::smart_refctd_ptr&& mch) override + { + m_logger.log("A mouse %p has been connected", nbl::system::ILogger::ELL_INFO, mch.get()); + m_inputSystem.get()->add(m_inputSystem.get()->m_mouse, std::move(mch)); + } + void onMouseDisconnected_impl(nbl::ui::IMouseEventChannel* mch) override + { + m_logger.log("A mouse %p has been disconnected", nbl::system::ILogger::ELL_INFO, mch); + m_inputSystem.get()->remove(m_inputSystem.get()->m_mouse, mch); + } + void onKeyboardConnected_impl(nbl::core::smart_refctd_ptr&& kbch) override + { + m_logger.log("A keyboard %p has been connected", nbl::system::ILogger::ELL_INFO, kbch.get()); + m_inputSystem.get()->add(m_inputSystem.get()->m_keyboard, std::move(kbch)); + } + void onKeyboardDisconnected_impl(nbl::ui::IKeyboardEventChannel* kbch) override + { + m_logger.log("A keyboard %p has been disconnected", nbl::system::ILogger::ELL_INFO, kbch); + m_inputSystem.get()->remove(m_inputSystem.get()->m_keyboard, kbch); + } + +private: + nbl::core::smart_refctd_ptr m_inputSystem = nullptr; + nbl::system::logger_opt_smart_ptr m_logger = nullptr; +}; + +class CSwapchainResources final : public ISmoothResizeSurface::ISwapchainResources +{ +public: + // Because we blit to the swapchain image asynchronously, we need a queue which can not only present but also perform graphics commands. + // If we for example used a compute shader to tonemap and MSAA resolve, we'd request the COMPUTE_BIT here. + constexpr static inline IQueue::FAMILY_FLAGS RequiredQueueFlags = IQueue::FAMILY_FLAGS::GRAPHICS_BIT; + +protected: + // We can return `BLIT_BIT` here, because the Source Image will be already in the correct layout to be used for the present + inline core::bitflag getTripleBufferPresentStages() const override { return asset::PIPELINE_STAGE_FLAGS::BLIT_BIT; } + + inline bool tripleBufferPresent(IGPUCommandBuffer* cmdbuf, const ISmoothResizeSurface::SPresentSource& source, const uint8_t imageIndex, const uint32_t qFamToAcquireSrcFrom) override + { + bool success = true; + auto acquiredImage = getImage(imageIndex); + + // Ownership of the Source Blit Image, not the Swapchain Image + const bool needToAcquireSrcOwnership = qFamToAcquireSrcFrom != IQueue::FamilyIgnored; + // Should never get asked to transfer ownership if the source is concurrent sharing + assert(!source.image->getCachedCreationParams().isConcurrentSharing() || !needToAcquireSrcOwnership); + + const auto blitDstLayout = IGPUImage::LAYOUT::TRANSFER_DST_OPTIMAL; + IGPUCommandBuffer::SPipelineBarrierDependencyInfo depInfo = {}; + + // barrier before to transition the swapchain image layout + using image_barrier_t = decltype(depInfo.imgBarriers)::element_type; + const image_barrier_t preBarriers[2] = { + { + .barrier = { + .dep = { + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::NONE, // acquire isn't a stage + .srcAccessMask = asset::ACCESS_FLAGS::NONE, // performs no accesses + .dstStageMask = asset::PIPELINE_STAGE_FLAGS::BLIT_BIT, + .dstAccessMask = asset::ACCESS_FLAGS::TRANSFER_WRITE_BIT + } + }, + .image = acquiredImage, + .subresourceRange = { + .aspectMask = IGPUImage::EAF_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 + }, + .oldLayout = IGPUImage::LAYOUT::UNDEFINED, // I do not care about previous contents of the swapchain + .newLayout = blitDstLayout + }, + { + .barrier = { + .dep = { + // when acquiring ownership the source access masks don't matter + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::NONE, + // Acquire must Happen-Before Semaphore wait, but neither has a true stage so NONE here + // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 + // If no ownership acquire needed then this dep info won't be used at all + .srcAccessMask = asset::ACCESS_FLAGS::NONE, + .dstStageMask = asset::PIPELINE_STAGE_FLAGS::BLIT_BIT, + .dstAccessMask = asset::ACCESS_FLAGS::TRANSFER_READ_BIT + }, + .ownershipOp = IGPUCommandBuffer::SOwnershipTransferBarrier::OWNERSHIP_OP::ACQUIRE, + .otherQueueFamilyIndex = qFamToAcquireSrcFrom + }, + .image = source.image, + .subresourceRange = TripleBufferUsedSubresourceRange + // no layout transition, already in the correct layout for the blit + } + }; + // We only barrier the source image if we need to acquire ownership, otherwise thanks to Timeline Semaphores all sync is good + depInfo.imgBarriers = { preBarriers,needToAcquireSrcOwnership ? 2ull : 1ull }; + success &= cmdbuf->pipelineBarrier(asset::EDF_NONE, depInfo); + + // TODO: Implement scaling modes other than a plain STRETCH, and allow for using subrectangles of the initial contents + { + const auto srcOffset = source.rect.offset; + const auto srcExtent = source.rect.extent; + const auto dstExtent = acquiredImage->getCreationParameters().extent; + const IGPUCommandBuffer::SImageBlit regions[1] = { { + .srcMinCoord = {static_cast(srcOffset.x),static_cast(srcOffset.y),0}, + .srcMaxCoord = {srcExtent.width,srcExtent.height,1}, + .dstMinCoord = {0,0,0}, + .dstMaxCoord = {dstExtent.width,dstExtent.height,1}, + .layerCount = acquiredImage->getCreationParameters().arrayLayers, + .srcBaseLayer = 0, + .dstBaseLayer = 0, + .srcMipLevel = 0 + } }; + success &= cmdbuf->blitImage(source.image, IGPUImage::LAYOUT::TRANSFER_SRC_OPTIMAL, acquiredImage, blitDstLayout, regions, IGPUSampler::ETF_LINEAR); + } + + // Barrier after, note that I don't care about preserving the contents of the Triple Buffer when the Render queue starts writing to it again. + // Therefore no ownership release, and no layout transition. + const image_barrier_t postBarrier[1] = { + { + .barrier = { + // When transitioning the image to VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR or VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, there is no need to delay subsequent processing, + // or perform any visibility operations (as vkQueuePresentKHR performs automatic visibility operations). + // To achieve this, the dstAccessMask member of the VkImageMemoryBarrier should be set to 0, and the dstStageMask parameter should be set to VK_PIPELINE_STAGE_2_NONE + .dep = preBarriers[0].barrier.dep.nextBarrier(asset::PIPELINE_STAGE_FLAGS::NONE,asset::ACCESS_FLAGS::NONE) + }, + .image = preBarriers[0].image, + .subresourceRange = preBarriers[0].subresourceRange, + .oldLayout = blitDstLayout, + .newLayout = IGPUImage::LAYOUT::PRESENT_SRC + } + }; + depInfo.imgBarriers = postBarrier; + success &= cmdbuf->pipelineBarrier(asset::EDF_NONE, depInfo); + + return success; + } +}; /* Renders scene texture to an offline @@ -27,132 +187,225 @@ constexpr static inline uint32_t MaxFramesInFlight = 3u; class UISampleApp final : public examples::SimpleWindowedApplication { - using device_base_t = examples::SimpleWindowedApplication; + using base_t = examples::SimpleWindowedApplication; using clock_t = std::chrono::steady_clock; - _NBL_STATIC_INLINE_CONSTEXPR uint32_t WIN_W = 1280, WIN_H = 720; + //_NBL_STATIC_INLINE_CONSTEXPR uint32_t WIN_W = 1280, WIN_H = 720; constexpr static inline clock_t::duration DisplayImageDuration = std::chrono::milliseconds(900); public: + using base_t::base_t; + inline UISampleApp(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) : IApplicationFramework(_localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD) {} - inline core::vector getSurfaces() const override + // Will get called mid-initialization, via `filterDevices` between when the API Connection is created and Physical Device is chosen + core::vector getSurfaces() const override { + // So let's create our Window and Surface then! if (!m_surface) { { - auto windowCallback = core::make_smart_refctd_ptr(smart_refctd_ptr(m_inputSystem), smart_refctd_ptr(m_logger)); + const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); + auto windowCallback = core::make_smart_refctd_ptr(smart_refctd_ptr(m_inputSystem), smart_refctd_ptr(m_logger)); + IWindow::SCreationParams params = {}; - params.callback = core::make_smart_refctd_ptr(); - params.width = WIN_W; - params.height = WIN_H; + params.callback = core::make_smart_refctd_ptr(); + params.width = dpyInfo.resX; + params.height = dpyInfo.resY; params.x = 32; params.y = 32; - params.flags = ui::IWindow::ECF_HIDDEN | IWindow::ECF_BORDERLESS | IWindow::ECF_MAXIMIZED; - params.windowCaption = "UISampleApp"; + params.flags = IWindow::ECF_INPUT_FOCUS | IWindow::ECF_CAN_RESIZE | IWindow::ECF_CAN_MAXIMIZE | IWindow::ECF_CAN_MINIMIZE; + params.windowCaption = "[Nabla Engine] UI App"; params.callback = windowCallback; + const_cast&>(m_window) = m_winMgr->createWindow(std::move(params)); } - auto surface = CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api), smart_refctd_ptr_static_cast(m_window)); - const_cast&>(m_surface) = nbl::video::CSimpleResizeSurface::create(std::move(surface)); + const_cast&>(m_surface) = CSmoothResizeSurface::create(std::move(surface)); } if (m_surface) + { + m_window->getManager()->maximize(m_window.get()); return { {m_surface->getSurface()/*,EQF_NONE*/} }; - + } + return {}; } inline bool onAppInitialized(smart_refctd_ptr&& system) override { + // Create imput system m_inputSystem = make_smart_refctd_ptr(logger_opt_smart_ptr(smart_refctd_ptr(m_logger))); - if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) + // Remember to call the base class initialization! + if (!base_t::onAppInitialized(std::move(system))) return false; + // Create asset manager m_assetManager = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); - auto* geometry = m_assetManager->getGeometryCreator(); + // First create the resources that don't depend on a swapchain m_semaphore = m_device->createSemaphore(m_realFrameIx); if (!m_semaphore) return logFail("Failed to Create a Semaphore!"); - ISwapchain::SCreationParams swapchainParams = { .surface = m_surface->getSurface() }; - if (!swapchainParams.deduceFormat(m_physicalDevice)) - return logFail("Could not choose a Surface Format for the Swapchain!"); + // The nice thing about having a triple buffer is that you don't need to do acrobatics to account for the formats available to the surface. + // You can transcode to the swapchain's format while copying, and I actually recommend to do surface rotation, tonemapping and OETF application there. + const auto format = asset::EF_R8G8B8A8_SRGB; + // Could be more clever and use the copy Triple Buffer to Swapchain as an opportunity to do a MSAA resolve or something + const auto samples = IGPUImage::ESCF_1_BIT; - const static IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = + // Create the renderpass { - { - .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, - .dstSubpass = 0, - .memoryBarrier = + const IGPURenderpass::SCreationParams::SColorAttachmentDescription colorAttachments[] = { + {{ + { + .format = format, + .samples = samples, + .mayAlias = false + }, + /*.loadOp = */IGPURenderpass::LOAD_OP::CLEAR, + /*.storeOp = */IGPURenderpass::STORE_OP::STORE, + /*.initialLayout = */IGPUImage::LAYOUT::UNDEFINED, // because we clear we don't care about contents when we grab the triple buffer img again + /*.finalLayout = */IGPUImage::LAYOUT::TRANSFER_SRC_OPTIMAL // put it already in the correct layout for the blit operation + }}, + IGPURenderpass::SCreationParams::ColorAttachmentsEnd + }; + IGPURenderpass::SCreationParams::SSubpassDescription subpasses[] = { + {}, + IGPURenderpass::SCreationParams::SubpassesEnd + }; + subpasses[0].colorAttachments[0] = { .render = {.attachmentIndex = 0,.layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL} }; + // We actually need external dependencies to ensure ordering of the Implicit Layout Transitions relative to the semaphore signals + IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = { + // wipe-transition to ATTACHMENT_OPTIMAL { - .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COPY_BIT, - .srcAccessMask = asset::ACCESS_FLAGS::TRANSFER_WRITE_BIT, + .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .dstSubpass = 0, + .memoryBarrier = { + // we can have NONE as Sources because the semaphore wait is ALL_COMMANDS + // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 .dstStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, .dstAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT } + // leave view offsets and flags default }, - { - .srcSubpass = 0, - .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, - .memoryBarrier = + // ATTACHMENT_OPTIMAL to PRESENT_SRC { - .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, - .srcAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT - } + .srcSubpass = 0, + .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .memoryBarrier = { + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + // we can have NONE as the Destinations because the semaphore signal is ALL_COMMANDS + // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 + } + // leave view offsets and flags default }, IGPURenderpass::SCreationParams::DependenciesEnd - }; + }; - auto scResources = std::make_unique(m_device.get(), swapchainParams.surfaceFormat.format, dependencies); - auto* renderpass = scResources->getRenderpass(); - - if (!renderpass) - return logFail("Failed to create Renderpass!"); + IGPURenderpass::SCreationParams params = {}; + params.colorAttachments = colorAttachments; + params.subpasses = subpasses; + params.dependencies = dependencies; + m_renderpass = m_device->createRenderpass(params); + if (!m_renderpass) + return logFail("Failed to Create a Renderpass!"); + } - auto gQueue = getGraphicsQueue(); - if (!m_surface || !m_surface->init(gQueue, std::move(scResources), swapchainParams.sharedParams)) - return logFail("Could not create Window & Surface or initialize the Surface!"); + // We just live life in easy mode and have the Swapchain Creation Parameters get deduced from the surface. + // We don't need any control over the format of the swapchain because we'll be only using Renderpasses this time! + // TODO: improve the queue allocation/choice and allocate a dedicated presentation queue to improve responsiveness and race to present. + if (!m_surface || !m_surface->init(m_surface->pickQueue(m_device.get()), std::make_unique(), {})) + return logFail("Failed to Create a Swapchain!"); - m_cmdPool = m_device->createCommandPool(gQueue->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); - - for (auto i = 0u; i < MaxFramesInFlight; i++) + // Normally you'd want to recreate these images whenever the swapchain is resized in some increment, like 64 pixels or something. + // But I'm super lazy here and will just create "worst case sized images" and waste all the VRAM I can get. + const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); + for (auto i = 0; i < MaxFramesInFlight; i++) { - if (!m_cmdPool) - return logFail("Couldn't create Command Pool!"); - if (!m_cmdPool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { m_cmdBufs.data() + i, 1 })) - return logFail("Couldn't create Command Buffer!"); + auto& image = m_tripleBuffers[i]; + { + IGPUImage::SCreationParams params = {}; + params = asset::IImage::SCreationParams{ + .type = IGPUImage::ET_2D, + .samples = samples, + .format = format, + .extent = {dpyInfo.resX,dpyInfo.resY,1}, + .mipLevels = 1, + .arrayLayers = 1, + .flags = IGPUImage::ECF_NONE, + // in this example I'll be using a renderpass to clear the image, and then a blit to copy it to the swapchain + .usage = IGPUImage::EUF_RENDER_ATTACHMENT_BIT | IGPUImage::EUF_TRANSFER_SRC_BIT + }; + image = m_device->createImage(std::move(params)); + if (!image) + return logFail("Failed to Create Triple Buffer Image!"); + + // use dedicated allocations, we have plenty of allocations left, even on Win32 + if (!m_device->allocate(image->getMemoryReqs(), image.get()).isValid()) + return logFail("Failed to allocate Device Memory for Image %d", i); + } + image->setObjectDebugName(("Triple Buffer Image " + std::to_string(i)).c_str()); + + // create framebuffers for the images + { + auto imageView = m_device->createImageView({ + .flags = IGPUImageView::ECF_NONE, + // give it a Transfer SRC usage flag so we can transition to the Tranfer SRC layout with End Renderpass + .subUsages = IGPUImage::EUF_RENDER_ATTACHMENT_BIT | IGPUImage::EUF_TRANSFER_SRC_BIT, + .image = core::smart_refctd_ptr(image), + .viewType = IGPUImageView::ET_2D, + .format = format + }); + const auto& imageParams = image->getCreationParameters(); + IGPUFramebuffer::SCreationParams params = { { + .renderpass = core::smart_refctd_ptr(m_renderpass), + .depthStencilAttachments = nullptr, + .colorAttachments = &imageView.get(), + .width = imageParams.extent.width, + .height = imageParams.extent.height, + .layers = imageParams.arrayLayers + } }; + m_framebuffers[i] = m_device->createFramebuffer(std::move(params)); + if (!m_framebuffers[i]) + return logFail("Failed to Create a Framebuffer for Image %d", i); + } } - - //pass.scene = CScene::create(smart_refctd_ptr(m_utils), smart_refctd_ptr(m_logger), gQueue, geometry); - pass.scene = CScene::create(smart_refctd_ptr(m_utils), smart_refctd_ptr(m_logger), gQueue, geometry); - nbl::ext::imgui::UI::SCreationParameters params; + // This time we'll create all CommandBuffers from one CommandPool, to keep life simple. However the Pool must support individually resettable CommandBuffers + // because they cannot be pre-recorded because the fraembuffers/swapchain images they use will change when a swapchain recreates. + auto pool = m_device->createCommandPool(getGraphicsQueue()->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + if (!pool || !pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { m_cmdBufs.data(),MaxFramesInFlight }, core::smart_refctd_ptr(m_logger))) + return logFail("Failed to Create CommandBuffers!"); - params.resources.texturesInfo = { .setIx = 0u, .bindingIx = 0u }; - params.resources.samplersInfo = { .setIx = 0u, .bindingIx = 1u }; - params.assetManager = m_assetManager; - params.pipelineCache = nullptr; - params.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, TexturesAmount); - params.renderpass = smart_refctd_ptr(renderpass); - params.streamingBuffer = nullptr; - params.subpassIx = 0u; - params.transfer = getTransferUpQueue(); - params.utilities = m_utils; + // UI { - pass.ui.manager = nbl::ext::imgui::UI::create(std::move(params)); + { + nbl::ext::imgui::UI::SCreationParameters params; + params.resources.texturesInfo = { .setIx = 0u, .bindingIx = 0u }; + params.resources.samplersInfo = { .setIx = 0u, .bindingIx = 1u }; + params.assetManager = m_assetManager; + params.pipelineCache = nullptr; + params.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, TexturesAmount); + params.renderpass = smart_refctd_ptr(m_renderpass); + params.streamingBuffer = nullptr; + params.subpassIx = 0u; + params.transfer = getTransferUpQueue(); + params.utilities = m_utils; + + pass.ui.manager = nbl::ext::imgui::UI::create(std::move(params)); + } if (!pass.ui.manager) return false; // note that we use default layout provided by our extension, but you are free to create your own by filling nbl::ext::imgui::UI::S_CREATION_PARAMETERS::resources const auto* descriptorSetLayout = pass.ui.manager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); - const auto& params = pass.ui.manager->getCreationParameters(); IDescriptorPool::SCreateInfo descriptorPoolInfo = {}; descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = (uint32_t)nbl::ext::imgui::UI::DefaultSamplerIx::COUNT; @@ -165,334 +418,16 @@ class UISampleApp final : public examples::SimpleWindowedApplication m_descriptorSetPool->createDescriptorSets(1u, &descriptorSetLayout, &pass.ui.descriptorSet); assert(pass.ui.descriptorSet); - } - pass.ui.manager->registerListener([this]() -> void - { - ImGuiIO& io = ImGui::GetIO(); - { - if (isPerspective) - { - if (isLH) - projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); - else - projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); - } - else - { - float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; - - if (isLH) - projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); - else - projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); - } - } - - ImGuizmo::SetOrthographic(false); - ImGuizmo::BeginFrame(); - - ImGui::SetNextWindowPos(ImVec2(1024, 100), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(256, 256), ImGuiCond_Appearing); - - // create a window and insert the inspector - ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); - ImGui::Begin("Editor"); - - if (ImGui::RadioButton("Full view", !transformParams.useWindow)) - transformParams.useWindow = false; - - ImGui::SameLine(); - if (ImGui::RadioButton("Window", transformParams.useWindow)) - transformParams.useWindow = true; - - ImGui::Text("Camera"); - bool viewDirty = false; - - if (ImGui::RadioButton("LH", isLH)) - isLH = true; - - ImGui::SameLine(); - - if (ImGui::RadioButton("RH", !isLH)) - isLH = false; - - if (ImGui::RadioButton("Perspective", isPerspective)) - isPerspective = true; - - ImGui::SameLine(); - - if (ImGui::RadioButton("Orthographic", !isPerspective)) - isPerspective = false; - - ImGui::Checkbox("Enable \"view manipulate\"", &transformParams.enableViewManipulate); - ImGui::Checkbox("Enable camera movement", &move); - ImGui::SliderFloat("Move speed", &moveSpeed, 0.1f, 10.f); - ImGui::SliderFloat("Rotate speed", &rotateSpeed, 0.1f, 10.f); - - // ImGui::Checkbox("Flip Gizmo's Y axis", &flipGizmoY); // let's not expose it to be changed in UI but keep the logic in case - - if (isPerspective) - ImGui::SliderFloat("Fov", &fov, 20.f, 150.f); - else - ImGui::SliderFloat("Ortho width", &viewWidth, 1, 20); - - ImGui::SliderFloat("zNear", &zNear, 0.1f, 100.f); - ImGui::SliderFloat("zFar", &zFar, 110.f, 10000.f); - - viewDirty |= ImGui::SliderFloat("Distance", &transformParams.camDistance, 1.f, 69.f); - - if (viewDirty || firstFrame) - { - float32_t3 cameraPosition(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); - float32_t3 cameraTarget(0.f, 0.f, 0.f); - - // TODO: lets generate events and make it - // happen purely on gimbal manipulation! - - //camera->getGimbal()->setPosition(cameraPosition); - //camera->getGimbal()->setTarget(cameraTarget); - - firstFrame = false; - } - - ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); - if (ImGuizmo::IsUsing()) - { - ImGui::Text("Using gizmo"); - } - else - { - ImGui::Text(ImGuizmo::IsOver() ? "Over gizmo" : ""); - ImGui::SameLine(); - ImGui::Text(ImGuizmo::IsOver(ImGuizmo::TRANSLATE) ? "Over translate gizmo" : ""); - ImGui::SameLine(); - ImGui::Text(ImGuizmo::IsOver(ImGuizmo::ROTATE) ? "Over rotate gizmo" : ""); - ImGui::SameLine(); - ImGui::Text(ImGuizmo::IsOver(ImGuizmo::SCALE) ? "Over scale gizmo" : ""); - } - ImGui::Separator(); - - /* - * ImGuizmo expects view & perspective matrix to be column major both with 4x4 layout - * and Nabla uses row major matricies - 3x4 matrix for view & 4x4 for projection - - - VIEW: - - ImGuizmo - - | X[0] Y[0] Z[0] 0.0f | - | X[1] Y[1] Z[1] 0.0f | - | X[2] Y[2] Z[2] 0.0f | - | -Dot(X, eye) -Dot(Y, eye) -Dot(Z, eye) 1.0f | - - Nabla - - | X[0] X[1] X[2] -Dot(X, eye) | - | Y[0] Y[1] Y[2] -Dot(Y, eye) | - | Z[0] Z[1] Z[2] -Dot(Z, eye) | - - = transpose(nbl::core::matrix4SIMD()) - - - PERSPECTIVE [PROJECTION CASE]: - - ImGuizmo - - | (temp / temp2) (0.0) (0.0) (0.0) | - | (0.0) (temp / temp3) (0.0) (0.0) | - | ((right + left) / temp2) ((top + bottom) / temp3) ((-zfar - znear) / temp4) (-1.0f) | - | (0.0) (0.0) ((-temp * zfar) / temp4) (0.0) | - - Nabla - - | w (0.0) (0.0) (0.0) | - | (0.0) -h (0.0) (0.0) | - | (0.0) (0.0) (-zFar/(zFar-zNear)) (-zNear*zFar/(zFar-zNear)) | - | (0.0) (0.0) (-1.0) (0.0) | - - = transpose() - - * - * the ViewManipulate final call (inside EditTransform) returns world space column major matrix for an object, - * note it also modifies input view matrix but projection matrix is immutable - */ - - static struct - { - float32_t4x4 view, projection, model; - } imguizmoM16InOut; - - const auto& projectionMatrix = projection->getMatrix(); - const auto& view = camera->getGimbal().getView(); - - ImGuizmo::SetID(0u); - imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(view.matrix)); - imguizmoM16InOut.projection = transpose(projectionMatrix); - imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(pass.scene->object.model)); - { - if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates - imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ - - transformParams.editTransformDecomposition = true; - EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); - } - - // to Nabla + update camera & model matrices - - // TODO: make it more nicely - const_cast(view.matrix) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) - { - static float32_t3x4 modelView, normal; - static float32_t4x4 modelViewProjection; - - auto& hook = pass.scene->object; - hook.model = float32_t3x4(transpose(imguizmoM16InOut.model)); - { - const auto& references = pass.scene->getResources().objects; - const auto type = static_cast(gcIndex); - - const auto& [gpu, meta] = references[type]; - hook.meta.type = type; - hook.meta.name = meta.name; - } - - auto& ubo = hook.viewParameters; - - modelView = concatenateBFollowedByA(view.matrix, hook.model); - - // TODO - //modelView.getSub3x3InverseTranspose(normal); - - auto concatMatrix = mul(projectionMatrix, getMatrix3x4As4x4(view.matrix)); - modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(hook.model)); - - memcpy(ubo.MVP, &modelViewProjection[0][0], sizeof(ubo.MVP)); - memcpy(ubo.MV, &modelView[0][0], sizeof(ubo.MV)); - memcpy(ubo.NormalMat, &normal[0][0], sizeof(ubo.NormalMat)); - - // object meta display - { - ImGui::Begin("Object"); - ImGui::Text("type: \"%s\"", hook.meta.name.data()); - ImGui::End(); - } - } - - // view matrices editor - { - ImGui::Begin("Matrices"); - - auto addMatrixTable = [&](const char* topText, const char* tableName, const int rows, const int columns, const float* pointer, const bool withSeparator = true) - { - ImGui::Text(topText); - if (ImGui::BeginTable(tableName, columns)) - { - for (int y = 0; y < rows; ++y) - { - ImGui::TableNextRow(); - for (int x = 0; x < columns; ++x) - { - ImGui::TableSetColumnIndex(x); - ImGui::Text("%.3f", *(pointer + (y * columns) + x)); - } - } - ImGui::EndTable(); - } - - if (withSeparator) - ImGui::Separator(); - }; - - const auto& orientation = camera->getGimbal().getOrthonornalMatrix(); - - addMatrixTable("Object's Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); - addMatrixTable("Camera's Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); - addMatrixTable("Camera's Orientation Quat", "OrientationQuatTable", 1, 4, &camera->getGimbal().getOrientation()[0]); - addMatrixTable("Camera's View Matrix", "ViewMatrixTable", 3, 4, &view.matrix[0][0]); - addMatrixTable("Bound Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); - - ImGui::End(); - } - - // Nabla Imgui backend MDI buffer info - // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, - // so unless you do that, don't use this basic info to optimize the size of your IMGUI buffer. - { - auto* streaminingBuffer = pass.ui.manager->getStreamingBuffer(); - - const size_t total = streaminingBuffer->get_total_size(); // total memory range size for which allocation can be requested - const size_t freeSize = streaminingBuffer->getAddressAllocator().get_free_size(); // max total free bloock memory size we can still allocate from total memory available - const size_t consumedMemory = total - freeSize; // memory currently consumed by streaming buffer - - float freePercentage = 100.0f * (float)(freeSize) / (float)total; - float allocatedPercentage = (float)(consumedMemory) / (float)total; - - ImVec2 barSize = ImVec2(400, 30); - float windowPadding = 10.0f; - float verticalPadding = ImGui::GetStyle().FramePadding.y; - - ImGui::SetNextWindowSize(ImVec2(barSize.x + 2 * windowPadding, 110 + verticalPadding), ImGuiCond_Always); - ImGui::Begin("Nabla Imgui MDI Buffer Info", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar); - - ImGui::Text("Total Allocated Size: %zu bytes", total); - ImGui::Text("In use: %zu bytes", consumedMemory); - ImGui::Text("Buffer Usage:"); - - ImGui::SetCursorPosX(windowPadding); - - if (freePercentage > 70.0f) - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.0f, 1.0f, 0.0f, 0.4f)); // Green - else if (freePercentage > 30.0f) - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(1.0f, 1.0f, 0.0f, 0.4f)); // Yellow - else - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(1.0f, 0.0f, 0.0f, 0.4f)); // Red - - ImGui::ProgressBar(allocatedPercentage, barSize, ""); - - ImGui::PopStyleColor(); - - ImDrawList* drawList = ImGui::GetWindowDrawList(); - - ImVec2 progressBarPos = ImGui::GetItemRectMin(); - ImVec2 progressBarSize = ImGui::GetItemRectSize(); - - const char* text = "%.2f%% free"; - char textBuffer[64]; - snprintf(textBuffer, sizeof(textBuffer), text, freePercentage); - - ImVec2 textSize = ImGui::CalcTextSize(textBuffer); - ImVec2 textPos = ImVec2 - ( - progressBarPos.x + (progressBarSize.x - textSize.x) * 0.5f, - progressBarPos.y + (progressBarSize.y - textSize.y) * 0.5f - ); - - ImVec4 bgColor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); - drawList->AddRectFilled - ( - ImVec2(textPos.x - 5, textPos.y - 2), - ImVec2(textPos.x + textSize.x + 5, textPos.y + textSize.y + 2), - ImGui::GetColorU32(bgColor) - ); - - ImGui::SetCursorScreenPos(textPos); - ImGui::Text("%s", textBuffer); - - ImGui::Dummy(ImVec2(0.0f, verticalPadding)); - - ImGui::End(); - } - - displayKeyMappingsAndVirtualStates(controller.get()); + pass.ui.manager->registerListener([this]() -> void { imguiListen(); }); + } - ImGui::End(); - } - ); + // Geometry Creator Scene + { + //pass.scene = CScene::create(smart_refctd_ptr(m_utils), smart_refctd_ptr(m_logger), getGraphicsQueue(), m_assetManager->getGeometryCreator()); + pass.scene = CScene::create(smart_refctd_ptr(m_utils), smart_refctd_ptr(m_logger), getGraphicsQueue(), m_assetManager->getGeometryCreator()); + } - m_winMgr->setWindowSize(m_window.get(), WIN_W, WIN_H); - m_surface->recreateSwapchain(); - m_winMgr->show(m_window.get()); oracle.reportBeginFrameRecord(); /* @@ -521,6 +456,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication keys = controller->getCamera()->getImguizmoMappingPreset(); }); + if (base_t::argv.size() >= 3 && argv[1] == "-timeout_seconds") + timeout = std::chrono::seconds(std::atoi(argv[2].c_str())); + start = clock_t::now(); return true; } @@ -558,133 +496,199 @@ class UISampleApp final : public examples::SimpleWindowedApplication // B) Acquire: Can't have more acquires in flight than a certain threshold returned by swapchain or your surface helper class. [MaxAcquiresInFlight] if (m_realFrameIx >= framesInFlight) { - const ISemaphore::SWaitInfo cbDonePending[] = - { + const ISemaphore::SWaitInfo cmdbufDonePending[] = { { .semaphore = m_semaphore.get(), .value = m_realFrameIx + 1 - framesInFlight } }; - if (m_device->blockForSemaphores(cbDonePending) != ISemaphore::WAIT_RESULT::SUCCESS) + if (m_device->blockForSemaphores(cmdbufDonePending) != ISemaphore::WAIT_RESULT::SUCCESS) return; } + // Predict size of next render, and bail if nothing to do + const auto currentSwapchainExtent = m_surface->getCurrentExtent(); + if (currentSwapchainExtent.width * currentSwapchainExtent.height <= 0) + return; + // The extent of the swapchain might change between now and `present` but the blit should adapt nicely + const VkRect2D currentRenderArea = { .offset = {0,0},.extent = currentSwapchainExtent }; + + // You explicitly should not use `getAcquireCount()` see the comment on `m_realFrameIx` const auto resourceIx = m_realFrameIx % MaxFramesInFlight; - // CPU events + // We will be using this command buffer to produce the frame + auto frame = m_tripleBuffers[resourceIx].get(); + auto cmdbuf = m_cmdBufs[resourceIx].get(); + + // update CPU stuff - controllers, events, UI state update(); - // render whole scene to offline frame buffer & submit - pass.scene->begin(); + bool willSubmit = true; { - pass.scene->update(); - pass.scene->record(); - pass.scene->end(); - } - pass.scene->submit(); + // render geometry creator scene to offline frame buffer & submit + // TODO: OK with TRI buffer this thing is retarded now + // (**) <- a note why bellow before submit + pass.scene->begin(); + { + pass.scene->update(); + pass.scene->record(); + pass.scene->end(); + } + pass.scene->submit(); - auto* const cb = m_cmdBufs.data()[resourceIx].get(); - cb->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); - cb->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - cb->beginDebugMarker("UISampleApp IMGUI Frame"); + willSubmit &= cmdbuf->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); + willSubmit &= cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + willSubmit &= cmdbuf->beginDebugMarker("UIApp Frame"); + + const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; + const IGPUCommandBuffer::SRenderpassBeginInfo info = { + .framebuffer = m_framebuffers[resourceIx].get(), + .colorClearValues = &clearValue, + .depthStencilClearValues = nullptr, + .renderArea = currentRenderArea + }; - auto* queue = getGraphicsQueue(); + // UI renderpass + willSubmit &= cmdbuf->beginRenderPass(info, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); + { + asset::SViewport viewport; + { + viewport.minDepth = 1.f; + viewport.maxDepth = 0.f; + viewport.x = 0u; + viewport.y = 0u; + viewport.width = m_window->getWidth(); + viewport.height = m_window->getHeight(); + } - asset::SViewport viewport; - { - viewport.minDepth = 1.f; - viewport.maxDepth = 0.f; - viewport.x = 0u; - viewport.y = 0u; - viewport.width = m_window->getWidth(); - viewport.height = m_window->getHeight(); - } - cb->setViewport(0u, 1u, &viewport); + willSubmit &= cmdbuf->setViewport(0u, 1u, &viewport); - const VkRect2D currentRenderArea = - { - .offset = {0,0}, - .extent = {m_window->getWidth(),m_window->getHeight()} - }; + const VkRect2D currentRenderArea = + { + .offset = {0,0}, + .extent = {m_window->getWidth(),m_window->getHeight()} + }; - IQueue::SSubmitInfo::SCommandBufferInfo commandBuffersInfo[] = {{.cmdbuf = cb }}; + IQueue::SSubmitInfo::SCommandBufferInfo commandBuffersInfo[] = { {.cmdbuf = cmdbuf } }; - // UI render pass - { - auto scRes = static_cast(m_surface->getSwapchainResources()); - const IGPUCommandBuffer::SRenderpassBeginInfo renderpassInfo = - { - .framebuffer = scRes->getFramebuffer(m_currentImageAcquire.imageIndex), - .colorClearValues = &clear.color, - .depthStencilClearValues = nullptr, - .renderArea = currentRenderArea - }; - nbl::video::ISemaphore::SWaitInfo waitInfo = { .semaphore = m_semaphore.get(), .value = m_realFrameIx + 1u }; + const IGPUCommandBuffer::SRenderpassBeginInfo info = + { + .framebuffer = m_framebuffers[resourceIx].get(), + .colorClearValues = &clear.color, + .depthStencilClearValues = nullptr, + .renderArea = currentRenderArea + }; - cb->beginRenderPass(renderpassInfo, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); - const auto uiParams = pass.ui.manager->getCreationParameters(); - auto* pipeline = pass.ui.manager->getPipeline(); - cb->bindGraphicsPipeline(pipeline); - cb->bindDescriptorSets(EPBP_GRAPHICS, pipeline->getLayout(), uiParams.resources.texturesInfo.setIx, 1u, &pass.ui.descriptorSet.get()); // note that we use default UI pipeline layout where uiParams.resources.textures.setIx == uiParams.resources.samplers.setIx - - if (!keepRunning()) - return; - - if (!pass.ui.manager->render(cb,waitInfo)) + nbl::video::ISemaphore::SWaitInfo waitInfo = { .semaphore = m_semaphore.get(), .value = m_realFrameIx + 1u }; + const auto uiParams = pass.ui.manager->getCreationParameters(); + auto* pipeline = pass.ui.manager->getPipeline(); + + cmdbuf->bindGraphicsPipeline(pipeline); + cmdbuf->bindDescriptorSets(EPBP_GRAPHICS, pipeline->getLayout(), uiParams.resources.texturesInfo.setIx, 1u, &pass.ui.descriptorSet.get()); // note that we use default UI pipeline layout where uiParams.resources.textures.setIx == uiParams.resources.samplers.setIx + + if (!keepRunning()) + return; + + willSubmit &= pass.ui.manager->render(cmdbuf, waitInfo); + } + willSubmit &= cmdbuf->endRenderPass(); + + // If the Rendering and Blit/Present Queues don't come from the same family we need to transfer ownership, because we need to preserve contents between them. + auto blitQueueFamily = m_surface->getAssignedQueue()->getFamilyIndex(); + // Also should crash/error if concurrent sharing enabled but would-be-user-queue is not in the share set, but oh well. + const bool needOwnershipRelease = cmdbuf->getQueueFamilyIndex() != blitQueueFamily && !frame->getCachedCreationParams().isConcurrentSharing(); + if (needOwnershipRelease) { - // TODO: need to present acquired image before bailing because its already acquired - return; + const IGPUCommandBuffer::SPipelineBarrierDependencyInfo::image_barrier_t barrier[] = { { + .barrier = { + .dep = { + // Normally I'd put `COLOR_ATTACHMENT` on the masks, but we want this to happen after Layout Transition :( + // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS, + .srcAccessMask = asset::ACCESS_FLAGS::MEMORY_READ_BITS | asset::ACCESS_FLAGS::MEMORY_WRITE_BITS, + // For a Queue Family Ownership Release the destination access masks are irrelevant + // and source stage mask can be NONE as long as the semaphore signals ALL_COMMANDS_BIT + .dstStageMask = asset::PIPELINE_STAGE_FLAGS::NONE, + .dstAccessMask = asset::ACCESS_FLAGS::NONE + }, + .ownershipOp = IGPUCommandBuffer::SOwnershipTransferBarrier::OWNERSHIP_OP::RELEASE, + .otherQueueFamilyIndex = blitQueueFamily + }, + .image = frame, + .subresourceRange = TripleBufferUsedSubresourceRange + // there will be no layout transition, already done by the Renderpass End + } }; + const IGPUCommandBuffer::SPipelineBarrierDependencyInfo depInfo = { .imgBarriers = barrier }; + willSubmit &= cmdbuf->pipelineBarrier(asset::EDF_NONE, depInfo); } - cb->endRenderPass(); } - cb->end(); + willSubmit &= cmdbuf->end(); + + // submit and present under a mutex ASAP + if (willSubmit) { - const IQueue::SSubmitInfo::SSemaphoreInfo rendered[] = - { - { - .semaphore = m_semaphore.get(), - .value = ++m_realFrameIx, - .stageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT - } + // We will signal a semaphore in the rendering queue, and await it with the presentation/blit queue + const IQueue::SSubmitInfo::SSemaphoreInfo rendered = + { + .semaphore = m_semaphore.get(), + .value = m_realFrameIx + 1, + // Normally I'd put `COLOR_ATTACHMENT` on the masks, but we want to signal after Layout Transitions and optional Ownership Release + // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 + .stageMask = asset::PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS }; - + const IQueue::SSubmitInfo::SCommandBufferInfo cmdbufs[1] = + { { + .cmdbuf = cmdbuf + } }; + // We need to wait on previous triple buffer blits/presents from our source image to complete + auto* pBlitWaitValue = m_blitWaitValues.data() + resourceIx; + auto swapchainLock = m_surface->pseudoAcquire(pBlitWaitValue); + const IQueue::SSubmitInfo::SSemaphoreInfo blitted = + { + .semaphore = m_surface->getPresentSemaphore(), + .value = pBlitWaitValue->load(), + // Normally I'd put `BLIT` on the masks, but we want to wait before Implicit Layout Transitions and optional Implicit Ownership Acquire + // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 + .stageMask = asset::PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS + }; + const IQueue::SSubmitInfo submitInfos[1] = { { - const IQueue::SSubmitInfo::SSemaphoreInfo acquired[] = - { - { - .semaphore = m_currentImageAcquire.semaphore, - .value = m_currentImageAcquire.acquireCount, - .stageMask = PIPELINE_STAGE_FLAGS::NONE - } - }; - - const IQueue::SSubmitInfo infos[] = - { - { - .waitSemaphores = acquired, - .commandBuffers = commandBuffersInfo, - .signalSemaphores = rendered - } - }; - - const nbl::video::ISemaphore::SWaitInfo waitInfos[] = - { { - .semaphore = pass.scene->semaphore.progress.get(), - .value = pass.scene->semaphore.finishedValue - } }; - - m_device->blockForSemaphores(waitInfos); - - updateGUIDescriptorSet(); - - if (queue->submit(infos) != IQueue::RESULT::SUCCESS) - m_realFrameIx--; + .waitSemaphores = {&blitted,1}, + .commandBuffers = cmdbufs, + .signalSemaphores = {&rendered,1} } + }; + + // (**) -> wait on offline framebuffer + { + const nbl::video::ISemaphore::SWaitInfo waitInfos[] = + { { + .semaphore = pass.scene->semaphore.progress.get(), + .value = pass.scene->semaphore.finishedValue + } }; + + m_device->blockForSemaphores(waitInfos); + updateGUIDescriptorSet(); } - m_window->setCaption("[Nabla Engine] UI App Test Demo"); - m_surface->present(m_currentImageAcquire.imageIndex, rendered); + if (getGraphicsQueue()->submit(submitInfos) != IQueue::RESULT::SUCCESS) + return; + + m_realFrameIx++; + + // only present if there's successful content to show + const ISmoothResizeSurface::SPresentInfo presentInfo = { + { + .source = {.image = frame,.rect = currentRenderArea}, + .waitSemaphore = rendered.semaphore, + .waitValue = rendered.value, + .pPresentSemaphoreWaitValue = pBlitWaitValue, + }, + // The Graphics Queue will be the the most recent owner just before it releases ownership + cmdbuf->getQueueFamilyIndex() + }; + m_surface->present(std::move(swapchainLock), presentInfo); } } @@ -698,7 +702,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication inline bool onAppTerminated() override { - return device_base_t::onAppTerminated(); + return base_t::onAppTerminated(); } inline void update() @@ -708,8 +712,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto updatePresentationTimestamp = [&]() { - m_currentImageAcquire = m_surface->acquireNextImage(); - oracle.reportEndFrameRecord(); const auto timestamp = oracle.getNextPresentationTimeStamp(); oracle.reportBeginFrameRecord(); @@ -764,21 +766,359 @@ class UISampleApp final : public examples::SimpleWindowedApplication } private: - // Maximum frames which can be simultaneously submitted, used to cycle through our per-frame resources like command buffers - constexpr static inline uint32_t MaxFramesInFlight = 3u; + inline void imguiListen() + { + ImGuiIO& io = ImGui::GetIO(); + { + if (isPerspective) + { + if (isLH) + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + else + projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + } + else + { + float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; + + if (isLH) + projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); + else + projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); + } + } + + ImGuizmo::SetOrthographic(false); + ImGuizmo::BeginFrame(); + + ImGui::SetNextWindowPos(ImVec2(1024, 100), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(256, 256), ImGuiCond_Appearing); + + // create a window and insert the inspector + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); + ImGui::Begin("Editor"); + + if (ImGui::RadioButton("Full view", !transformParams.useWindow)) + transformParams.useWindow = false; + + ImGui::SameLine(); + + if (ImGui::RadioButton("Window", transformParams.useWindow)) + transformParams.useWindow = true; + + ImGui::Text("Camera"); + bool viewDirty = false; + + if (ImGui::RadioButton("LH", isLH)) + isLH = true; + + ImGui::SameLine(); + + if (ImGui::RadioButton("RH", !isLH)) + isLH = false; + + if (ImGui::RadioButton("Perspective", isPerspective)) + isPerspective = true; + + ImGui::SameLine(); + + if (ImGui::RadioButton("Orthographic", !isPerspective)) + isPerspective = false; + + ImGui::Checkbox("Enable \"view manipulate\"", &transformParams.enableViewManipulate); + ImGui::Checkbox("Enable camera movement", &move); + ImGui::SliderFloat("Move speed", &moveSpeed, 0.1f, 10.f); + ImGui::SliderFloat("Rotate speed", &rotateSpeed, 0.1f, 10.f); + + // ImGui::Checkbox("Flip Gizmo's Y axis", &flipGizmoY); // let's not expose it to be changed in UI but keep the logic in case + + if (isPerspective) + ImGui::SliderFloat("Fov", &fov, 20.f, 150.f); + else + ImGui::SliderFloat("Ortho width", &viewWidth, 1, 20); + + ImGui::SliderFloat("zNear", &zNear, 0.1f, 100.f); + ImGui::SliderFloat("zFar", &zFar, 110.f, 10000.f); + + viewDirty |= ImGui::SliderFloat("Distance", &transformParams.camDistance, 1.f, 69.f); + + if (viewDirty || firstFrame) + { + float32_t3 cameraPosition(cosf(camYAngle) * cosf(camXAngle) * transformParams.camDistance, sinf(camXAngle) * transformParams.camDistance, sinf(camYAngle) * cosf(camXAngle) * transformParams.camDistance); + float32_t3 cameraTarget(0.f, 0.f, 0.f); + + // TODO: lets generate events and make it + // happen purely on gimbal manipulation! + + //camera->getGimbal()->setPosition(cameraPosition); + //camera->getGimbal()->setTarget(cameraTarget); + + firstFrame = false; + } + + ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); + if (ImGuizmo::IsUsing()) + { + ImGui::Text("Using gizmo"); + } + else + { + ImGui::Text(ImGuizmo::IsOver() ? "Over gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::TRANSLATE) ? "Over translate gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::ROTATE) ? "Over rotate gizmo" : ""); + ImGui::SameLine(); + ImGui::Text(ImGuizmo::IsOver(ImGuizmo::SCALE) ? "Over scale gizmo" : ""); + } + ImGui::Separator(); + + /* + * ImGuizmo expects view & perspective matrix to be column major both with 4x4 layout + * and Nabla uses row major matricies - 3x4 matrix for view & 4x4 for projection + + - VIEW: + + ImGuizmo + + | X[0] Y[0] Z[0] 0.0f | + | X[1] Y[1] Z[1] 0.0f | + | X[2] Y[2] Z[2] 0.0f | + | -Dot(X, eye) -Dot(Y, eye) -Dot(Z, eye) 1.0f | + + Nabla + + | X[0] X[1] X[2] -Dot(X, eye) | + | Y[0] Y[1] Y[2] -Dot(Y, eye) | + | Z[0] Z[1] Z[2] -Dot(Z, eye) | + + = transpose(nbl::core::matrix4SIMD()) + + - PERSPECTIVE [PROJECTION CASE]: + + ImGuizmo + + | (temp / temp2) (0.0) (0.0) (0.0) | + | (0.0) (temp / temp3) (0.0) (0.0) | + | ((right + left) / temp2) ((top + bottom) / temp3) ((-zfar - znear) / temp4) (-1.0f) | + | (0.0) (0.0) ((-temp * zfar) / temp4) (0.0) | + + Nabla + + | w (0.0) (0.0) (0.0) | + | (0.0) -h (0.0) (0.0) | + | (0.0) (0.0) (-zFar/(zFar-zNear)) (-zNear*zFar/(zFar-zNear)) | + | (0.0) (0.0) (-1.0) (0.0) | + + = transpose() + + * + * the ViewManipulate final call (inside EditTransform) returns world space column major matrix for an object, + * note it also modifies input view matrix but projection matrix is immutable + */ + + static struct + { + float32_t4x4 view, projection, model; + } imguizmoM16InOut; + + const auto& projectionMatrix = projection->getMatrix(); + const auto& view = camera->getGimbal().getView(); + + ImGuizmo::SetID(0u); + imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(view.matrix)); + imguizmoM16InOut.projection = transpose(projectionMatrix); + imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(pass.scene->object.model)); + { + if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates + imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ + + transformParams.editTransformDecomposition = true; + EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); + } + + // to Nabla + update camera & model matrices + + // TODO: make it more nicely + const_cast(view.matrix) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) + { + static float32_t3x4 modelView, normal; + static float32_t4x4 modelViewProjection; + + auto& hook = pass.scene->object; + hook.model = float32_t3x4(transpose(imguizmoM16InOut.model)); + { + const auto& references = pass.scene->getResources().objects; + const auto type = static_cast(gcIndex); + + const auto& [gpu, meta] = references[type]; + hook.meta.type = type; + hook.meta.name = meta.name; + } + + auto& ubo = hook.viewParameters; + + modelView = concatenateBFollowedByA(view.matrix, hook.model); + + // TODO + //modelView.getSub3x3InverseTranspose(normal); + + auto concatMatrix = mul(projectionMatrix, getMatrix3x4As4x4(view.matrix)); + modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(hook.model)); + + memcpy(ubo.MVP, &modelViewProjection[0][0], sizeof(ubo.MVP)); + memcpy(ubo.MV, &modelView[0][0], sizeof(ubo.MV)); + memcpy(ubo.NormalMat, &normal[0][0], sizeof(ubo.NormalMat)); + + // object meta display + { + ImGui::Begin("Object"); + ImGui::Text("type: \"%s\"", hook.meta.name.data()); + ImGui::End(); + } + } + // view matrices editor + { + ImGui::Begin("Matrices"); + + auto addMatrixTable = [&](const char* topText, const char* tableName, const int rows, const int columns, const float* pointer, const bool withSeparator = true) + { + ImGui::Text(topText); + if (ImGui::BeginTable(tableName, columns)) + { + for (int y = 0; y < rows; ++y) + { + ImGui::TableNextRow(); + for (int x = 0; x < columns; ++x) + { + ImGui::TableSetColumnIndex(x); + ImGui::Text("%.3f", *(pointer + (y * columns) + x)); + } + } + ImGui::EndTable(); + } + + if (withSeparator) + ImGui::Separator(); + }; + + const auto& orientation = camera->getGimbal().getOrthonornalMatrix(); + + addMatrixTable("Object's Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); + addMatrixTable("Camera's Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); + addMatrixTable("Camera's Orientation Quat", "OrientationQuatTable", 1, 4, &camera->getGimbal().getOrientation()[0]); + addMatrixTable("Camera's View Matrix", "ViewMatrixTable", 3, 4, &view.matrix[0][0]); + addMatrixTable("Bound Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); + + ImGui::End(); + } + + // Nabla Imgui backend MDI buffer info + // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, + // so unless you do that, don't use this basic info to optimize the size of your IMGUI buffer. + { + auto* streaminingBuffer = pass.ui.manager->getStreamingBuffer(); + + const size_t total = streaminingBuffer->get_total_size(); // total memory range size for which allocation can be requested + const size_t freeSize = streaminingBuffer->getAddressAllocator().get_free_size(); // max total free bloock memory size we can still allocate from total memory available + const size_t consumedMemory = total - freeSize; // memory currently consumed by streaming buffer + + float freePercentage = 100.0f * (float)(freeSize) / (float)total; + float allocatedPercentage = (float)(consumedMemory) / (float)total; + + ImVec2 barSize = ImVec2(400, 30); + float windowPadding = 10.0f; + float verticalPadding = ImGui::GetStyle().FramePadding.y; + + ImGui::SetNextWindowSize(ImVec2(barSize.x + 2 * windowPadding, 110 + verticalPadding), ImGuiCond_Always); + ImGui::Begin("Nabla Imgui MDI Buffer Info", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar); + + ImGui::Text("Total Allocated Size: %zu bytes", total); + ImGui::Text("In use: %zu bytes", consumedMemory); + ImGui::Text("Buffer Usage:"); + + ImGui::SetCursorPosX(windowPadding); + + if (freePercentage > 70.0f) + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.0f, 1.0f, 0.0f, 0.4f)); // Green + else if (freePercentage > 30.0f) + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(1.0f, 1.0f, 0.0f, 0.4f)); // Yellow + else + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(1.0f, 0.0f, 0.0f, 0.4f)); // Red + + ImGui::ProgressBar(allocatedPercentage, barSize, ""); + + ImGui::PopStyleColor(); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + ImVec2 progressBarPos = ImGui::GetItemRectMin(); + ImVec2 progressBarSize = ImGui::GetItemRectSize(); + + const char* text = "%.2f%% free"; + char textBuffer[64]; + snprintf(textBuffer, sizeof(textBuffer), text, freePercentage); + + ImVec2 textSize = ImGui::CalcTextSize(textBuffer); + ImVec2 textPos = ImVec2 + ( + progressBarPos.x + (progressBarSize.x - textSize.x) * 0.5f, + progressBarPos.y + (progressBarSize.y - textSize.y) * 0.5f + ); + + ImVec4 bgColor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + drawList->AddRectFilled + ( + ImVec2(textPos.x - 5, textPos.y - 2), + ImVec2(textPos.x + textSize.x + 5, textPos.y + textSize.y + 2), + ImGui::GetColorU32(bgColor) + ); + + ImGui::SetCursorScreenPos(textPos); + ImGui::Text("%s", textBuffer); + + ImGui::Dummy(ImVec2(0.0f, verticalPadding)); + + ImGui::End(); + } + + displayKeyMappingsAndVirtualStates(controller.get()); + + ImGui::End(); + } + + std::chrono::seconds timeout = std::chrono::seconds(0x7fffFFFFu); + clock_t::time_point start; + + //! One window & surface + smart_refctd_ptr> m_surface; smart_refctd_ptr m_window; - smart_refctd_ptr> m_surface; - smart_refctd_ptr m_pipeline; + // We can't use the same semaphore for acquire and present, because that would disable "Frames in Flight" by syncing previous present against next acquire. + // At least two timelines must be used. smart_refctd_ptr m_semaphore; - smart_refctd_ptr m_cmdPool; + // Maximum frames which can be simultaneously submitted, used to cycle through our per-frame resources like command buffers + constexpr static inline uint32_t MaxFramesInFlight = 3u; + // Use a separate counter to cycle through our resources because `getAcquireCount()` increases upon spontaneous resizes with immediate blit-presents uint64_t m_realFrameIx = 0; + // We'll write to the Triple Buffer with a Renderpass + core::smart_refctd_ptr m_renderpass = {}; + // These are atomic counters where the Surface lets us know what's the latest Blit timeline semaphore value which will be signalled on the resource + std::array m_blitWaitValues; + // Enough Command Buffers and other resources for all frames in flight! std::array, MaxFramesInFlight> m_cmdBufs; - ISimpleManagedSurface::SAcquireResult m_currentImageAcquire = {}; - + // Our own persistent images that don't get recreated with the swapchain + std::array, MaxFramesInFlight> m_tripleBuffers; + // Resources derived from the images + std::array, MaxFramesInFlight> m_framebuffers = {}; + // We will use it to get some asset stuff like geometry creator smart_refctd_ptr m_assetManager; + // Input system for capturing system events core::smart_refctd_ptr m_inputSystem; + // Handles mouse events InputSystem::ChannelReader mouse; + // Handles keyboard events InputSystem::ChannelReader keyboard; constexpr static inline auto TexturesAmount = 2u; From 65efa99addb6e968a923f5f646023b0c0823f156 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 15 Nov 2024 14:15:07 +0100 Subject: [PATCH 36/84] fix some init bugs --- 61_UI/include/common.hpp | 2 +- 61_UI/main.cpp | 2 +- common/include/CGeomtryCreatorScene.hpp | 2 +- common/include/ICamera.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index b509031cf..653d2fc20 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -9,7 +9,7 @@ #include "CFPSCamera.hpp" #include "camera/CCameraController.hpp" #include "SimpleWindowedApplication.hpp" -#include "CEventCallback.hpp" +#include "InputSystem.hpp" // the example's headers #include "transform.hpp" diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 0725bc98f..2e1da13d2 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -435,7 +435,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication */ projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); - camera = make_smart_refctd_ptr(float32_t3{ -1.958f, 0.697f, 0.881f }, glm::quat(0.092f, 0.851f, -0.159f, 0.492f)); + camera = make_smart_refctd_ptr(float32_t3{ -2.017f, 0.386f, 0.684f }, glm::quat(0.55f, 0.047f, 0.830f, -0.072f)); // order important for quat, the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) controller = make_smart_refctd_ptr(core::smart_refctd_ptr(camera)); // init keyboard map diff --git a/common/include/CGeomtryCreatorScene.hpp b/common/include/CGeomtryCreatorScene.hpp index 7b6492d11..fdd877efb 100644 --- a/common/include/CGeomtryCreatorScene.hpp +++ b/common/include/CGeomtryCreatorScene.hpp @@ -1098,7 +1098,7 @@ class ResourceBuilder struct ObjectDrawHookCpu { - nbl::hlsl::float32_t3x4 model; + nbl::hlsl::float32_t3x4 model = nbl::hlsl::float32_t3x4(1.f); nbl::asset::SBasicViewParameters viewParameters; ObjectMeta meta; }; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index 630c7b624..7488a2af6 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -34,7 +34,7 @@ class ICamera : public IGimbalManipulateEncoder, virtual public core::IReference public: using base_t = IGimbal; - CGimbal(typename base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) {} + CGimbal(typename base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) { updateView(); } ~CGimbal() = default; struct SView From 2c428694d4a6e71a61e969ad3cac263b072f7fe3 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 15 Nov 2024 14:39:42 +0100 Subject: [PATCH 37/84] play with aspect ratio, make projection work in GUI window on window resize requests - add notes in comments, I will have to keep track for aspects per GUI windows in which I will render scene from active camera perspective bound to the GUI window --- 61_UI/include/transform.hpp | 12 ++++++++--- 61_UI/main.cpp | 41 +++++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/61_UI/include/transform.hpp b/61_UI/include/transform.hpp index 88a78f751..e02f0f533 100644 --- a/61_UI/include/transform.hpp +++ b/61_UI/include/transform.hpp @@ -10,11 +10,11 @@ static constexpr inline auto OfflineSceneTextureIx = 1u; struct TransformRequestParams { - bool useWindow = true, editTransformDecomposition = false, enableViewManipulate = false; - float camDistance = 8.f; + bool useWindow = true, editTransformDecomposition = false, enableViewManipulate = false; + float camDistance = 8.f, aspectRatio = {}, invAspectRatio = {}; }; -void EditTransform(float* cameraView, const float* cameraProjection, float* matrix, const TransformRequestParams& params) +void EditTransform(float* cameraView, const float* cameraProjection, float* matrix, TransformRequestParams& params) { static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::TRANSLATE); static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::LOCAL); @@ -116,6 +116,9 @@ void EditTransform(float* cameraView, const float* cameraProjection, float* matr ImVec2 windowPos = ImGui::GetWindowPos(); ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + params.aspectRatio = contentRegionSize.x / contentRegionSize.y; + params.invAspectRatio = contentRegionSize.y / contentRegionSize.x; + ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); @@ -140,6 +143,9 @@ void EditTransform(float* cameraView, const float* cameraProjection, float* matr viewManipulateRight = cursorPos.x + contentRegionSize.x; viewManipulateTop = cursorPos.y; + + params.aspectRatio = io.DisplaySize.x / io.DisplaySize.y; + params.invAspectRatio = io.DisplaySize.y / io.DisplaySize.x; } ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, NULL, useSnap ? &snap[0] : NULL, boundSizing ? bounds : NULL, boundSizingSnap ? boundsSnap : NULL); diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 2e1da13d2..b828a84b0 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -434,7 +434,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication TESTS, TODO: remove all once finished work & integrate with the example properly */ - projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); + transformParams.aspectRatio = float(m_window->getWidth()) / float(m_window->getHeight()); + transformParams.invAspectRatio = float(m_window->getHeight()) / float(m_window->getWidth()); + camera = make_smart_refctd_ptr(float32_t3{ -2.017f, 0.386f, 0.684f }, glm::quat(0.55f, 0.047f, 0.830f, -0.072f)); // order important for quat, the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) controller = make_smart_refctd_ptr(core::smart_refctd_ptr(camera)); @@ -769,17 +771,34 @@ class UISampleApp final : public examples::SimpleWindowedApplication inline void imguiListen() { ImGuiIO& io = ImGui::GetIO(); + + ImGuizmo::SetOrthographic(false); + ImGuizmo::BeginFrame(); + + ImGui::SetNextWindowPos(ImVec2(1024, 100), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(256, 256), ImGuiCond_Appearing); + + // create a window and insert the inspector + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); + ImGui::Begin("Editor"); + + if (ImGui::RadioButton("Full view", !transformParams.useWindow)) + transformParams.useWindow = false; + + // TODO: I need this logic per viewport we will render a scene from a point of view of its bound camera { + if (isPerspective) { if (isLH) - projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), transformParams.aspectRatio, zNear, zFar)); else - projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), transformParams.aspectRatio, zNear, zFar)); } else { - float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; + float viewHeight = viewWidth * transformParams.invAspectRatio; if (isLH) projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); @@ -788,20 +807,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - ImGuizmo::SetOrthographic(false); - ImGuizmo::BeginFrame(); - - ImGui::SetNextWindowPos(ImVec2(1024, 100), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(256, 256), ImGuiCond_Appearing); - - // create a window and insert the inspector - ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); - ImGui::Begin("Editor"); - - if (ImGui::RadioButton("Full view", !transformParams.useWindow)) - transformParams.useWindow = false; - ImGui::SameLine(); if (ImGui::RadioButton("Window", transformParams.useWindow)) From c1063e29ed38f85410f97232b9372cb43a7a551e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 18 Nov 2024 14:55:24 +0100 Subject: [PATCH 38/84] Make room for multiple geometry creator scene frame-buffers, add second camera to the example, do a few clean-ups. Mark TODOs, time to test imguizmo controller but first I need to pull latest imguizmo --- 61_UI/include/common.hpp | 6 +- 61_UI/include/keysmapping.hpp | 22 +- 61_UI/include/transform.hpp | 160 --- 61_UI/main.cpp | 318 ++++- common/include/CGeomtryCreatorScene.hpp | 1371 ++++++------------- common/include/ICamera.hpp | 4 +- common/include/camera/CCameraController.hpp | 90 -- common/include/camera/IGimbalController.hpp | 2 +- 8 files changed, 723 insertions(+), 1250 deletions(-) delete mode 100644 61_UI/include/transform.hpp delete mode 100644 common/include/camera/CCameraController.hpp diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index 653d2fc20..c5a1860da 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -7,12 +7,14 @@ // common api #include "CFPSCamera.hpp" -#include "camera/CCameraController.hpp" #include "SimpleWindowedApplication.hpp" #include "InputSystem.hpp" // the example's headers -#include "transform.hpp" +#include "nbl/ui/ICursorControl.h" +#include "nbl/ext/ImGui/ImGui.h" +#include "imgui/imgui_internal.h" +#include "imguizmo/ImGuizmo.h" #include "CGeomtryCreatorScene.hpp" using namespace nbl; diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp index 25bba3e68..69872c4ca 100644 --- a/61_UI/include/keysmapping.hpp +++ b/61_UI/include/keysmapping.hpp @@ -2,10 +2,10 @@ #define __NBL_KEYSMAPPING_H_INCLUDED__ #include "common.hpp" -#include "camera/CCameraController.hpp" +#include "ICamera.hpp" template -void handleAddMapping(const char* tableID, CCameraController* controller, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) +void handleAddMapping(const char* tableID, ICamera* camera, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) { ImGui::BeginTable(tableID, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.33f); @@ -67,9 +67,9 @@ void handleAddMapping(const char* tableID, CCameraController* controller, IGi if (ImGui::Button("Confirm Add", ImVec2(100, 30))) { if (activeController == IGimbalManipulateEncoder::Keyboard) - controller->updateKeyboardMapping([&](auto& keys) { keys[newKey] = selectedEventType; }); + camera->updateKeyboardMapping([&](auto& keys) { keys[newKey] = selectedEventType; }); else - controller->updateMouseMapping([&](auto& mouse) { mouse[newMouseCode] = selectedEventType; }); + camera->updateMouseMapping([&](auto& mouse) { mouse[newMouseCode] = selectedEventType; }); addMode = false; } @@ -77,7 +77,7 @@ void handleAddMapping(const char* tableID, CCameraController* controller, IGi } template -void displayKeyMappingsAndVirtualStates(CCameraController* controller) +void displayKeyMappingsAndVirtualStates(ICamera* camera) { static bool addMode = false, pendingChanges = false; static CVirtualGimbalEvent::VirtualEventType selectedEventType = CVirtualGimbalEvent::VirtualEventType::MoveForward; @@ -85,8 +85,8 @@ void displayKeyMappingsAndVirtualStates(CCameraController* controller) static ui::E_MOUSE_CODE newMouseCode = ui::EMC_LEFT_BUTTON; static IGimbalManipulateEncoder::EncoderType activeController = IGimbalManipulateEncoder::Keyboard; - const auto& keyboardMappings = controller->getKeyboardVirtualEventMap(); - const auto& mouseMappings = controller->getMouseVirtualEventMap(); + const auto& keyboardMappings = camera->getKeyboardVirtualEventMap(); + const auto& mouseMappings = camera->getMouseVirtualEventMap(); ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(600, 69000)); @@ -141,7 +141,7 @@ void displayKeyMappingsAndVirtualStates(CCameraController* controller) ImGui::TableSetColumnIndex(4); if (ImGui::Button(("Delete##deleteKey" + std::to_string(static_cast(hash.event.type))).c_str())) { - controller->updateKeyboardMapping([&](auto& keys) { keys.erase(keyboardCode); }); + camera->updateKeyboardMapping([&](auto& keys) { keys.erase(keyboardCode); }); pendingChanges = true; break; } @@ -151,7 +151,7 @@ void displayKeyMappingsAndVirtualStates(CCameraController* controller) if (addMode) { ImGui::Separator(); - handleAddMapping("AddKeyboardMappingTable", controller, activeController, selectedEventType, newKey, newMouseCode, addMode); + handleAddMapping("AddKeyboardMappingTable", camera, activeController, selectedEventType, newKey, newMouseCode, addMode); } ImGui::EndTabItem(); @@ -203,7 +203,7 @@ void displayKeyMappingsAndVirtualStates(CCameraController* controller) ImGui::TableSetColumnIndex(4); if (ImGui::Button(("Delete##deleteMouse" + std::to_string(static_cast(hash.event.type))).c_str())) { - controller->updateMouseMapping([&](auto& mouse) { mouse.erase(mouseCode); }); + camera->updateMouseMapping([&](auto& mouse) { mouse.erase(mouseCode); }); pendingChanges = true; break; } @@ -213,7 +213,7 @@ void displayKeyMappingsAndVirtualStates(CCameraController* controller) if (addMode) { ImGui::Separator(); - handleAddMapping("AddMouseMappingTable", controller, activeController, selectedEventType, newKey, newMouseCode, addMode); + handleAddMapping("AddMouseMappingTable", camera, activeController, selectedEventType, newKey, newMouseCode, addMode); } ImGui::EndTabItem(); } diff --git a/61_UI/include/transform.hpp b/61_UI/include/transform.hpp deleted file mode 100644 index e02f0f533..000000000 --- a/61_UI/include/transform.hpp +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef __NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED__ -#define __NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED__ - -#include "nbl/ui/ICursorControl.h" -#include "nbl/ext/ImGui/ImGui.h" -#include "imgui/imgui_internal.h" -#include "imguizmo/ImGuizmo.h" - -static constexpr inline auto OfflineSceneTextureIx = 1u; - -struct TransformRequestParams -{ - bool useWindow = true, editTransformDecomposition = false, enableViewManipulate = false; - float camDistance = 8.f, aspectRatio = {}, invAspectRatio = {}; -}; - -void EditTransform(float* cameraView, const float* cameraProjection, float* matrix, TransformRequestParams& params) -{ - static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::TRANSLATE); - static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::LOCAL); - static bool useSnap = false; - static float snap[3] = { 1.f, 1.f, 1.f }; - static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; - static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; - static bool boundSizing = false; - static bool boundSizingSnap = false; - - if (params.editTransformDecomposition) - { - if (ImGui::IsKeyPressed(ImGuiKey_T)) - mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - if (ImGui::IsKeyPressed(ImGuiKey_R)) - mCurrentGizmoOperation = ImGuizmo::ROTATE; - if (ImGui::IsKeyPressed(ImGuiKey_S)) - mCurrentGizmoOperation = ImGuizmo::SCALE; - if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) - mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - ImGui::SameLine(); - if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) - mCurrentGizmoOperation = ImGuizmo::ROTATE; - ImGui::SameLine(); - if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) - mCurrentGizmoOperation = ImGuizmo::SCALE; - if (ImGui::RadioButton("Universal", mCurrentGizmoOperation == ImGuizmo::UNIVERSAL)) - mCurrentGizmoOperation = ImGuizmo::UNIVERSAL; - float matrixTranslation[3], matrixRotation[3], matrixScale[3]; - ImGuizmo::DecomposeMatrixToComponents(matrix, matrixTranslation, matrixRotation, matrixScale); - ImGui::InputFloat3("Tr", matrixTranslation); - ImGui::InputFloat3("Rt", matrixRotation); - ImGui::InputFloat3("Sc", matrixScale); - ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix); - - if (mCurrentGizmoOperation != ImGuizmo::SCALE) - { - if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) - mCurrentGizmoMode = ImGuizmo::LOCAL; - ImGui::SameLine(); - if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) - mCurrentGizmoMode = ImGuizmo::WORLD; - } - if (ImGui::IsKeyPressed(ImGuiKey_S) && ImGui::IsKeyPressed(ImGuiKey_LeftShift)) - useSnap = !useSnap; - ImGui::Checkbox("##UseSnap", &useSnap); - ImGui::SameLine(); - - switch (mCurrentGizmoOperation) - { - case ImGuizmo::TRANSLATE: - ImGui::InputFloat3("Snap", &snap[0]); - break; - case ImGuizmo::ROTATE: - ImGui::InputFloat("Angle Snap", &snap[0]); - break; - case ImGuizmo::SCALE: - ImGui::InputFloat("Scale Snap", &snap[0]); - break; - } - ImGui::Checkbox("Bound Sizing", &boundSizing); - if (boundSizing) - { - ImGui::PushID(3); - ImGui::Checkbox("##BoundSizing", &boundSizingSnap); - ImGui::SameLine(); - ImGui::InputFloat3("Snap", boundsSnap); - ImGui::PopID(); - } - } - - ImGuiIO& io = ImGui::GetIO(); - float viewManipulateRight = io.DisplaySize.x; - float viewManipulateTop = 0; - static ImGuiWindowFlags gizmoWindowFlags = 0; - - /* - for the "useWindow" case we just render to a gui area, - otherwise to fake full screen transparent window - - note that for both cases we make sure gizmo being - rendered is aligned to our texture scene using - imgui "cursor" screen positions - */ - - SImResourceInfo info; - info.textureID = OfflineSceneTextureIx; - info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; - - if (params.useWindow) - { - ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); - ImGui::SetNextWindowPos(ImVec2(400, 20), ImGuiCond_Appearing); - ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); - ImGui::Begin("Gizmo", 0, gizmoWindowFlags); - ImGuizmo::SetDrawlist(); - - ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); - ImVec2 windowPos = ImGui::GetWindowPos(); - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - - params.aspectRatio = contentRegionSize.x / contentRegionSize.y; - params.invAspectRatio = contentRegionSize.y / contentRegionSize.x; - - ImGui::Image(info, contentRegionSize); - ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - viewManipulateRight = cursorPos.x + contentRegionSize.x; - viewManipulateTop = cursorPos.y; - - ImGuiWindow* window = ImGui::GetCurrentWindow(); - gizmoWindowFlags = (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); - } - else - { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(io.DisplaySize); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window - ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); - - ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - - ImGui::Image(info, contentRegionSize); - ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - viewManipulateRight = cursorPos.x + contentRegionSize.x; - viewManipulateTop = cursorPos.y; - - params.aspectRatio = io.DisplaySize.x / io.DisplaySize.y; - params.invAspectRatio = io.DisplaySize.y / io.DisplaySize.x; - } - - ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, NULL, useSnap ? &snap[0] : NULL, boundSizing ? bounds : NULL, boundSizingSnap ? boundsSnap : NULL); - - if(params.enableViewManipulate) - ImGuizmo::ViewManipulate(cameraView, params.camDistance, ImVec2(viewManipulateRight - 128, viewManipulateTop), ImVec2(128, 128), 0x10101010); - - ImGui::End(); - ImGui::PopStyleColor(); -} - -#endif // __NBL_THIS_EXAMPLE_TRANSFORM_H_INCLUDED__ \ No newline at end of file diff --git a/61_UI/main.cpp b/61_UI/main.cpp index b828a84b0..7ee57ca3a 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -9,7 +9,6 @@ // FPS Camera, TESTS using camera_t = CFPSCamera; -using controller_t = CCameraController; using projection_t = IProjection>; // TODO: temporary -> projections will own/reference cameras constexpr IGPUImage::SSubresourceRange TripleBufferUsedSubresourceRange = @@ -398,14 +397,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication params.transfer = getTransferUpQueue(); params.utilities = m_utils; - pass.ui.manager = nbl::ext::imgui::UI::create(std::move(params)); + m_ui.manager = nbl::ext::imgui::UI::create(std::move(params)); } - if (!pass.ui.manager) + if (!m_ui.manager) return false; // note that we use default layout provided by our extension, but you are free to create your own by filling nbl::ext::imgui::UI::S_CREATION_PARAMETERS::resources - const auto* descriptorSetLayout = pass.ui.manager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); + const auto* descriptorSetLayout = m_ui.manager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); IDescriptorPool::SCreateInfo descriptorPoolInfo = {}; descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = (uint32_t)nbl::ext::imgui::UI::DefaultSamplerIx::COUNT; @@ -416,16 +415,52 @@ class UISampleApp final : public examples::SimpleWindowedApplication m_descriptorSetPool = m_device->createDescriptorPool(std::move(descriptorPoolInfo)); assert(m_descriptorSetPool); - m_descriptorSetPool->createDescriptorSets(1u, &descriptorSetLayout, &pass.ui.descriptorSet); - assert(pass.ui.descriptorSet); + m_descriptorSetPool->createDescriptorSets(1u, &descriptorSetLayout, &m_ui.descriptorSet); + assert(m_ui.descriptorSet); - pass.ui.manager->registerListener([this]() -> void { imguiListen(); }); + m_ui.manager->registerListener([this]() -> void { imguiListen(); }); } - // Geometry Creator Scene + // Geometry Creator Render Scenes { - //pass.scene = CScene::create(smart_refctd_ptr(m_utils), smart_refctd_ptr(m_logger), getGraphicsQueue(), m_assetManager->getGeometryCreator()); - pass.scene = CScene::create(smart_refctd_ptr(m_utils), smart_refctd_ptr(m_logger), getGraphicsQueue(), m_assetManager->getGeometryCreator()); + resources = ResourcesBundle::create(m_device.get(), m_logger.get(), getGraphicsQueue(), m_assetManager->getGeometryCreator()); + + if (!resources) + { + m_logger->log("Could not create geometry creator gpu resources!", ILogger::ELL_ERROR); + return false; + } + + for (auto& camera : cameraz) + camera = make_smart_refctd_ptr(float32_t3{ -2.017f, 0.386f, 0.684f }, glm::quat(0.55f, 0.047f, 0.830f, -0.072f)); // order important for quat, the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) + + // lets use key map presets to update the controller + auto& camera = cameraz.front(); + scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources)); + + if (!scene) + { + m_logger->log("Could not create geometry creator scene!", ILogger::ELL_ERROR); + return false; + } + + // init keyboard map + camera->updateKeyboardMapping([&](auto& keys) + { + keys = camera->getKeyboardMappingPreset(); + }); + + // init mouse map + camera->updateMouseMapping([&](auto& keys) + { + keys = camera->getMouseMappingPreset(); + }); + + // init imguizmo map + camera->updateImguizmoMapping([&](auto& keys) + { + keys = camera->getImguizmoMappingPreset(); + }); } oracle.reportBeginFrameRecord(); @@ -437,27 +472,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication transformParams.aspectRatio = float(m_window->getWidth()) / float(m_window->getHeight()); transformParams.invAspectRatio = float(m_window->getHeight()) / float(m_window->getWidth()); - camera = make_smart_refctd_ptr(float32_t3{ -2.017f, 0.386f, 0.684f }, glm::quat(0.55f, 0.047f, 0.830f, -0.072f)); // order important for quat, the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) - controller = make_smart_refctd_ptr(core::smart_refctd_ptr(camera)); - - // init keyboard map - controller->updateKeyboardMapping([&](auto& keys) - { - keys = controller->getCamera()->getKeyboardMappingPreset(); - }); - - // init mouse map - controller->updateMouseMapping([&](auto& keys) - { - keys = controller->getCamera()->getMouseMappingPreset(); - }); - - // init imguizmo map - controller->updateImguizmoMapping([&](auto& keys) - { - keys = controller->getCamera()->getImguizmoMappingPreset(); - }); - if (base_t::argv.size() >= 3 && argv[1] == "-timeout_seconds") timeout = std::chrono::seconds(std::atoi(argv[2].c_str())); start = clock_t::now(); @@ -471,14 +485,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication static IGPUDescriptorSet::SWriteDescriptorSet writes[TexturesAmount]; descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].desc = core::smart_refctd_ptr(pass.ui.manager->getFontAtlasView()); + descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].desc = core::smart_refctd_ptr(m_ui.manager->getFontAtlasView()); descriptorInfo[OfflineSceneTextureIx].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - descriptorInfo[OfflineSceneTextureIx].desc = pass.scene->getResources().attachments.color; + descriptorInfo[OfflineSceneTextureIx].desc = scene->getColorAttachment(); for (uint32_t i = 0; i < descriptorInfo.size(); ++i) { - writes[i].dstSet = pass.ui.descriptorSet.get(); + writes[i].dstSet = m_ui.descriptorSet.get(); writes[i].binding = 0u; writes[i].arrayElement = i; writes[i].count = 1u; @@ -530,13 +544,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication // render geometry creator scene to offline frame buffer & submit // TODO: OK with TRI buffer this thing is retarded now // (**) <- a note why bellow before submit - pass.scene->begin(); + scene->begin(); { - pass.scene->update(); - pass.scene->record(); - pass.scene->end(); + scene->update(); + scene->record(); + scene->end(); } - pass.scene->submit(); + scene->submit(getGraphicsQueue()); willSubmit &= cmdbuf->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); willSubmit &= cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); @@ -573,25 +587,27 @@ class UISampleApp final : public examples::SimpleWindowedApplication IQueue::SSubmitInfo::SCommandBufferInfo commandBuffersInfo[] = { {.cmdbuf = cmdbuf } }; + + const IGPUCommandBuffer::SRenderpassBeginInfo info = { .framebuffer = m_framebuffers[resourceIx].get(), - .colorClearValues = &clear.color, + .colorClearValues = &Traits::clearColor, .depthStencilClearValues = nullptr, .renderArea = currentRenderArea }; nbl::video::ISemaphore::SWaitInfo waitInfo = { .semaphore = m_semaphore.get(), .value = m_realFrameIx + 1u }; - const auto uiParams = pass.ui.manager->getCreationParameters(); - auto* pipeline = pass.ui.manager->getPipeline(); + const auto uiParams = m_ui.manager->getCreationParameters(); + auto* pipeline = m_ui.manager->getPipeline(); cmdbuf->bindGraphicsPipeline(pipeline); - cmdbuf->bindDescriptorSets(EPBP_GRAPHICS, pipeline->getLayout(), uiParams.resources.texturesInfo.setIx, 1u, &pass.ui.descriptorSet.get()); // note that we use default UI pipeline layout where uiParams.resources.textures.setIx == uiParams.resources.samplers.setIx + cmdbuf->bindDescriptorSets(EPBP_GRAPHICS, pipeline->getLayout(), uiParams.resources.texturesInfo.setIx, 1u, &m_ui.descriptorSet.get()); // note that we use default UI pipeline layout where uiParams.resources.textures.setIx == uiParams.resources.samplers.setIx if (!keepRunning()) return; - willSubmit &= pass.ui.manager->render(cmdbuf, waitInfo); + willSubmit &= m_ui.manager->render(cmdbuf, waitInfo); } willSubmit &= cmdbuf->endRenderPass(); @@ -666,8 +682,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication { const nbl::video::ISemaphore::SWaitInfo waitInfos[] = { { - .semaphore = pass.scene->semaphore.progress.get(), - .value = pass.scene->semaphore.finishedValue + .semaphore = scene->semaphore.progress.get(), + .value = scene->semaphore.finishedValue } }; m_device->blockForSemaphores(waitInfos); @@ -761,10 +777,27 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (move) { // TODO: testing - controller->manipulateViewGimbal({ params.keyboardEvents, params.mouseEvents }, nextPresentationTimestamp); + auto& camera = cameraz.front().get(); + + + std::vector virtualEvents(0x45); // TODO: tmp + uint32_t vCount; + + camera->beginInputProcessing(nextPresentationTimestamp); + { + camera->process(nullptr, vCount); + + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); + + camera->process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); + } + camera->endInputProcessing(); + + camera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); } - pass.ui.manager->update(params); + m_ui.manager->update(params); } private: @@ -929,18 +962,165 @@ class UISampleApp final : public examples::SimpleWindowedApplication } imguizmoM16InOut; const auto& projectionMatrix = projection->getMatrix(); - const auto& view = camera->getGimbal().getView(); + const auto& view = cameraz.front()->getGimbal().getView(); ImGuizmo::SetID(0u); imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(view.matrix)); imguizmoM16InOut.projection = transpose(projectionMatrix); - imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(pass.scene->object.model)); + imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(scene->object.model)); { if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ transformParams.editTransformDecomposition = true; - EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); + { + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::TRANSLATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::LOCAL); + static bool useSnap = false; + static float snap[3] = { 1.f, 1.f, 1.f }; + static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; + static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; + static bool boundSizing = false; + static bool boundSizingSnap = false; + + auto* cameraView = &imguizmoM16InOut.view[0][0]; + auto* cameraProjection = &imguizmoM16InOut.projection[0][0]; + auto* matrix = &imguizmoM16InOut.model[0][0]; + + if (transformParams.editTransformDecomposition) + { + if (ImGui::IsKeyPressed(ImGuiKey_T)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(ImGuiKey_R)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(ImGuiKey_S)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Universal", mCurrentGizmoOperation == ImGuizmo::UNIVERSAL)) + mCurrentGizmoOperation = ImGuizmo::UNIVERSAL; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation); + ImGui::InputFloat3("Rt", matrixRotation); + ImGui::InputFloat3("Sc", matrixScale); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + if (ImGui::IsKeyPressed(ImGuiKey_S) && ImGui::IsKeyPressed(ImGuiKey_LeftShift)) + useSnap = !useSnap; + ImGui::Checkbox("##UseSnap", &useSnap); + ImGui::SameLine(); + + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + ImGui::InputFloat3("Snap", &snap[0]); + break; + case ImGuizmo::ROTATE: + ImGui::InputFloat("Angle Snap", &snap[0]); + break; + case ImGuizmo::SCALE: + ImGui::InputFloat("Scale Snap", &snap[0]); + break; + } + ImGui::Checkbox("Bound Sizing", &boundSizing); + if (boundSizing) + { + ImGui::PushID(3); + ImGui::Checkbox("##BoundSizing", &boundSizingSnap); + ImGui::SameLine(); + ImGui::InputFloat3("Snap", boundsSnap); + ImGui::PopID(); + } + } + + ImGuiIO& io = ImGui::GetIO(); + float viewManipulateRight = io.DisplaySize.x; + float viewManipulateTop = 0; + static ImGuiWindowFlags gizmoWindowFlags = 0; + + /* + for the "useWindow" case we just render to a gui area, + otherwise to fake full screen transparent window + + note that for both cases we make sure gizmo being + rendered is aligned to our texture scene using + imgui "cursor" screen positions + */ + + SImResourceInfo info; + info.textureID = OfflineSceneTextureIx; + info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; + + if (transformParams.useWindow) + { + ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImVec2(400, 20), ImGuiCond_Appearing); + ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); + ImGui::Begin("Gizmo", 0, gizmoWindowFlags); + ImGuizmo::SetDrawlist(); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + transformParams.aspectRatio = contentRegionSize.x / contentRegionSize.y; + transformParams.invAspectRatio = contentRegionSize.y / contentRegionSize.x; + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + + viewManipulateRight = cursorPos.x + contentRegionSize.x; + viewManipulateTop = cursorPos.y; + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + gizmoWindowFlags = (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); + } + else + { + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window + ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + + viewManipulateRight = cursorPos.x + contentRegionSize.x; + viewManipulateTop = cursorPos.y; + + transformParams.aspectRatio = io.DisplaySize.x / io.DisplaySize.y; + transformParams.invAspectRatio = io.DisplaySize.y / io.DisplaySize.x; + } + + // object gizmo + ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, NULL, useSnap ? &snap[0] : NULL, boundSizing ? bounds : NULL, boundSizingSnap ? boundsSnap : NULL); + + // second camera + { + // TODO: manipulate second camera given view & projection from first, use delta matrix to be passed to imguizmo controller + } + + ImGui::End(); + ImGui::PopStyleColor(); + } } // to Nabla + update camera & model matrices @@ -951,10 +1131,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication static float32_t3x4 modelView, normal; static float32_t4x4 modelViewProjection; - auto& hook = pass.scene->object; + auto& hook = scene->object; hook.model = float32_t3x4(transpose(imguizmoM16InOut.model)); { - const auto& references = pass.scene->getResources().objects; + const auto& references = resources->objects; const auto type = static_cast(gcIndex); const auto& [gpu, meta] = references[type]; @@ -1009,9 +1189,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Separator(); }; - const auto& orientation = camera->getGimbal().getOrthonornalMatrix(); + auto& camera = cameraz.front(); + const auto& orientation = cameraz.front()->getGimbal().getOrthonornalMatrix(); - addMatrixTable("Object's Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); + addMatrixTable("Object's Model Matrix", "ModelMatrixTable", 3, 4, &scene->object.model[0][0]); addMatrixTable("Camera's Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); addMatrixTable("Camera's Orientation Quat", "OrientationQuatTable", 1, 4, &camera->getGimbal().getOrientation()[0]); addMatrixTable("Camera's View Matrix", "ViewMatrixTable", 3, 4, &view.matrix[0][0]); @@ -1024,7 +1205,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, // so unless you do that, don't use this basic info to optimize the size of your IMGUI buffer. { - auto* streaminingBuffer = pass.ui.manager->getStreamingBuffer(); + auto* streaminingBuffer = m_ui.manager->getStreamingBuffer(); const size_t total = streaminingBuffer->get_total_size(); // total memory range size for which allocation can be requested const size_t freeSize = streaminingBuffer->getAddressAllocator().get_free_size(); // max total free bloock memory size we can still allocate from total memory available @@ -1089,7 +1270,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } - displayKeyMappingsAndVirtualStates(controller.get()); + displayKeyMappingsAndVirtualStates(cameraz.front().get()); ImGui::End(); } @@ -1130,7 +1311,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication core::smart_refctd_ptr m_descriptorSetPool; - struct C_UI + struct CRenderUI { nbl::core::smart_refctd_ptr manager; @@ -1142,19 +1323,26 @@ class UISampleApp final : public examples::SimpleWindowedApplication core::smart_refctd_ptr descriptorSet; }; - struct E_APP_PASS - { - nbl::core::smart_refctd_ptr scene; - C_UI ui; - } pass; + nbl::core::smart_refctd_ptr scene; + // lets first test 2 cameras & imguizmo controller, then we will add second scene to render into 2 frame buffers + std::array>, 2u> cameraz; + nbl::core::smart_refctd_ptr resources; + + CRenderUI m_ui; smart_refctd_ptr projection = make_smart_refctd_ptr(); // TMP! - core::smart_refctd_ptr> camera; - core::smart_refctd_ptr controller; video::CDumbPresentationOracle oracle; uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed + struct TransformRequestParams + { + bool useWindow = true, editTransformDecomposition = false, enableViewManipulate = false; + float camDistance = 8.f, aspectRatio = {}, invAspectRatio = {}; + }; + + static constexpr inline auto OfflineSceneTextureIx = 1u; + TransformRequestParams transformParams; bool isPerspective = true, isLH = true, flipGizmoY = true, move = false; float fov = 60.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; diff --git a/common/include/CGeomtryCreatorScene.hpp b/common/include/CGeomtryCreatorScene.hpp index fdd877efb..07f54b19a 100644 --- a/common/include/CGeomtryCreatorScene.hpp +++ b/common/include/CGeomtryCreatorScene.hpp @@ -10,6 +10,21 @@ namespace nbl::scene::geometrycreator { +#define EXPOSE_NABLA_NAMESPACES() using namespace nbl; \ +using namespace core; \ +using namespace asset; \ +using namespace video; \ +using namespace scene; \ +using namespace system + +struct Traits +{ + static constexpr auto FramebufferW = 1280u, FramebufferH = 720u; + static constexpr auto ColorFboAttachmentFormat = nbl::asset::EF_R8G8B8A8_SRGB, DepthFboAttachmentFormat = nbl::asset::EF_D16_UNORM; + static constexpr auto Samples = nbl::video::IGPUImage::ESCF_1_BIT; + static constexpr nbl::video::IGPUCommandBuffer::SClearColorValue clearColor = { .float32 = {0.f,0.f,0.f,1.f} }; + static constexpr nbl::video::IGPUCommandBuffer::SClearDepthStencilValue clearDepth = { .depth = 0.f }; +}; enum ObjectType : uint8_t { @@ -32,38 +47,16 @@ struct ObjectMeta std::string_view name = "Unknown"; }; -constexpr static inline struct ClearValues +struct ResourcesBundle : public virtual nbl::core::IReferenceCounted { - nbl::video::IGPUCommandBuffer::SClearColorValue color = { .float32 = {0.f,0.f,0.f,1.f} }; - nbl::video::IGPUCommandBuffer::SClearDepthStencilValue depth = { .depth = 0.f }; -} clear; - -#define TYPES_IMPL_BOILERPLATE(WithConverter) struct Types \ -{ \ - using descriptor_set_layout_t = std::conditional_t; \ - using pipeline_layout_t = std::conditional_t; \ - using renderpass_t = std::conditional_t; \ - using image_view_t = std::conditional_t; \ - using image_t = std::conditional_t; \ - using buffer_t = std::conditional_t; \ - using shader_t = std::conditional_t; \ - using graphics_pipeline_t = std::conditional_t; \ - using descriptor_set = std::conditional_t; \ -} - -template -struct ResourcesBundleBase -{ - TYPES_IMPL_BOILERPLATE(withAssetConverter); - struct ReferenceObject { struct Bindings { - nbl::asset::SBufferBinding vertex, index; + nbl::asset::SBufferBinding vertex, index; }; - nbl::core::smart_refctd_ptr pipeline = nullptr; + nbl::core::smart_refctd_ptr pipeline = nullptr; Bindings bindings; nbl::asset::E_INDEX_TYPE indexType = nbl::asset::E_INDEX_TYPE::EIT_UNKNOWN; @@ -72,526 +65,129 @@ struct ResourcesBundleBase using ReferenceDrawHook = std::pair; - nbl::core::smart_refctd_ptr renderpass; std::array objects; - nbl::asset::SBufferBinding ubo; + nbl::core::smart_refctd_ptr renderpass; + nbl::core::smart_refctd_ptr dsLayout; - struct - { - nbl::core::smart_refctd_ptr color, depth; - } attachments; - - nbl::core::smart_refctd_ptr descriptorSet; -}; - -struct ResourcesBundle : public ResourcesBundleBase -{ - using base_t = ResourcesBundleBase; -}; - -#define EXPOSE_NABLA_NAMESPACES() using namespace nbl; \ -using namespace core; \ -using namespace asset; \ -using namespace video; \ -using namespace scene; \ -using namespace system - -template -class ResourceBuilder -{ -public: - TYPES_IMPL_BOILERPLATE(withAssetConverter); - - using this_t = ResourceBuilder; - - ResourceBuilder(nbl::video::IUtilities* const _utilities, nbl::video::IGPUCommandBuffer* const _commandBuffer, nbl::system::ILogger* const _logger, const nbl::asset::IGeometryCreator* const _geometryCreator) - : utilities(_utilities), commandBuffer(_commandBuffer), logger(_logger), geometries(_geometryCreator) - { - assert(utilities); - assert(logger); - } - - /* - if (withAssetConverter) then - -> .build cpu objects - else - -> .build gpu objects & record any resource update upload transfers into command buffer - */ - - inline bool build() + static inline nbl::core::smart_refctd_ptr create(nbl::video::ILogicalDevice* const device, nbl::system::ILogger* const logger, nbl::video::CThreadSafeQueueAdapter* transferCapableQueue, const nbl::asset::IGeometryCreator* gc) { EXPOSE_NABLA_NAMESPACES(); - if constexpr (!withAssetConverter) - { - commandBuffer->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); - commandBuffer->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - commandBuffer->beginDebugMarker("Resources builder's buffers upload [manual]"); - } - - using functor_t = std::function; - - auto work = std::to_array - ({ - functor_t(std::bind(&this_t::createDescriptorSetLayout, this)), - functor_t(std::bind(&this_t::createPipelineLayout, this)), - functor_t(std::bind(&this_t::createRenderpass, this)), - functor_t(std::bind(&this_t::createFramebufferAttachments, this)), - functor_t(std::bind(&this_t::createShaders, this)), - functor_t(std::bind(&this_t::createGeometries, this)), - functor_t(std::bind(&this_t::createViewParametersUboBuffer, this)), - functor_t(std::bind(&this_t::createDescriptorSet, this)) - }); - - for (auto& task : work) - if (!task()) - return false; - - if constexpr (!withAssetConverter) - commandBuffer->end(); - - return true; - } + if (!device) + return nullptr; - /* - if (withAssetConverter) then - -> .convert cpu objects to gpu & update gpu buffers - else - -> update gpu buffers - */ + if (!logger) + return nullptr; - inline bool finalize(ResourcesBundle& output, nbl::video::CThreadSafeQueueAdapter* transferCapableQueue) - { - EXPOSE_NABLA_NAMESPACES(); + if (!transferCapableQueue) + return nullptr; - // TODO: use multiple command buffers - std::array commandBuffers = {}; - { - commandBuffers.front().cmdbuf = commandBuffer; - } + auto cPool = device->createCommandPool(transferCapableQueue->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); - if constexpr (withAssetConverter) + if (!cPool) { - // note that asset converter records basic transfer uploads itself, we only begin the recording with ONE_TIME_SUBMIT_BIT - commandBuffer->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); - commandBuffer->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - commandBuffer->beginDebugMarker("Resources builder's buffers upload [asset converter]"); - - // asset converter - scratch at this point has ready to convert cpu resources - smart_refctd_ptr converter = CAssetConverter::create({ .device = utilities->getLogicalDevice(),.optimizer = {} }); - CAssetConverter::SInputs inputs = {}; - inputs.logger = logger; - - struct ProxyCpuHooks - { - using object_size_t = std::tuple_size; - - std::array renderpass; - std::array pipelines; - std::array buffers; - std::array attachments; - std::array descriptorSet; - } hooks; - - enum AttachmentIx - { - AI_COLOR = 0u, - AI_DEPTH = 1u, - - AI_COUNT - }; - - // gather CPU assets into span memory views - { - hooks.renderpass.front() = scratch.renderpass.get(); - for (uint32_t i = 0u; i < hooks.pipelines.size(); ++i) - { - auto& [reference, meta] = scratch.objects[static_cast(i)]; - hooks.pipelines[i] = reference.pipeline.get(); - - // [[ [vertex, index] [vertex, index] [vertex, index] ... [ubo] ]] - hooks.buffers[2u * i + 0u] = reference.bindings.vertex.buffer.get(); - hooks.buffers[2u * i + 1u] = reference.bindings.index.buffer.get(); - } - hooks.buffers.back() = scratch.ubo.buffer.get(); - hooks.attachments[AI_COLOR] = scratch.attachments.color.get(); - hooks.attachments[AI_DEPTH] = scratch.attachments.depth.get(); - hooks.descriptorSet.front() = scratch.descriptorSet.get(); - } - - // assign the CPU hooks to converter's inputs - { - std::get>(inputs.assets) = hooks.renderpass; - std::get>(inputs.assets) = hooks.pipelines; - std::get>(inputs.assets) = hooks.buffers; - // std::get>(inputs.assets) = hooks.attachments; // NOTE: THIS IS NOT IMPLEMENTED YET IN CONVERTER! - std::get>(inputs.assets) = hooks.descriptorSet; - } - - // reserve and create the GPU object handles - auto reservation = converter->reserve(inputs); - { - auto prepass = [&](const auto& references) -> bool - { - // retrieve the reserved handles - auto objects = reservation.getGPUObjects(); - - uint32_t counter = {}; - for (auto& object : objects) - { - // anything that fails to be reserved is a nullptr in the span of GPU Objects - auto gpu = object.value; - auto* reference = references[counter]; - - if (reference) - { - // validate - if (!gpu) // throw errors only if corresponding cpu hook was VALID (eg. we may have nullptr for some index buffers in the span for converter but it's OK, I'm too lazy to filter them before passing to the converter inputs and don't want to deal with dynamic alloc) - { - logger->log("Failed to convert a CPU object to GPU!", ILogger::ELL_ERROR); - return false; - } - } - - ++counter; - } - - return true; - }; - - prepass.template operator() < ICPURenderpass > (hooks.renderpass); - prepass.template operator() < ICPUGraphicsPipeline > (hooks.pipelines); - prepass.template operator() < ICPUBuffer > (hooks.buffers); - // validate.template operator() < ICPUImageView > (hooks.attachments); - prepass.template operator() < ICPUDescriptorSet > (hooks.descriptorSet); - } - - auto semaphore = utilities->getLogicalDevice()->createSemaphore(0u); - - // TODO: compute submit as well for the images' mipmaps - SIntendedSubmitInfo transfer = {}; - transfer.queue = transferCapableQueue; - transfer.scratchCommandBuffers = commandBuffers; - transfer.scratchSemaphore = { - .semaphore = semaphore.get(), - .value = 0u, - .stageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS - }; - // issue the convert call - { - CAssetConverter::SConvertParams params = {}; - params.utilities = utilities; - params.transfer = &transfer; - - // basically it records all data uploads and submits them right away - auto future = reservation.convert(params); - if (future.copy()!=IQueue::RESULT::SUCCESS) - { - logger->log("Failed to await submission feature!", ILogger::ELL_ERROR); - return false; - } - - // assign gpu objects to output - auto& base = static_cast(output); - { - auto&& [renderpass, pipelines, buffers, descriptorSet] = std::make_tuple(reservation.getGPUObjects().front().value, reservation.getGPUObjects(), reservation.getGPUObjects(), reservation.getGPUObjects().front().value); - { - base.renderpass = renderpass; - for (uint32_t i = 0u; i < pipelines.size(); ++i) - { - const auto type = static_cast(i); - const auto& [rcpu, rmeta] = scratch.objects[type]; - auto& [gpu, meta] = base.objects[type]; - - gpu.pipeline = pipelines[i].value; - // [[ [vertex, index] [vertex, index] [vertex, index] ... [ubo] ]] - gpu.bindings.vertex = {.offset = 0u, .buffer = buffers[2u * i + 0u].value}; - gpu.bindings.index = {.offset = 0u, .buffer = buffers[2u * i + 1u].value}; - - gpu.indexCount = rcpu.indexCount; - gpu.indexType = rcpu.indexType; - meta.name = rmeta.name; - meta.type = rmeta.type; - } - base.ubo = {.offset = 0u, .buffer = buffers.back().value}; - base.descriptorSet = descriptorSet; - - /* - // base.attachments.color = attachments[AI_COLOR].value; - // base.attachments.depth = attachments[AI_DEPTH].value; - - note conversion of image views is not yet supported by the asset converter - - it's complicated, we have to kinda temporary ignore DRY a bit here to not break the design which is correct - - TEMPORARY: we patch attachments by allocating them ourselves here given cpu instances & parameters - TODO: remove following code once asset converter works with image views & update stuff - */ - - for (uint32_t i = 0u; i < AI_COUNT; ++i) - { - const auto* reference = hooks.attachments[i]; - auto& out = (i == AI_COLOR ? base.attachments.color : base.attachments.depth); - - const auto& viewParams = reference->getCreationParameters(); - const auto& imageParams = viewParams.image->getCreationParameters(); - - auto image = utilities->getLogicalDevice()->createImage - ( - IGPUImage::SCreationParams - ({ - .type = imageParams.type, - .samples = imageParams.samples, - .format = imageParams.format, - .extent = imageParams.extent, - .mipLevels = imageParams.mipLevels, - .arrayLayers = imageParams.arrayLayers, - .usage = imageParams.usage - }) - ); - - if (!image) - { - logger->log("Could not create image!", ILogger::ELL_ERROR); - return false; - } - - bool IS_DEPTH = isDepthOrStencilFormat(imageParams.format); - std::string_view DEBUG_NAME = IS_DEPTH ? "UI Scene Depth Attachment Image" : "UI Scene Color Attachment Image"; - image->setObjectDebugName(DEBUG_NAME.data()); - - if (!utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) - { - logger->log("Could not allocate memory for an image!", ILogger::ELL_ERROR); - return false; - } - - out = utilities->getLogicalDevice()->createImageView - ( - IGPUImageView::SCreationParams - ({ - .flags = viewParams.flags, - .subUsages = viewParams.subUsages, - .image = std::move(image), - .viewType = viewParams.viewType, - .format = viewParams.format, - .subresourceRange = viewParams.subresourceRange - }) - ); - - if (!out) - { - logger->log("Could not create image view!", ILogger::ELL_ERROR); - return false; - } - } - - logger->log("Image View attachments has been allocated by hand after asset converter successful submit becasuse it doesn't support converting them yet!", ILogger::ELL_WARNING); - } - } - } + logger->log("Couldn't create command pool!", ILogger::ELL_ERROR); + return nullptr; } - else - { - auto completed = utilities->getLogicalDevice()->createSemaphore(0u); - std::array signals; - { - auto& signal = signals.front(); - signal.value = 1; - signal.stageMask = bitflag(PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS); - signal.semaphore = completed.get(); - } - - const IQueue::SSubmitInfo infos [] = - { - { - .waitSemaphores = {}, - .commandBuffers = commandBuffers, // note that here our command buffer is already recorded! - .signalSemaphores = signals - } - }; - - if (transferCapableQueue->submit(infos) != IQueue::RESULT::SUCCESS) - { - logger->log("Failed to submit transfer upload operations!", ILogger::ELL_ERROR); - return false; - } - - const ISemaphore::SWaitInfo info [] = - { { - .semaphore = completed.get(), - .value = 1 - } }; - - utilities->getLogicalDevice()->blockForSemaphores(info); - - static_cast(output) = static_cast(scratch); // scratch has all ready to use allocated gpu resources with uploaded memory so now just assign resources to base output - } + nbl::core::smart_refctd_ptr cmd; - // write the descriptor set + if (!cPool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { &cmd , 1 })) { - // descriptor write ubo - IGPUDescriptorSet::SWriteDescriptorSet write; - write.dstSet = output.descriptorSet.get(); - write.binding = 0; - write.arrayElement = 0u; - write.count = 1u; - - IGPUDescriptorSet::SDescriptorInfo info; - { - info.desc = smart_refctd_ptr(output.ubo.buffer); - info.info.buffer.offset = output.ubo.offset; - info.info.buffer.size = output.ubo.buffer->getSize(); - } - - write.info = &info; - - if(!utilities->getLogicalDevice()->updateDescriptorSets(1u, &write, 0u, nullptr)) - { - logger->log("Could not write descriptor set!", ILogger::ELL_ERROR); - return false; - } + logger->log("Couldn't create command buffer!", ILogger::ELL_ERROR); + return nullptr; } - return true; - } + if (!cmd) + return nullptr; -private: - bool createDescriptorSetLayout() - { - EXPOSE_NABLA_NAMESPACES(); + cmd->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); + cmd->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + cmd->beginDebugMarker("GC Scene resources upload buffer"); - typename Types::descriptor_set_layout_t::SBinding bindings[] = + //! descriptor set layout + + IGPUDescriptorSetLayout::SBinding bindings[] = { { .binding = 0u, .type = IDescriptor::E_TYPE::ET_UNIFORM_BUFFER, - .createFlags = Types::descriptor_set_layout_t::SBinding::E_CREATE_FLAGS::ECF_NONE, + .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, .count = 1u, } }; - if constexpr (withAssetConverter) - scratch.descriptorSetLayout = make_smart_refctd_ptr(bindings); - else - scratch.descriptorSetLayout = utilities->getLogicalDevice()->createDescriptorSetLayout(bindings); + auto dsLayout = device->createDescriptorSetLayout(bindings); - if (!scratch.descriptorSetLayout) + if (!dsLayout) { logger->log("Could not descriptor set layout!", ILogger::ELL_ERROR); - return false; - } - - return true; - } - - bool createDescriptorSet() - { - EXPOSE_NABLA_NAMESPACES(); - - if constexpr (withAssetConverter) - scratch.descriptorSet = make_smart_refctd_ptr(smart_refctd_ptr(scratch.descriptorSetLayout)); - else - { - const IGPUDescriptorSetLayout* const layouts[] = { scratch.descriptorSetLayout.get()}; - const uint32_t setCounts[] = { 1u }; - - // note descriptor set has back smart pointer to its pool, so we dont need to keep it explicitly - auto pool = utilities->getLogicalDevice()->createDescriptorPoolForDSLayouts(IDescriptorPool::E_CREATE_FLAGS::ECF_NONE, layouts, setCounts); - - if (!pool) - { - logger->log("Could not create Descriptor Pool!", ILogger::ELL_ERROR); - return false; - } - - pool->createDescriptorSets(layouts, &scratch.descriptorSet); - } - - if (!scratch.descriptorSet) - { - logger->log("Could not create Descriptor Set!", ILogger::ELL_ERROR); - return false; + return nullptr; } - return true; - } - - bool createPipelineLayout() - { - EXPOSE_NABLA_NAMESPACES(); - - const std::span range = {}; - - if constexpr (withAssetConverter) - scratch.pipelineLayout = make_smart_refctd_ptr(range, nullptr, smart_refctd_ptr(scratch.descriptorSetLayout), nullptr, nullptr); - else - scratch.pipelineLayout = utilities->getLogicalDevice()->createPipelineLayout(range, nullptr, smart_refctd_ptr(scratch.descriptorSetLayout), nullptr, nullptr); + //! pipeline layout + + auto pipelineLayout = device->createPipelineLayout({}, nullptr, smart_refctd_ptr(dsLayout), nullptr, nullptr); - if (!scratch.pipelineLayout) + if (!pipelineLayout) { logger->log("Could not create pipeline layout!", ILogger::ELL_ERROR); - return false; + return nullptr; } - return true; - } - - bool createRenderpass() - { - EXPOSE_NABLA_NAMESPACES(); - - static constexpr Types::renderpass_t::SCreationParams::SColorAttachmentDescription colorAttachments[] = + //! renderpass + + static constexpr IGPURenderpass::SCreationParams::SColorAttachmentDescription colorAttachments[] = { { { { - .format = ColorFboAttachmentFormat, - .samples = Samples, + .format = Traits::ColorFboAttachmentFormat, + .samples = Traits::Samples, .mayAlias = false }, - /* .loadOp = */ Types::renderpass_t::LOAD_OP::CLEAR, - /* .storeOp = */ Types::renderpass_t::STORE_OP::STORE, - /* .initialLayout = */ Types::image_t::LAYOUT::UNDEFINED, - /* .finalLayout = */ Types::image_t::LAYOUT::READ_ONLY_OPTIMAL + /* .loadOp = */ IGPURenderpass::LOAD_OP::CLEAR, + /* .storeOp = */ IGPURenderpass::STORE_OP::STORE, + /* .initialLayout = */ IGPUImage::LAYOUT::UNDEFINED, + /* .finalLayout = */ IGPUImage::LAYOUT::READ_ONLY_OPTIMAL } }, - Types::renderpass_t::SCreationParams::ColorAttachmentsEnd + IGPURenderpass::SCreationParams::ColorAttachmentsEnd }; - static constexpr Types::renderpass_t::SCreationParams::SDepthStencilAttachmentDescription depthAttachments[] = + static constexpr IGPURenderpass::SCreationParams::SDepthStencilAttachmentDescription depthAttachments[] = { { { { - .format = DepthFboAttachmentFormat, - .samples = Samples, + .format = Traits::DepthFboAttachmentFormat, + .samples = Traits::Samples, .mayAlias = false }, - /* .loadOp = */ {Types::renderpass_t::LOAD_OP::CLEAR}, - /* .storeOp = */ {Types::renderpass_t::STORE_OP::STORE}, - /* .initialLayout = */ {Types::image_t::LAYOUT::UNDEFINED}, - /* .finalLayout = */ {Types::image_t::LAYOUT::ATTACHMENT_OPTIMAL} + /* .loadOp = */ {IGPURenderpass::LOAD_OP::CLEAR}, + /* .storeOp = */ {IGPURenderpass::STORE_OP::STORE}, + /* .initialLayout = */ {IGPUImage::LAYOUT::UNDEFINED}, + /* .finalLayout = */ {IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL} } }, - Types::renderpass_t::SCreationParams::DepthStencilAttachmentsEnd + IGPURenderpass::SCreationParams::DepthStencilAttachmentsEnd }; - typename Types::renderpass_t::SCreationParams::SSubpassDescription subpasses[] = + IGPURenderpass::SCreationParams::SSubpassDescription subpasses[] = { {}, - Types::renderpass_t::SCreationParams::SubpassesEnd + IGPURenderpass::SCreationParams::SubpassesEnd }; - subpasses[0].depthStencilAttachment.render = { .attachmentIndex = 0u,.layout = Types::image_t::LAYOUT::ATTACHMENT_OPTIMAL }; - subpasses[0].colorAttachments[0] = { .render = {.attachmentIndex = 0u, .layout = Types::image_t::LAYOUT::ATTACHMENT_OPTIMAL } }; + subpasses[0].depthStencilAttachment.render = { .attachmentIndex = 0u,.layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL }; + subpasses[0].colorAttachments[0] = { .render = {.attachmentIndex = 0u, .layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL } }; - static constexpr Types::renderpass_t::SCreationParams::SSubpassDependency dependencies[] = + static constexpr IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = { // wipe-transition of Color to ATTACHMENT_OPTIMAL { - .srcSubpass = Types::renderpass_t::SCreationParams::SSubpassDependency::External, + .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, .dstSubpass = 0, .memoryBarrier = { @@ -609,7 +205,7 @@ class ResourceBuilder // color from ATTACHMENT_OPTIMAL to PRESENT_SRC { .srcSubpass = 0, - .dstSubpass = Types::renderpass_t::SCreationParams::SSubpassDependency::External, + .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, .memoryBarrier = { // last place where the depth can get modified @@ -624,199 +220,117 @@ class ResourceBuilder } // leave view offsets and flags default }, - Types::renderpass_t::SCreationParams::DependenciesEnd + IGPURenderpass::SCreationParams::DependenciesEnd }; - typename Types::renderpass_t::SCreationParams params = {}; + IGPURenderpass::SCreationParams params = {}; params.colorAttachments = colorAttachments; params.depthStencilAttachments = depthAttachments; params.subpasses = subpasses; params.dependencies = dependencies; - if constexpr (withAssetConverter) - scratch.renderpass = ICPURenderpass::create(params); - else - scratch.renderpass = utilities->getLogicalDevice()->createRenderpass(params); + auto renderpass = device->createRenderpass(params); - if (!scratch.renderpass) + if (!renderpass) { logger->log("Could not create render pass!", ILogger::ELL_ERROR); - return false; + return nullptr; } - return true; - } - - bool createFramebufferAttachments() - { - EXPOSE_NABLA_NAMESPACES(); - - auto createImageView = [&](smart_refctd_ptr& outView) -> smart_refctd_ptr - { - constexpr bool IS_DEPTH = isDepthOrStencilFormat(); - constexpr auto USAGE = [](const bool isDepth) - { - bitflag usage = Types::image_t::EUF_RENDER_ATTACHMENT_BIT; - - if (!isDepth) - usage |= Types::image_t::EUF_SAMPLED_BIT; - - return usage; - }(IS_DEPTH); - constexpr auto ASPECT = IS_DEPTH ? IImage::E_ASPECT_FLAGS::EAF_DEPTH_BIT : IImage::E_ASPECT_FLAGS::EAF_COLOR_BIT; - constexpr std::string_view DEBUG_NAME = IS_DEPTH ? "UI Scene Depth Attachment Image" : "UI Scene Color Attachment Image"; - { - smart_refctd_ptr image; - { - auto params = typename Types::image_t::SCreationParams( - { - .type = Types::image_t::ET_2D, - .samples = Samples, - .format = format, - .extent = { FramebufferW, FramebufferH, 1u }, - .mipLevels = 1u, - .arrayLayers = 1u, - .usage = USAGE - }); - - if constexpr (withAssetConverter) - image = ICPUImage::create(params); - else - image = utilities->getLogicalDevice()->createImage(std::move(params)); - } - - if (!image) - { - logger->log("Could not create image!", ILogger::ELL_ERROR); - return nullptr; - } - - if constexpr (withAssetConverter) - { - auto dummyBuffer = make_smart_refctd_ptr(FramebufferW * FramebufferH * getTexelOrBlockBytesize()); - dummyBuffer->setContentHash(dummyBuffer->computeContentHash()); - - auto regions = make_refctd_dynamic_array>(1u); - auto& region = regions->front(); - - region.imageSubresource = { .aspectMask = ASPECT, .mipLevel = 0u, .baseArrayLayer = 0u, .layerCount = 0u }; - region.bufferOffset = 0u; - region.bufferRowLength = IImageAssetHandlerBase::calcPitchInBlocks(FramebufferW, getTexelOrBlockBytesize()); - region.bufferImageHeight = 0u; - region.imageOffset = { 0u, 0u, 0u }; - region.imageExtent = { FramebufferW, FramebufferH, 1u }; - - if (!image->setBufferAndRegions(std::move(dummyBuffer), regions)) - { - logger->log("Could not set image's regions!", ILogger::ELL_ERROR); - return nullptr; - } - image->setContentHash(image->computeContentHash()); - } - else - { - image->setObjectDebugName(DEBUG_NAME.data()); - - if (!utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) - { - logger->log("Could not allocate memory for an image!", ILogger::ELL_ERROR); - return nullptr; - } - } - - auto params = typename Types::image_view_t::SCreationParams - ({ - .flags = Types::image_view_t::ECF_NONE, - .subUsages = USAGE, - .image = std::move(image), - .viewType = Types::image_view_t::ET_2D, - .format = format, - .subresourceRange = { .aspectMask = ASPECT, .baseMipLevel = 0u, .levelCount = 1u, .baseArrayLayer = 0u, .layerCount = 1u } - }); - - if constexpr (withAssetConverter) - outView = make_smart_refctd_ptr(std::move(params)); - else - outView = utilities->getLogicalDevice()->createImageView(std::move(params)); - - if (!outView) - { - logger->log("Could not create image view!", ILogger::ELL_ERROR); - return nullptr; - } - - return smart_refctd_ptr(outView); - } - }; - - const bool allocated = createImageView.template operator() < ColorFboAttachmentFormat > (scratch.attachments.color) && createImageView.template operator() < DepthFboAttachmentFormat > (scratch.attachments.depth); - - if (!allocated) - { - logger->log("Could not allocate frame buffer's attachments!", ILogger::ELL_ERROR); - return false; - } + //! shaders + + auto createShader = [&](IShader::E_SHADER_STAGE stage, smart_refctd_ptr& outShader) -> smart_refctd_ptr + { + const SBuiltinFile& in = ::geometry::creator::spirv::builtin::get_resource(); + const auto buffer = make_smart_refctd_ptr, true> >(in.size, (void*)in.contents, adopt_memory); + auto shader = make_smart_refctd_ptr(smart_refctd_ptr(buffer), stage, IShader::E_CONTENT_TYPE::ECT_SPIRV, ""); - return true; - } + outShader = device->createShader(shader.get()); - bool createShaders() - { - EXPOSE_NABLA_NAMESPACES(); + return outShader; + }; - auto createShader = [&](IShader::E_SHADER_STAGE stage, smart_refctd_ptr& outShader) -> smart_refctd_ptr + struct GeometriesCpu { - // TODO: use SPIRV loader & our ::system ns to get those cpu shaders, do not create myself (shit I forgot it exists) + enum GeometryShader + { + GP_BASIC = 0, + GP_CONE, + GP_ICO, - const SBuiltinFile& in = ::geometry::creator::spirv::builtin::get_resource(); - const auto buffer = make_smart_refctd_ptr, true> >(in.size, (void*)in.contents, adopt_memory); - auto shader = make_smart_refctd_ptr(smart_refctd_ptr(buffer), stage, IShader::E_CONTENT_TYPE::ECT_SPIRV, ""); // must create cpu instance regardless underlying type + GP_COUNT + }; + + struct ReferenceObjectCpu + { + ObjectMeta meta; + GeometryShader shadersType; + nbl::asset::CGeometryCreator::return_type data; + }; - if constexpr (withAssetConverter) + GeometriesCpu(const nbl::asset::IGeometryCreator* _gc) + : gc(_gc), + objects + ({ + ReferenceObjectCpu {.meta = {.type = OT_CUBE, .name = "Cube Mesh" }, .shadersType = GP_BASIC, .data = gc->createCubeMesh(nbl::core::vector3df(1.f, 1.f, 1.f)) }, + ReferenceObjectCpu {.meta = {.type = OT_SPHERE, .name = "Sphere Mesh" }, .shadersType = GP_BASIC, .data = gc->createSphereMesh(2, 16, 16) }, + ReferenceObjectCpu {.meta = {.type = OT_CYLINDER, .name = "Cylinder Mesh" }, .shadersType = GP_BASIC, .data = gc->createCylinderMesh(2, 2, 20) }, + ReferenceObjectCpu {.meta = {.type = OT_RECTANGLE, .name = "Rectangle Mesh" }, .shadersType = GP_BASIC, .data = gc->createRectangleMesh(nbl::core::vector2df_SIMD(1.5, 3)) }, + ReferenceObjectCpu {.meta = {.type = OT_DISK, .name = "Disk Mesh" }, .shadersType = GP_BASIC, .data = gc->createDiskMesh(2, 30) }, + ReferenceObjectCpu {.meta = {.type = OT_ARROW, .name = "Arrow Mesh" }, .shadersType = GP_BASIC, .data = gc->createArrowMesh() }, + ReferenceObjectCpu {.meta = {.type = OT_CONE, .name = "Cone Mesh" }, .shadersType = GP_CONE, .data = gc->createConeMesh(2, 3, 10) }, + ReferenceObjectCpu {.meta = {.type = OT_ICOSPHERE, .name = "Icoshpere Mesh" }, .shadersType = GP_ICO, .data = gc->createIcoSphere(1, 3, true) } + }) { - buffer->setContentHash(buffer->computeContentHash()); - outShader = std::move(shader); + gc = nullptr; // one shot } - else - outShader = utilities->getLogicalDevice()->createShader(shader.get()); - return outShader; + private: + const nbl::asset::IGeometryCreator* gc; + + public: + const std::array objects; + }; + + struct Shaders + { + nbl::core::smart_refctd_ptr vertex = nullptr, fragment = nullptr; }; - typename ResourcesBundleScratch::Shaders& basic = scratch.shaders[GeometriesCpu::GP_BASIC]; + GeometriesCpu geometries(gc); + std::array shaders; + + auto& basic = shaders[GeometriesCpu::GP_BASIC]; createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("geometryCreator/spirv/gc.basic.vertex.spv") > (IShader::E_SHADER_STAGE::ESS_VERTEX, basic.vertex); createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("geometryCreator/spirv/gc.basic.fragment.spv") > (IShader::E_SHADER_STAGE::ESS_FRAGMENT, basic.fragment); - typename ResourcesBundleScratch::Shaders& cone = scratch.shaders[GeometriesCpu::GP_CONE]; + auto& cone = shaders[GeometriesCpu::GP_CONE]; createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("geometryCreator/spirv/gc.cone.vertex.spv") > (IShader::E_SHADER_STAGE::ESS_VERTEX, cone.vertex); createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("geometryCreator/spirv/gc.basic.fragment.spv") > (IShader::E_SHADER_STAGE::ESS_FRAGMENT, cone.fragment); // note we reuse fragment from basic! - typename ResourcesBundleScratch::Shaders& ico = scratch.shaders[GeometriesCpu::GP_ICO]; + auto& ico = shaders[GeometriesCpu::GP_ICO]; createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("geometryCreator/spirv/gc.ico.vertex.spv") > (IShader::E_SHADER_STAGE::ESS_VERTEX, ico.vertex); createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("geometryCreator/spirv/gc.basic.fragment.spv") > (IShader::E_SHADER_STAGE::ESS_FRAGMENT, ico.fragment); // note we reuse fragment from basic! - - for (const auto& it : scratch.shaders) + + for (const auto& it : shaders) { if (!it.vertex || !it.fragment) { - logger->log("Could not create shaders!", ILogger::ELL_ERROR); - return false; + logger->log("Could not create a shader!", ILogger::ELL_ERROR); + return nullptr; } } - return true; - } + // geometries - bool createGeometries() - { - EXPOSE_NABLA_NAMESPACES(); + auto output = make_smart_refctd_ptr(); + output->renderpass = smart_refctd_ptr(renderpass); + output->dsLayout = smart_refctd_ptr(dsLayout); for (uint32_t i = 0; i < geometries.objects.size(); ++i) { const auto& inGeometry = geometries.objects[i]; - auto& [obj, meta] = scratch.objects[i]; - - bool status = true; + auto& [obj, meta] = output->objects[i]; meta.name = inGeometry.meta.name; meta.type = inGeometry.meta.type; @@ -825,7 +339,7 @@ class ResourceBuilder { SBlendParams blend; SRasterizationParams rasterization; - typename Types::graphics_pipeline_t::SCreationParams pipeline; + IGPUGraphicsPipeline::SCreationParams pipeline; } params; { @@ -843,35 +357,27 @@ class ResourceBuilder params.rasterization.faceCullingMode = EFCM_NONE; { - const typename Types::shader_t::SSpecInfo info [] = + const IGPUShader::SSpecInfo sInfo [] = { - {.entryPoint = "VSMain", .shader = scratch.shaders[inGeometry.shadersType].vertex.get() }, - {.entryPoint = "PSMain", .shader = scratch.shaders[inGeometry.shadersType].fragment.get() } + {.entryPoint = "VSMain", .shader = shaders[inGeometry.shadersType].vertex.get() }, + {.entryPoint = "PSMain", .shader = shaders[inGeometry.shadersType].fragment.get() } }; - params.pipeline.layout = scratch.pipelineLayout.get(); - params.pipeline.shaders = info; - params.pipeline.renderpass = scratch.renderpass.get(); + params.pipeline.layout = pipelineLayout.get(); + params.pipeline.shaders = sInfo; + params.pipeline.renderpass = renderpass.get(); params.pipeline.cached = { .vertexInput = inGeometry.data.inputParams, .primitiveAssembly = inGeometry.data.assemblyParams, .rasterization = params.rasterization, .blend = params.blend, .subpassIx = 0u }; obj.indexCount = inGeometry.data.indexCount; obj.indexType = inGeometry.data.indexType; - // TODO: cache pipeline & try lookup for existing one first maybe - - // similar issue like with shaders again, in this case gpu contructor allows for extra cache parameters + there is no constructor you can use to fire make_smart_refctd_ptr yourself for cpu - if constexpr (withAssetConverter) - obj.pipeline = ICPUGraphicsPipeline::create(params.pipeline); - else - { - const std::array info = { { params.pipeline } }; - utilities->getLogicalDevice()->createGraphicsPipelines(nullptr, info, &obj.pipeline); - } + const std::array pInfo = { { params.pipeline } }; + device->createGraphicsPipelines(nullptr, pInfo, &obj.pipeline); if (!obj.pipeline) { logger->log("Could not create graphics pipeline for [%s] object!", ILogger::ELL_ERROR, meta.name.data()); - status = false; + return nullptr; } // object buffers @@ -888,71 +394,48 @@ class ResourceBuilder constexpr static auto INDEX_USAGE = bitflag(ibuffer_t::EUF_INDEX_BUFFER_BIT) | ibuffer_t::EUF_VERTEX_BUFFER_BIT | ibuffer_t::EUF_TRANSFER_DST_BIT | ibuffer_t::EUF_INLINE_UPDATE_VIA_CMDBUF; obj.bindings.index.offset = 0u; - if constexpr (withAssetConverter) - { - if (!vBuffer) - return false; - - vBuffer->addUsageFlags(VERTEX_USAGE); - vBuffer->setContentHash(vBuffer->computeContentHash()); - obj.bindings.vertex = { .offset = 0u, .buffer = vBuffer }; - - if (inGeometry.data.indexType != EIT_UNKNOWN) - if (iBuffer) - { - iBuffer->addUsageFlags(INDEX_USAGE); - iBuffer->setContentHash(iBuffer->computeContentHash()); - } - else - return false; + auto vertexBuffer = device->createBuffer(IGPUBuffer::SCreationParams({ .size = vBuffer->getSize(), .usage = VERTEX_USAGE })); + auto indexBuffer = iBuffer ? device->createBuffer(IGPUBuffer::SCreationParams({ .size = iBuffer->getSize(), .usage = INDEX_USAGE })) : nullptr; - obj.bindings.index = { .offset = 0u, .buffer = iBuffer }; - } - else - { - auto vertexBuffer = utilities->getLogicalDevice()->createBuffer(IGPUBuffer::SCreationParams({ .size = vBuffer->getSize(), .usage = VERTEX_USAGE })); - auto indexBuffer = iBuffer ? utilities->getLogicalDevice()->createBuffer(IGPUBuffer::SCreationParams({ .size = iBuffer->getSize(), .usage = INDEX_USAGE })) : nullptr; + if (!vertexBuffer) + return false; - if (!vertexBuffer) + if (inGeometry.data.indexType != EIT_UNKNOWN) + if (!indexBuffer) return false; - if (inGeometry.data.indexType != EIT_UNKNOWN) - if (!indexBuffer) - return false; - - const auto mask = utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - for (auto it : { vertexBuffer , indexBuffer }) + const auto mask = device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + for (auto it : { vertexBuffer , indexBuffer }) + { + if (it) { - if (it) - { - auto reqs = it->getMemoryReqs(); - reqs.memoryTypeBits &= mask; + auto reqs = it->getMemoryReqs(); + reqs.memoryTypeBits &= mask; - utilities->getLogicalDevice()->allocate(reqs, it.get()); - } + device->allocate(reqs, it.get()); } + } - // record transfer uploads - obj.bindings.vertex = { .offset = 0u, .buffer = std::move(vertexBuffer) }; + // record transfer uploads + obj.bindings.vertex = { .offset = 0u, .buffer = std::move(vertexBuffer) }; + { + const SBufferRange range = { .offset = obj.bindings.vertex.offset, .size = obj.bindings.vertex.buffer->getSize(), .buffer = obj.bindings.vertex.buffer }; + if (!cmd->updateBuffer(range, vBuffer->getPointer())) { - const SBufferRange range = { .offset = obj.bindings.vertex.offset, .size = obj.bindings.vertex.buffer->getSize(), .buffer = obj.bindings.vertex.buffer }; - if (!commandBuffer->updateBuffer(range, vBuffer->getPointer())) - { - logger->log("Could not record vertex buffer transfer upload for [%s] object!", ILogger::ELL_ERROR, meta.name.data()); - status = false; - } + logger->log("Could not record vertex buffer transfer upload for [%s] object!", ILogger::ELL_ERROR, meta.name.data()); + return false; } - obj.bindings.index = { .offset = 0u, .buffer = std::move(indexBuffer) }; + } + obj.bindings.index = { .offset = 0u, .buffer = std::move(indexBuffer) }; + { + if (iBuffer) { - if (iBuffer) - { - const SBufferRange range = { .offset = obj.bindings.index.offset, .size = obj.bindings.index.buffer->getSize(), .buffer = obj.bindings.index.buffer }; + const SBufferRange range = { .offset = obj.bindings.index.offset, .size = obj.bindings.index.buffer->getSize(), .buffer = obj.bindings.index.buffer }; - if (!commandBuffer->updateBuffer(range, iBuffer->getPointer())) - { - logger->log("Could not record index buffer transfer upload for [%s] object!", ILogger::ELL_ERROR, meta.name.data()); - status = false; - } + if (!cmd->updateBuffer(range, iBuffer->getPointer())) + { + logger->log("Could not record index buffer transfer upload for [%s] object!", ILogger::ELL_ERROR, meta.name.data()); + return false; } } } @@ -963,209 +446,289 @@ class ResourceBuilder if (!createVIBuffers()) { logger->log("Could not create buffers for [%s] object!", ILogger::ELL_ERROR, meta.name.data()); - status = false; + return nullptr; } + } + } - if (!status) - { - logger->log("[%s] object will not be created!", ILogger::ELL_ERROR, meta.name.data()); + cmd->end(); + + // submit + { + std::array commandBuffers = {}; + { + commandBuffers.front().cmdbuf = cmd.get(); + } - obj.bindings.vertex = {}; - obj.bindings.index = {}; - obj.indexCount = 0u; - obj.indexType = E_INDEX_TYPE::EIT_UNKNOWN; - obj.pipeline = nullptr; + auto completed = device->createSemaphore(0u); + + std::array signals; + { + auto& signal = signals.front(); + signal.value = 1; + signal.stageMask = bitflag(PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS); + signal.semaphore = completed.get(); + } - continue; + const IQueue::SSubmitInfo infos[] = + { + { + .waitSemaphores = {}, + .commandBuffers = commandBuffers, + .signalSemaphores = signals } + }; + + if (transferCapableQueue->submit(infos) != IQueue::RESULT::SUCCESS) + { + logger->log("Failed to submit transfer upload operations!", ILogger::ELL_ERROR); + return nullptr; } + + const ISemaphore::SWaitInfo info[] = + { { + .semaphore = completed.get(), + .value = 1 + } }; + + device->blockForSemaphores(info); } - return true; + return output; } +}; + +struct ObjectInstance +{ + nbl::hlsl::float32_t3x4 model = nbl::hlsl::float32_t3x4(1.f); + nbl::asset::SBasicViewParameters viewParameters; + ObjectMeta meta; +}; - bool createViewParametersUboBuffer() +class CScene final : public nbl::core::IReferenceCounted +{ +public: + ObjectInstance object; // optional TODO: MDI, allow for multiple objects on the scene -> read (*) bellow at private class members + + struct + { + static constexpr uint32_t startedValue = 0, finishedValue = 0x45; + nbl::core::smart_refctd_ptr progress; + } semaphore; + + static inline nbl::core::smart_refctd_ptr create(nbl::core::smart_refctd_ptr device, nbl::core::smart_refctd_ptr logger, nbl::video::CThreadSafeQueueAdapter* const transferCapableQueue, const nbl::core::smart_refctd_ptr resources) { EXPOSE_NABLA_NAMESPACES(); - using ibuffer_t = ::nbl::asset::IBuffer; // seems to be ambigous, both asset & core namespaces has IBuffer - constexpr static auto UboUsage = bitflag(ibuffer_t::EUF_UNIFORM_BUFFER_BIT) | ibuffer_t::EUF_TRANSFER_DST_BIT | ibuffer_t::EUF_INLINE_UPDATE_VIA_CMDBUF; + if (!device) + return nullptr; - if constexpr (withAssetConverter) - { - auto uboBuffer = make_smart_refctd_ptr(sizeof(SBasicViewParameters)); - uboBuffer->addUsageFlags(UboUsage); - uboBuffer->setContentHash(uboBuffer->computeContentHash()); - scratch.ubo = { .offset = 0u, .buffer = std::move(uboBuffer) }; - } - else - { - const auto mask = utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto uboBuffer = utilities->getLogicalDevice()->createBuffer(IGPUBuffer::SCreationParams({ .size = sizeof(SBasicViewParameters), .usage = UboUsage })); + if (!logger) + return nullptr; - if (!uboBuffer) - return false; + if (!transferCapableQueue) + return nullptr; - for (auto it : { uboBuffer }) - { - IDeviceMemoryBacked::SDeviceMemoryRequirements reqs = it->getMemoryReqs(); - reqs.memoryTypeBits &= mask; + if (!resources) + return nullptr; - utilities->getLogicalDevice()->allocate(reqs, it.get()); - } + // cmd - scratch.ubo = { .offset = 0u, .buffer = std::move(uboBuffer) }; + auto cPool = device->createCommandPool(transferCapableQueue->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + + if (!cPool) + { + logger->log("Couldn't create command pool!", ILogger::ELL_ERROR); + return nullptr; } - return true; - } + nbl::core::smart_refctd_ptr cmd; - struct GeometriesCpu - { - enum GeometryShader + if (!cPool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { &cmd , 1 })) { - GP_BASIC = 0, - GP_CONE, - GP_ICO, + logger->log("Couldn't create command buffer!", ILogger::ELL_ERROR); + return nullptr; + } - GP_COUNT - }; + if (!cmd) + return nullptr; - struct ReferenceObjectCpu - { - ObjectMeta meta; - GeometryShader shadersType; - nbl::asset::CGeometryCreator::return_type data; - }; + // UBO with basic view parameters + + using ibuffer_t = ::nbl::asset::IBuffer; + constexpr static auto UboUsage = bitflag(ibuffer_t::EUF_UNIFORM_BUFFER_BIT) | ibuffer_t::EUF_TRANSFER_DST_BIT | ibuffer_t::EUF_INLINE_UPDATE_VIA_CMDBUF; + + const auto mask = device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + auto uboBuffer = device->createBuffer(IGPUBuffer::SCreationParams({ .size = sizeof(SBasicViewParameters), .usage = UboUsage })); + + if (!uboBuffer) + logger->log("Could not create UBO!", ILogger::ELL_ERROR); - GeometriesCpu(const nbl::asset::IGeometryCreator* _gc) - : gc(_gc), - objects - ({ - ReferenceObjectCpu {.meta = {.type = OT_CUBE, .name = "Cube Mesh" }, .shadersType = GP_BASIC, .data = gc->createCubeMesh(nbl::core::vector3df(1.f, 1.f, 1.f)) }, - ReferenceObjectCpu {.meta = {.type = OT_SPHERE, .name = "Sphere Mesh" }, .shadersType = GP_BASIC, .data = gc->createSphereMesh(2, 16, 16) }, - ReferenceObjectCpu {.meta = {.type = OT_CYLINDER, .name = "Cylinder Mesh" }, .shadersType = GP_BASIC, .data = gc->createCylinderMesh(2, 2, 20) }, - ReferenceObjectCpu {.meta = {.type = OT_RECTANGLE, .name = "Rectangle Mesh" }, .shadersType = GP_BASIC, .data = gc->createRectangleMesh(nbl::core::vector2df_SIMD(1.5, 3)) }, - ReferenceObjectCpu {.meta = {.type = OT_DISK, .name = "Disk Mesh" }, .shadersType = GP_BASIC, .data = gc->createDiskMesh(2, 30) }, - ReferenceObjectCpu {.meta = {.type = OT_ARROW, .name = "Arrow Mesh" }, .shadersType = GP_BASIC, .data = gc->createArrowMesh() }, - ReferenceObjectCpu {.meta = {.type = OT_CONE, .name = "Cone Mesh" }, .shadersType = GP_CONE, .data = gc->createConeMesh(2, 3, 10) }, - ReferenceObjectCpu {.meta = {.type = OT_ICOSPHERE, .name = "Icoshpere Mesh" }, .shadersType = GP_ICO, .data = gc->createIcoSphere(1, 3, true) } - }) + for (auto it : { uboBuffer }) { - gc = nullptr; // one shot + IDeviceMemoryBacked::SDeviceMemoryRequirements reqs = it->getMemoryReqs(); + reqs.memoryTypeBits &= mask; + + device->allocate(reqs, it.get()); } - private: - const nbl::asset::IGeometryCreator* gc; + nbl::asset::SBufferBinding ubo = { .offset = 0u, .buffer = std::move(uboBuffer) }; - public: - const std::array objects; - }; + // descriptor set for the resource + + const IGPUDescriptorSetLayout* const layouts[] = { resources->dsLayout.get() }; + const uint32_t setCounts[] = { 1u }; - using resources_bundle_base_t = ResourcesBundleBase; + // note descriptor set has back smart pointer to its pool, so we dont need to keep it explicitly + auto dPool = device->createDescriptorPoolForDSLayouts(IDescriptorPool::E_CREATE_FLAGS::ECF_NONE, layouts, setCounts); - struct ResourcesBundleScratch : public resources_bundle_base_t - { - using Types = resources_bundle_base_t::Types; + if (!dPool) + { + logger->log("Could not create Descriptor Pool!", ILogger::ELL_ERROR); + return nullptr; + } - ResourcesBundleScratch() - : resources_bundle_base_t() {} + nbl::core::smart_refctd_ptr ds; + dPool->createDescriptorSets(layouts, &ds); - struct Shaders + if (!ds) { - nbl::core::smart_refctd_ptr vertex = nullptr, fragment = nullptr; - }; + logger->log("Could not create Descriptor Set!", ILogger::ELL_ERROR); + return nullptr; + } - nbl::core::smart_refctd_ptr descriptorSetLayout; - nbl::core::smart_refctd_ptr pipelineLayout; - std::array shaders; - }; + // write the descriptor set + { + // descriptor write ubo + IGPUDescriptorSet::SWriteDescriptorSet write; + write.dstSet = ds.get(); + write.binding = 0; + write.arrayElement = 0u; + write.count = 1u; - // TODO: we could make those params templated with default values like below - static constexpr auto FramebufferW = 1280u, FramebufferH = 720u; - static constexpr auto ColorFboAttachmentFormat = nbl::asset::EF_R8G8B8A8_SRGB, DepthFboAttachmentFormat = nbl::asset::EF_D16_UNORM; - static constexpr auto Samples = nbl::video::IGPUImage::ESCF_1_BIT; + IGPUDescriptorSet::SDescriptorInfo info; + { + info.desc = smart_refctd_ptr(ubo.buffer); + info.info.buffer.offset = ubo.offset; + info.info.buffer.size = ubo.buffer->getSize(); + } - ResourcesBundleScratch scratch; + write.info = &info; - nbl::video::IUtilities* const utilities; - nbl::video::IGPUCommandBuffer* const commandBuffer; - nbl::system::ILogger* const logger; - GeometriesCpu geometries; -}; + if (!device->updateDescriptorSets(1u, &write, 0u, nullptr)) + { + logger->log("Could not write descriptor set!", ILogger::ELL_ERROR); + return nullptr; + } + } -#undef TYPES_IMPL_BOILERPLATE + // color & depth attachments + + auto createImageView = [&](smart_refctd_ptr&outView) -> smart_refctd_ptr + { + constexpr bool IS_DEPTH = isDepthOrStencilFormat(); + constexpr auto USAGE = [](const bool isDepth) + { + bitflag usage = IGPUImage::EUF_RENDER_ATTACHMENT_BIT; -struct ObjectDrawHookCpu -{ - nbl::hlsl::float32_t3x4 model = nbl::hlsl::float32_t3x4(1.f); - nbl::asset::SBasicViewParameters viewParameters; - ObjectMeta meta; -}; + if (!isDepth) + usage |= IGPUImage::EUF_SAMPLED_BIT; -/* - Rendering to offline framebuffer which we don't present, color - scene attachment texture we use for second UI renderpass - sampling it & rendering into desired GUI area. + return usage; + }(IS_DEPTH); + constexpr auto ASPECT = IS_DEPTH ? IImage::E_ASPECT_FLAGS::EAF_DEPTH_BIT : IImage::E_ASPECT_FLAGS::EAF_COLOR_BIT; + constexpr std::string_view DEBUG_NAME = IS_DEPTH ? "GC Scene Depth Attachment Image" : "GC Scene Color Attachment Image"; + { + smart_refctd_ptr image; + { + auto params = IGPUImage::SCreationParams( + { + .type = IGPUImage::ET_2D, + .samples = Traits::Samples, + .format = format, + .extent = { Traits::FramebufferW, Traits::FramebufferH, 1u }, + .mipLevels = 1u, + .arrayLayers = 1u, + .usage = USAGE + }); + + image = device->createImage(std::move(params)); + } - The scene can be created from simple geometry - using our Geomtry Creator class. -*/ + if (!image) + { + logger->log("Could not create image!", ILogger::ELL_ERROR); + } -class CScene final : public nbl::core::IReferenceCounted -{ -public: - ObjectDrawHookCpu object; // TODO: this could be a vector (to not complicate the example I leave it single object), we would need a better system for drawing then to make only 1 max 2 indirect draw calls (indexed and not indexed objects) + image->setObjectDebugName(DEBUG_NAME.data()); - struct - { - const uint32_t startedValue = 0, finishedValue = 0x45; - nbl::core::smart_refctd_ptr progress; - } semaphore; + if (!device->allocate(image->getMemoryReqs(), image.get()).isValid()) + { + logger->log("Could not allocate memory for an image!", ILogger::ELL_ERROR); + return nullptr; + } - struct CreateResourcesDirectlyWithDevice { using Builder = ResourceBuilder; }; - struct CreateResourcesWithAssetConverter { using Builder = ResourceBuilder; }; + auto params = IGPUImageView::SCreationParams + ({ + .flags = IGPUImageView::ECF_NONE, + .subUsages = USAGE, + .image = std::move(image), + .viewType = IGPUImageView::ET_2D, + .format = format, + .subresourceRange = {.aspectMask = ASPECT, .baseMipLevel = 0u, .levelCount = 1u, .baseArrayLayer = 0u, .layerCount = 1u } + }); - ~CScene() {} + outView = device->createImageView(std::move(params)); - static inline nbl::core::smart_refctd_ptr createCommandBuffer(nbl::video::ILogicalDevice* const device, nbl::system::ILogger* const logger, const uint32_t familyIx) - { - EXPOSE_NABLA_NAMESPACES(); - auto pool = device->createCommandPool(familyIx, IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + if (!outView) + { + logger->log("Could not create image view!", ILogger::ELL_ERROR); + return nullptr; + } - if (!pool) - { - logger->log("Couldn't create Command Pool!", ILogger::ELL_ERROR); - return nullptr; - } + return smart_refctd_ptr(outView); + } + }; - nbl::core::smart_refctd_ptr cmd; + nbl::core::smart_refctd_ptr color, depth; + const bool allocated = createImageView.template operator() < Traits::ColorFboAttachmentFormat > (color) && createImageView.template operator() < Traits::DepthFboAttachmentFormat > (depth); - if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { &cmd , 1 })) + if (!allocated) { - logger->log("Couldn't create Command Buffer!", ILogger::ELL_ERROR); + logger->log("Could not allocate frame buffer's attachments!", ILogger::ELL_ERROR); return nullptr; } - return cmd; - } + //! frame buffer + + const auto extent = color->getCreationParameters().image->getCreationParameters().extent; - template - static auto create(Args&&... args) -> decltype(auto) - { - EXPOSE_NABLA_NAMESPACES(); + IGPUFramebuffer::SCreationParams params = + { + { + .renderpass = smart_refctd_ptr((IGPURenderpass*)resources->renderpass.get()), // NOTE: those creation params are to be corrected & this should take immutable renderpass (smart pointer OK but take const for type) + .depthStencilAttachments = &depth.get(), + .colorAttachments = &color.get(), + .width = extent.width, + .height = extent.height, + .layers = 1u + } + }; - /* - user should call the constructor's args without last argument explicitly, this is a trick to make constructor templated, - eg.create(smart_refctd_ptr(device), smart_refctd_ptr(logger), queuePointer, geometryPointer) - */ + auto frameBuffer = device->createFramebuffer(std::move(params)); - auto* scene = new CScene(std::forward(args)..., CreateWith {}); - smart_refctd_ptr smart(scene, dont_grab); + if (!frameBuffer) + { + logger->log("Could not create frame buffer!", ILogger::ELL_ERROR); + return nullptr; + } - return smart; + auto output = new CScene(smart_refctd_ptr(device), smart_refctd_ptr(logger), smart_refctd_ptr(cmd), smart_refctd_ptr(frameBuffer), smart_refctd_ptr(ds), ubo, smart_refctd_ptr(color), smart_refctd_ptr(depth), smart_refctd_ptr(resources)); + return smart_refctd_ptr(output); } + ~CScene() {} + inline void begin() { EXPOSE_NABLA_NAMESPACES(); @@ -1174,13 +737,15 @@ class CScene final : public nbl::core::IReferenceCounted m_commandBuffer->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); m_commandBuffer->beginDebugMarker("UISampleApp Offline Scene Frame"); - semaphore.progress = m_utilities->getLogicalDevice()->createSemaphore(semaphore.startedValue); + semaphore.progress = m_device->createSemaphore(semaphore.startedValue); } - inline void record() + inline bool record() { EXPOSE_NABLA_NAMESPACES(); + bool valid = true; + const struct { const uint32_t width, height; @@ -1196,12 +761,13 @@ class CScene final : public nbl::core::IReferenceCounted viewport.height = fbo.height; } - m_commandBuffer->setViewport(0u, 1u, &viewport); + valid &= m_commandBuffer->setViewport(0u, 1u, &viewport); VkRect2D scissor = {}; scissor.offset = { 0, 0 }; scissor.extent = { fbo.width, fbo.height }; - m_commandBuffer->setScissor(0u, 1u, &scissor); + + valid &= m_commandBuffer->setScissor(0u, 1u, &scissor); const VkRect2D renderArea = { @@ -1212,31 +778,33 @@ class CScene final : public nbl::core::IReferenceCounted const IGPUCommandBuffer::SRenderpassBeginInfo info = { .framebuffer = m_frameBuffer.get(), - .colorClearValues = &clear.color, - .depthStencilClearValues = &clear.depth, + .colorClearValues = &Traits::clearColor, + .depthStencilClearValues = &Traits::clearDepth, .renderArea = renderArea }; - m_commandBuffer->beginRenderPass(info, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); + valid &= m_commandBuffer->beginRenderPass(info, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); - const auto& [hook, meta] = resources.objects[object.meta.type]; - auto* rawPipeline = hook.pipeline.get(); + const auto& [hook, meta] = m_resources->objects[object.meta.type]; + const auto* rawPipeline = hook.pipeline.get(); SBufferBinding vertex = hook.bindings.vertex, index = hook.bindings.index; - m_commandBuffer->bindGraphicsPipeline(rawPipeline); - m_commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 1, 1, &resources.descriptorSet.get()); - m_commandBuffer->bindVertexBuffers(0, 1, &vertex); + valid &= m_commandBuffer->bindGraphicsPipeline(rawPipeline); + valid &= m_commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 1, 1, &m_ds.get()); + valid &= m_commandBuffer->bindVertexBuffers(0, 1, &vertex); if (index.buffer && hook.indexType != EIT_UNKNOWN) { - m_commandBuffer->bindIndexBuffer(index, hook.indexType); - m_commandBuffer->drawIndexed(hook.indexCount, 1, 0, 0, 0); + valid &= m_commandBuffer->bindIndexBuffer(index, hook.indexType); + valid &= m_commandBuffer->drawIndexed(hook.indexCount, 1, 0, 0, 0); } else - m_commandBuffer->draw(hook.indexCount, 1, 0, 0); + valid &= m_commandBuffer->draw(hook.indexCount, 1, 0, 0); + + valid &= m_commandBuffer->endRenderPass(); - m_commandBuffer->endRenderPass(); + return valid; } inline void end() @@ -1244,10 +812,13 @@ class CScene final : public nbl::core::IReferenceCounted m_commandBuffer->end(); } - inline bool submit() + inline bool submit(nbl::video::CThreadSafeQueueAdapter* queue) { EXPOSE_NABLA_NAMESPACES(); + if (!queue) + return false; + const IQueue::SSubmitInfo::SCommandBufferInfo buffers[] = { { .cmdbuf = m_commandBuffer.get() } @@ -1267,78 +838,40 @@ class CScene final : public nbl::core::IReferenceCounted return queue->submit(infos) == IQueue::RESULT::SUCCESS; } - // note: must be updated outside render pass inline void update() { EXPOSE_NABLA_NAMESPACES(); SBufferRange range; - range.buffer = smart_refctd_ptr(resources.ubo.buffer); - range.size = resources.ubo.buffer->getSize(); + range.buffer = smart_refctd_ptr(m_ubo.buffer); + range.size = m_ubo.buffer->getSize(); m_commandBuffer->updateBuffer(range, &object.viewParameters); } - inline decltype(auto) getResources() - { - return (resources); // note: do not remove "()" - it makes the return type lvalue reference instead of copy - } + inline auto getColorAttachment() { return nbl::core::smart_refctd_ptr(m_color); } private: - template // TODO: enforce constraints, only those 2 above are valid - CScene(nbl::core::smart_refctd_ptr _utilities, nbl::core::smart_refctd_ptr _logger, nbl::video::CThreadSafeQueueAdapter* _graphicsQueue, const nbl::asset::IGeometryCreator* _geometryCreator, CreateWith createWith = {}) - : m_utilities(nbl::core::smart_refctd_ptr(_utilities)), m_logger(nbl::core::smart_refctd_ptr(_logger)), queue(_graphicsQueue) - { - EXPOSE_NABLA_NAMESPACES(); - using Builder = typename CreateWith::Builder; - - m_commandBuffer = createCommandBuffer(m_utilities->getLogicalDevice(), m_utilities->getLogger(), queue->getFamilyIndex()); - Builder builder(m_utilities.get(), m_commandBuffer.get(), m_logger.get(), _geometryCreator); - - // gpu resources - if (builder.build()) - { - if (!builder.finalize(resources, queue)) - m_logger->log("Could not finalize resource objects to gpu objects!", ILogger::ELL_ERROR); - } - else - m_logger->log("Could not build resource objects!", ILogger::ELL_ERROR); - - // frame buffer - { - const auto extent = resources.attachments.color->getCreationParameters().image->getCreationParameters().extent; - - IGPUFramebuffer::SCreationParams params = - { - { - .renderpass = smart_refctd_ptr(resources.renderpass), - .depthStencilAttachments = &resources.attachments.depth.get(), - .colorAttachments = &resources.attachments.color.get(), - .width = extent.width, - .height = extent.height, - .layers = 1u - } - }; - - m_frameBuffer = m_utilities->getLogicalDevice()->createFramebuffer(std::move(params)); - - if (!m_frameBuffer) - { - m_logger->log("Could not create frame buffer!", ILogger::ELL_ERROR); - return; - } - } - } + CScene(nbl::core::smart_refctd_ptr device, nbl::core::smart_refctd_ptr logger, nbl::core::smart_refctd_ptr commandBuffer, nbl::core::smart_refctd_ptr frameBuffer, nbl::core::smart_refctd_ptr ds, nbl::asset::SBufferBinding ubo, nbl::core::smart_refctd_ptr color, nbl::core::smart_refctd_ptr depth, const nbl::core::smart_refctd_ptr resources) + : m_device(nbl::core::smart_refctd_ptr(device)), m_logger(nbl::core::smart_refctd_ptr(logger)), m_commandBuffer(nbl::core::smart_refctd_ptr(commandBuffer)), m_frameBuffer(nbl::core::smart_refctd_ptr(frameBuffer)), m_ds(nbl::core::smart_refctd_ptr(ds)), m_ubo(ubo), m_color(nbl::core::smart_refctd_ptr(color)), m_depth(nbl::core::smart_refctd_ptr(depth)), m_resources(nbl::core::smart_refctd_ptr(resources)) {} - nbl::core::smart_refctd_ptr m_utilities; + nbl::core::smart_refctd_ptr m_device; nbl::core::smart_refctd_ptr m_logger; - nbl::video::CThreadSafeQueueAdapter* queue; - nbl::core::smart_refctd_ptr m_commandBuffer; + //! (*) I still make an assumption we have only one object on the scene, + //! I'm not going to make it ext-like and go with MDI + streaming buffer now, + //! but I want it to be easy to spam multiple instances of this class to have many + //! frame buffers we can render too given resource geometry buffers with Traits constraints + //! + //! optional TODO: make it ImGUI-ext-like -> renderpass as creation input, ST buffer, MDI, ds outside - nbl::core::smart_refctd_ptr m_frameBuffer; + nbl::core::smart_refctd_ptr m_commandBuffer = nullptr; + nbl::core::smart_refctd_ptr m_frameBuffer = nullptr; + nbl::core::smart_refctd_ptr m_ds = nullptr; + nbl::asset::SBufferBinding m_ubo = {}; + nbl::core::smart_refctd_ptr m_color = nullptr, m_depth = nullptr; - ResourcesBundle resources; + const nbl::core::smart_refctd_ptr m_resources; }; } // nbl::scene::geometrycreator diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index 7488a2af6..db5cf969d 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -11,10 +11,10 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { template -class ICamera : public IGimbalManipulateEncoder, virtual public core::IReferenceCounted +class ICamera : public IGimbalController, virtual public core::IReferenceCounted { public: - using IGimbalManipulateEncoder::IGimbalManipulateEncoder; + using IGimbalController::IGimbalController; using precision_t = T; //! Manipulation mode for virtual events diff --git a/common/include/camera/CCameraController.hpp b/common/include/camera/CCameraController.hpp deleted file mode 100644 index c858c00ee..000000000 --- a/common/include/camera/CCameraController.hpp +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef _NBL_C_CAMERA_CONTROLLER_HPP_ -#define _NBL_C_CAMERA_CONTROLLER_HPP_ - -#include "IGimbalController.hpp" -#include "CGeneralPurposeGimbal.hpp" -#include "ICamera.hpp" - -// TODO: DIFFERENT NAMESPACE -namespace nbl::hlsl -{ - //! Controls any type of camera with available controllers using virtual gimbal events in Local mode - template - class CCameraController final : public IGimbalController - { - public: - using IGimbalController::IGimbalController; - using precision_t = T; - - using interface_camera_t = ICamera; - using interface_gimbal_t = IGimbal; - - CCameraController(core::smart_refctd_ptr camera) - : m_camera(std::move(camera)), m_target(interface_gimbal_t::SCreationParameters({ .position = m_camera->getGimbal().getWorldTarget() })) {} - ~CCameraController() {} - - const keyboard_to_virtual_events_t& getKeyboardMappingPreset() const override { return m_camera->getKeyboardMappingPreset(); } - const mouse_to_virtual_events_t& getMouseMappingPreset() const override { return m_camera->getMouseMappingPreset(); } - const imguizmo_to_virtual_events_t& getImguizmoMappingPreset() const override { return m_camera->getImguizmoMappingPreset(); } - - //! Manipulate the camera view gimbal by requesting a manipulation to its world target represented by a target gimbal, - //! on success it may change both the camera view gimbal & target gimbal - bool manipulateTargetGimbal(SUpdateParameters parameters, std::chrono::microseconds nextPresentationTimestamp) - { - // TODO & note to self: - // thats a little bit tricky -> a request of m_target manipulation which represents camera world target is a step where we consider only change of its - // position and translate that onto virtual orientation events we fire camera->manipulate with. Note that *we can fail* the manipulation because each - // camera type has some constraints on how it works right, however.. if any manipulation happens it means "target vector" changes and it doesn't matter - // what camera type is bound to the camera controller! If so, on success we can simply update m_target gimbal accordingly and represent it nicely on the - // screen with gizmo (as we do for camera view gimbal in Drag & Drop mode) or whatever, otherwise we do nothing because it means we failed the gimbal view - // manipulation hence "target vector" did not change (its really the orientation which changes right, but an orientation change means target vector changes) - - // and whats nice is we can do it with ANY controller now - } - - //! Manipulate the camera view gimbal directly, - //! on success it may change both the camera view gimbal & target gimbal - bool manipulateViewGimbal(SUpdateParameters parameters, std::chrono::microseconds nextPresentationTimestamp) - { - // TODO & note to self: - // and here there is a small difference because we don't map any target gimbal position change to virtual orientation events to request camera view - // gimbal manipulation but we directly try to manipulate the view gimbal of our camera and if we success then we simply update our m_target gimbal accordingly - - // and whats nice is we can do it with ANY controller now - - std::vector virtualEvents(0x45); - uint32_t vCount; - - beginInputProcessing(nextPresentationTimestamp); - { - process(nullptr, vCount); - - if (virtualEvents.size() < vCount) - virtualEvents.resize(vCount); - - process(virtualEvents.data(), vCount, parameters); - } - endInputProcessing(); - - bool manipulated = m_camera->manipulate({ virtualEvents.data(), vCount }, interface_camera_t::Local); - - if (manipulated) - { - // TODO: *any* manipulate success? -> update m_target - } - - return manipulated; - } - - inline const interface_camera_t* getCamera() { return m_camera.get(); } - - private: - core::smart_refctd_ptr m_camera; - - //! Represents the camera world target vector as gimbal we can manipulate - CGeneralPurposeGimbal m_target; - }; - -} // nbl::hlsl namespace - -#endif // _NBL_C_CAMERA_CONTROLLER_HPP_ diff --git a/common/include/camera/IGimbalController.hpp b/common/include/camera/IGimbalController.hpp index a1201a7c1..1aae6617b 100644 --- a/common/include/camera/IGimbalController.hpp +++ b/common/include/camera/IGimbalController.hpp @@ -86,7 +86,7 @@ struct IGimbalManipulateEncoder virtual const imguizmo_to_virtual_events_t& getImguizmoMappingPreset() const = 0u; }; -class IGimbalController : public IGimbalManipulateEncoder, virtual public core::IReferenceCounted +class IGimbalController : public IGimbalManipulateEncoder { public: using IGimbalManipulateEncoder::IGimbalManipulateEncoder; From d6db4a7f0e10535d44e925e7237a6d4fa56cc740 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 18 Nov 2024 16:34:37 +0100 Subject: [PATCH 39/84] display second gizmo on the scene (and make it capable of manipulations) , prepare for imguizmo gimbal controller which I will use to manipulate secondary camera from the first camera's perspective --- 61_UI/main.cpp | 358 +++++++++++++++++++++++++------------------------ 1 file changed, 184 insertions(+), 174 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 7ee57ca3a..91576820b 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -469,8 +469,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication TESTS, TODO: remove all once finished work & integrate with the example properly */ - transformParams.aspectRatio = float(m_window->getWidth()) / float(m_window->getHeight()); - transformParams.invAspectRatio = float(m_window->getHeight()) / float(m_window->getWidth()); + aspectRatio = float(m_window->getWidth()) / float(m_window->getHeight()); + invAspectRatio = float(m_window->getHeight()) / float(m_window->getWidth()); if (base_t::argv.size() >= 3 && argv[1] == "-timeout_seconds") timeout = std::chrono::seconds(std::atoi(argv[2].c_str())); @@ -816,8 +816,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); ImGui::Begin("Editor"); - if (ImGui::RadioButton("Full view", !transformParams.useWindow)) - transformParams.useWindow = false; + if (ImGui::RadioButton("Full view", !useWindow)) + useWindow = false; // TODO: I need this logic per viewport we will render a scene from a point of view of its bound camera { @@ -825,13 +825,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (isPerspective) { if (isLH) - projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), transformParams.aspectRatio, zNear, zFar)); + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio, zNear, zFar)); else - projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), transformParams.aspectRatio, zNear, zFar)); + projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio, zNear, zFar)); } else { - float viewHeight = viewWidth * transformParams.invAspectRatio; + float viewHeight = viewWidth * invAspectRatio; if (isLH) projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); @@ -842,12 +842,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SameLine(); - if (ImGui::RadioButton("Window", transformParams.useWindow)) - transformParams.useWindow = true; + if (ImGui::RadioButton("Window", useWindow)) + useWindow = true; ImGui::Text("Camera"); - bool viewDirty = false; - + if (ImGui::RadioButton("LH", isLH)) isLH = true; @@ -864,7 +863,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::RadioButton("Orthographic", !isPerspective)) isPerspective = false; - ImGui::Checkbox("Enable \"view manipulate\"", &transformParams.enableViewManipulate); ImGui::Checkbox("Enable camera movement", &move); ImGui::SliderFloat("Move speed", &moveSpeed, 0.1f, 10.f); ImGui::SliderFloat("Rotate speed", &rotateSpeed, 0.1f, 10.f); @@ -879,22 +877,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SliderFloat("zNear", &zNear, 0.1f, 100.f); ImGui::SliderFloat("zFar", &zFar, 110.f, 10000.f); - viewDirty |= ImGui::SliderFloat("Distance", &transformParams.camDistance, 1.f, 69.f); - - if (viewDirty || firstFrame) - { - float32_t3 cameraPosition(cosf(camYAngle) * cosf(camXAngle) * transformParams.camDistance, sinf(camXAngle) * transformParams.camDistance, sinf(camYAngle) * cosf(camXAngle) * transformParams.camDistance); - float32_t3 cameraTarget(0.f, 0.f, 0.f); - - // TODO: lets generate events and make it - // happen purely on gimbal manipulation! - - //camera->getGimbal()->setPosition(cameraPosition); - //camera->getGimbal()->setTarget(cameraTarget); - - firstFrame = false; - } - ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); if (ImGuizmo::IsUsing()) { @@ -972,154 +954,30 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ - transformParams.editTransformDecomposition = true; + // imguizmo manipulations { - static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::TRANSLATE); - static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::LOCAL); - static bool useSnap = false; - static float snap[3] = { 1.f, 1.f, 1.f }; - static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; - static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; - static bool boundSizing = false; - static bool boundSizingSnap = false; - auto* cameraView = &imguizmoM16InOut.view[0][0]; auto* cameraProjection = &imguizmoM16InOut.projection[0][0]; - auto* matrix = &imguizmoM16InOut.model[0][0]; - - if (transformParams.editTransformDecomposition) - { - if (ImGui::IsKeyPressed(ImGuiKey_T)) - mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - if (ImGui::IsKeyPressed(ImGuiKey_R)) - mCurrentGizmoOperation = ImGuizmo::ROTATE; - if (ImGui::IsKeyPressed(ImGuiKey_S)) - mCurrentGizmoOperation = ImGuizmo::SCALE; - if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) - mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - ImGui::SameLine(); - if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) - mCurrentGizmoOperation = ImGuizmo::ROTATE; - ImGui::SameLine(); - if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) - mCurrentGizmoOperation = ImGuizmo::SCALE; - if (ImGui::RadioButton("Universal", mCurrentGizmoOperation == ImGuizmo::UNIVERSAL)) - mCurrentGizmoOperation = ImGuizmo::UNIVERSAL; - float matrixTranslation[3], matrixRotation[3], matrixScale[3]; - ImGuizmo::DecomposeMatrixToComponents(matrix, matrixTranslation, matrixRotation, matrixScale); - ImGui::InputFloat3("Tr", matrixTranslation); - ImGui::InputFloat3("Rt", matrixRotation); - ImGui::InputFloat3("Sc", matrixScale); - ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix); - - if (mCurrentGizmoOperation != ImGuizmo::SCALE) - { - if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) - mCurrentGizmoMode = ImGuizmo::LOCAL; - ImGui::SameLine(); - if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) - mCurrentGizmoMode = ImGuizmo::WORLD; - } - if (ImGui::IsKeyPressed(ImGuiKey_S) && ImGui::IsKeyPressed(ImGuiKey_LeftShift)) - useSnap = !useSnap; - ImGui::Checkbox("##UseSnap", &useSnap); - ImGui::SameLine(); - - switch (mCurrentGizmoOperation) - { - case ImGuizmo::TRANSLATE: - ImGui::InputFloat3("Snap", &snap[0]); - break; - case ImGuizmo::ROTATE: - ImGui::InputFloat("Angle Snap", &snap[0]); - break; - case ImGuizmo::SCALE: - ImGui::InputFloat("Scale Snap", &snap[0]); - break; - } - ImGui::Checkbox("Bound Sizing", &boundSizing); - if (boundSizing) - { - ImGui::PushID(3); - ImGui::Checkbox("##BoundSizing", &boundSizingSnap); - ImGui::SameLine(); - ImGui::InputFloat3("Snap", boundsSnap); - ImGui::PopID(); - } - } - - ImGuiIO& io = ImGui::GetIO(); - float viewManipulateRight = io.DisplaySize.x; - float viewManipulateTop = 0; - static ImGuiWindowFlags gizmoWindowFlags = 0; + float* objectMatrices[2u]; + + objectMatrices[0u] = &imguizmoM16InOut.model[0][0]; - /* - for the "useWindow" case we just render to a gui area, - otherwise to fake full screen transparent window + // TODO: here we will use second camera model as temp input to get deltra TRS matrix for imguizmo controller + objectMatrices[1u] = secondCameraModel.data(); - note that for both cases we make sure gizmo being - rendered is aligned to our texture scene using - imgui "cursor" screen positions - */ - - SImResourceInfo info; - info.textureID = OfflineSceneTextureIx; - info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; - - if (transformParams.useWindow) + TransformStart(cameraView, cameraProjection, objectMatrices[lastUsing]); + for (int matId = 0; matId < 2u; matId++) { - ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); - ImGui::SetNextWindowPos(ImVec2(400, 20), ImGuiCond_Appearing); - ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); - ImGui::Begin("Gizmo", 0, gizmoWindowFlags); - ImGuizmo::SetDrawlist(); - - ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); - ImVec2 windowPos = ImGui::GetWindowPos(); - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + ImGuizmo::PushID(matId); - transformParams.aspectRatio = contentRegionSize.x / contentRegionSize.y; - transformParams.invAspectRatio = contentRegionSize.y / contentRegionSize.x; - - ImGui::Image(info, contentRegionSize); - ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - viewManipulateRight = cursorPos.x + contentRegionSize.x; - viewManipulateTop = cursorPos.y; - - ImGuiWindow* window = ImGui::GetCurrentWindow(); - gizmoWindowFlags = (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); - } - else - { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(io.DisplaySize); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window - ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); - - ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - - ImGui::Image(info, contentRegionSize); - ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - viewManipulateRight = cursorPos.x + contentRegionSize.x; - viewManipulateTop = cursorPos.y; - - transformParams.aspectRatio = io.DisplaySize.x / io.DisplaySize.y; - transformParams.invAspectRatio = io.DisplaySize.y / io.DisplaySize.x; + EditTransform(cameraView, cameraProjection, objectMatrices[matId]); + if (ImGuizmo::IsUsing()) + lastUsing = matId; + ImGuizmo::PopID(); } + TransformEnd(); - // object gizmo - ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, NULL, useSnap ? &snap[0] : NULL, boundSizing ? bounds : NULL, boundSizingSnap ? boundsSnap : NULL); - - // second camera - { - // TODO: manipulate second camera given view & projection from first, use delta matrix to be passed to imguizmo controller - } - - ImGui::End(); - ImGui::PopStyleColor(); + //ImGui::End(); } } @@ -1275,6 +1133,149 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } + + void TransformStart(float* cameraView, float* cameraProjection, float* matrix) + { + static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; + static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; + static bool boundSizing = false; + static bool boundSizingSnap = false; + + if (ImGui::IsKeyPressed(ImGuiKey_T)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(ImGuiKey_E)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(ImGuiKey_R)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation); + ImGui::InputFloat3("Rt", matrixRotation); + ImGui::InputFloat3("Sc", matrixScale); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + + if (ImGui::IsKeyPressed(ImGuiKey_S)) + useSnap = !useSnap; + ImGui::Checkbox(" ", &useSnap); + ImGui::SameLine(); + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + ImGui::InputFloat3("Snap", &snap[0]); + break; + case ImGuizmo::ROTATE: + ImGui::InputFloat("Angle Snap", &snap[0]); + break; + case ImGuizmo::SCALE: + ImGui::InputFloat("Scale Snap", &snap[0]); + break; + } + + ImGuiIO& io = ImGui::GetIO(); + float viewManipulateRight = io.DisplaySize.x; + float viewManipulateTop = 0; + static ImGuiWindowFlags gizmoWindowFlags = 0; + + /* + for the "useWindow" case we just render to a gui area, + otherwise to fake full screen transparent window + + note that for both cases we make sure gizmo being + rendered is aligned to our texture scene using + imgui "cursor" screen positions + */ + + SImResourceInfo info; + info.textureID = OfflineSceneTextureIx; + info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; + + if (useWindow) + { + ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImVec2(400, 20), ImGuiCond_Appearing); + ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); + ImGui::Begin("Gizmo", 0, gizmoWindowFlags); + ImGuizmo::SetDrawlist(); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + aspectRatio = contentRegionSize.x / contentRegionSize.y; + invAspectRatio = contentRegionSize.y / contentRegionSize.x; + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + + viewManipulateRight = cursorPos.x + contentRegionSize.x; + viewManipulateTop = cursorPos.y; + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + gizmoWindowFlags = (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); + } + else + { + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window + ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); + + ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + + viewManipulateRight = cursorPos.x + contentRegionSize.x; + viewManipulateTop = cursorPos.y; + + aspectRatio = io.DisplaySize.x / io.DisplaySize.y; + invAspectRatio = io.DisplaySize.y / io.DisplaySize.x; + } + } + + void EditTransform(float* cameraView, float* cameraProjection, float* matrix) + { + ImGuiIO& io = ImGui::GetIO(); + float windowWidth = (float)ImGui::GetWindowWidth(); + float windowHeight = (float)ImGui::GetWindowHeight(); + if (!useWindow) + { + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + } + else + { + ImGuizmo::SetRect(ImGui::GetWindowPos().x, ImGui::GetWindowPos().y, windowWidth, windowHeight); + } + ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, NULL, useSnap ? &snap[0] : NULL); + } + + void TransformEnd() + { + if (useWindow) + { + ImGui::End(); + } + ImGui::PopStyleColor(1); + } + std::chrono::seconds timeout = std::chrono::seconds(0x7fffFFFFu); clock_t::time_point start; @@ -1335,15 +1336,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed - struct TransformRequestParams - { - bool useWindow = true, editTransformDecomposition = false, enableViewManipulate = false; - float camDistance = 8.f, aspectRatio = {}, invAspectRatio = {}; - }; - static constexpr inline auto OfflineSceneTextureIx = 1u; - TransformRequestParams transformParams; bool isPerspective = true, isLH = true, flipGizmoY = true, move = false; float fov = 60.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; float viewWidth = 10.f; @@ -1351,6 +1345,22 @@ class UISampleApp final : public examples::SimpleWindowedApplication float camXAngle = 32.f / 180.f * 3.14159f; bool firstFrame = true; + + // gizmo stuff + float camDistance = 8.f, aspectRatio = {}, invAspectRatio = {}; + bool useWindow = true, useSnap = false; + ImGuizmo::OPERATION mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::WORLD; + float snap[3] = { 1.f, 1.f, 1.f }; + int lastUsing = 0; + + // TMP + std::array secondCameraModel = { + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f + }; }; NBL_MAIN_FUNC(UISampleApp) \ No newline at end of file From 334651d2b18e050377a29153559a0284835a2b1b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 22 Nov 2024 13:14:10 +0100 Subject: [PATCH 40/84] add `inline const matrix operator()() const` IGimbal operator returning TRS, update the example and display imguizmo of second camera given its TRS matrix --- 61_UI/main.cpp | 75 +++++++++++++------------------ common/include/camera/IGimbal.hpp | 31 ++++++++++++- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 91576820b..6194e40a0 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -432,10 +432,30 @@ class UISampleApp final : public examples::SimpleWindowedApplication } for (auto& camera : cameraz) + { + // lets use key map presets to update the controller + camera = make_smart_refctd_ptr(float32_t3{ -2.017f, 0.386f, 0.684f }, glm::quat(0.55f, 0.047f, 0.830f, -0.072f)); // order important for quat, the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) - - // lets use key map presets to update the controller - auto& camera = cameraz.front(); + + // init keyboard map + camera->updateKeyboardMapping([&](auto& keys) + { + keys = camera->getKeyboardMappingPreset(); + }); + + // init mouse map + camera->updateMouseMapping([&](auto& keys) + { + keys = camera->getMouseMappingPreset(); + }); + + // init imguizmo map + camera->updateImguizmoMapping([&](auto& keys) + { + keys = camera->getImguizmoMappingPreset(); + }); + } + scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources)); if (!scene) @@ -444,23 +464,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - // init keyboard map - camera->updateKeyboardMapping([&](auto& keys) - { - keys = camera->getKeyboardMappingPreset(); - }); - - // init mouse map - camera->updateMouseMapping([&](auto& keys) - { - keys = camera->getMouseMappingPreset(); - }); - - // init imguizmo map - camera->updateImguizmoMapping([&](auto& keys) - { - keys = camera->getImguizmoMappingPreset(); - }); } oracle.reportBeginFrameRecord(); @@ -587,8 +590,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication IQueue::SSubmitInfo::SCommandBufferInfo commandBuffersInfo[] = { {.cmdbuf = cmdbuf } }; - - const IGPUCommandBuffer::SRenderpassBeginInfo info = { .framebuffer = m_framebuffers[resourceIx].get(), @@ -779,7 +780,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication // TODO: testing auto& camera = cameraz.front().get(); - std::vector virtualEvents(0x45); // TODO: tmp uint32_t vCount; @@ -940,7 +940,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication static struct { - float32_t4x4 view, projection, model; + float32_t4x4 view, projection, model[2u], deltaTRS[2u]; } imguizmoM16InOut; const auto& projectionMatrix = projection->getMatrix(); @@ -949,7 +949,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::SetID(0u); imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(view.matrix)); imguizmoM16InOut.projection = transpose(projectionMatrix); - imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(scene->object.model)); + imguizmoM16InOut.model[0] = transpose(getMatrix3x4As4x4(scene->object.model)); + imguizmoM16InOut.model[1] = transpose(getMatrix3x4As4x4(cameraz.back()->getGimbal()())); { if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ @@ -958,19 +959,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication { auto* cameraView = &imguizmoM16InOut.view[0][0]; auto* cameraProjection = &imguizmoM16InOut.projection[0][0]; - float* objectMatrices[2u]; - - objectMatrices[0u] = &imguizmoM16InOut.model[0][0]; - - // TODO: here we will use second camera model as temp input to get deltra TRS matrix for imguizmo controller - objectMatrices[1u] = secondCameraModel.data(); - TransformStart(cameraView, cameraProjection, objectMatrices[lastUsing]); + TransformStart(cameraView, cameraProjection, &imguizmoM16InOut.model[lastUsing][0][0]); for (int matId = 0; matId < 2u; matId++) { ImGuizmo::PushID(matId); - EditTransform(cameraView, cameraProjection, objectMatrices[matId]); + EditTransform(cameraView, cameraProjection, &imguizmoM16InOut.model[matId][0][0], &imguizmoM16InOut.deltaTRS[matId][0][0]); if (ImGuizmo::IsUsing()) lastUsing = matId; ImGuizmo::PopID(); @@ -990,7 +985,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication static float32_t4x4 modelViewProjection; auto& hook = scene->object; - hook.model = float32_t3x4(transpose(imguizmoM16InOut.model)); + hook.model = float32_t3x4(transpose(imguizmoM16InOut.model[0])); { const auto& references = resources->objects; const auto type = static_cast(gcIndex); @@ -1251,7 +1246,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - void EditTransform(float* cameraView, float* cameraProjection, float* matrix) + void EditTransform(float* cameraView, float* cameraProjection, float* matrix, float* deltaTRS) { ImGuiIO& io = ImGui::GetIO(); float windowWidth = (float)ImGui::GetWindowWidth(); @@ -1264,7 +1259,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { ImGuizmo::SetRect(ImGui::GetWindowPos().x, ImGui::GetWindowPos().y, windowWidth, windowHeight); } - ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, NULL, useSnap ? &snap[0] : NULL); + ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, deltaTRS, useSnap ? &snap[0] : NULL); } void TransformEnd() @@ -1353,14 +1348,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::WORLD; float snap[3] = { 1.f, 1.f, 1.f }; int lastUsing = 0; - - // TMP - std::array secondCameraModel = { - 1.f, 0.f, 0.f, 0.f, - 0.f, 1.f, 0.f, 0.f, - 0.f, 0.f, 1.f, 0.f, - 0.f, 0.f, 0.f, 1.f - }; }; NBL_MAIN_FUNC(UISampleApp) \ No newline at end of file diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index 341f55e17..a3d153b92 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -232,6 +232,12 @@ namespace nbl::hlsl m_position = position; } + inline void setScale(const vector& scale) + { + // we are not consider it a manipulation because it only impacts TRS world matrix we do not store + m_scale = scale; + } + inline void setOrientation(const glm::quat& orientation) { assert(m_isManipulating); // TODO: log error and return without doing nothing @@ -293,6 +299,24 @@ namespace nbl::hlsl //! Orientation of gimbal inline const auto& getOrientation() const { return m_orientation; } + //! Scale transform component + inline const auto& getScale() const { return m_scale; } + + //! World matrix (TRS) + inline const matrix operator()() const + { + const auto& position = getPosition(); + const auto& rotation = getOrthonornalMatrix(); + const auto& scale = getScale(); + + return + { + vector(rotation[0] * scale.x, position.x), + vector(rotation[1] * scale.y, position.y), + vector(rotation[2] * scale.z, position.z) + }; + } + //! Orthonormal [getXAxis(), getYAxis(), getZAxis()] orientation matrix inline const auto& getOrthonornalMatrix() const { return m_orthonormal; } @@ -330,9 +354,12 @@ namespace nbl::hlsl //! TODO: precision + replace with our "quat at home" glm::quat m_orientation; - //! Orthonormal base composed from "m_orientation" representing gimbal's "forward", "up" & "right" vectors in local space + //! Scale transform component + vector m_scale = { 1.f, 1.f , 1.f }; + + //! Orthonormal base composed from "m_orientation" representing gimbal's "forward", "up" & "right" vectors in local space - basically it spans orientation space matrix m_orthonormal; - + //! Counter that increments for each performed manipulation, resets with each begin() call size_t m_counter = {}; From c170c5d68719977a9000af4ad5eed35a517a8d1d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 22 Nov 2024 15:01:17 +0100 Subject: [PATCH 41/84] add virtual getIdentifier to camera interface, display matrices for both cameras in a window --- 61_UI/main.cpp | 129 +++++++++++++++++++++++++--------- common/include/CFPSCamera.hpp | 5 ++ common/include/ICamera.hpp | 3 + 3 files changed, 105 insertions(+), 32 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 6194e40a0..dadc1d7bc 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -976,6 +976,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } + auto& hook = scene->object; + // to Nabla + update camera & model matrices // TODO: make it more nicely @@ -984,7 +986,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication static float32_t3x4 modelView, normal; static float32_t4x4 modelViewProjection; - auto& hook = scene->object; + hook.model = float32_t3x4(transpose(imguizmoM16InOut.model[0])); { const auto& references = resources->objects; @@ -1008,52 +1010,115 @@ class UISampleApp final : public examples::SimpleWindowedApplication memcpy(ubo.MVP, &modelViewProjection[0][0], sizeof(ubo.MVP)); memcpy(ubo.MV, &modelView[0][0], sizeof(ubo.MV)); memcpy(ubo.NormalMat, &normal[0][0], sizeof(ubo.NormalMat)); - - // object meta display - { - ImGui::Begin("Object"); - ImGui::Text("type: \"%s\"", hook.meta.name.data()); - ImGui::End(); - } } - // view matrices editor { - ImGui::Begin("Matrices"); - - auto addMatrixTable = [&](const char* topText, const char* tableName, const int rows, const int columns, const float* pointer, const bool withSeparator = true) + auto addMatrixTable = [&](const char* topText, const char* tableName, int rows, int columns, const float* pointer, bool withSeparator = true) + { + ImGui::Text(topText); + ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg)); + ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg)); + if (ImGui::BeginTable(tableName, columns, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame)) { - ImGui::Text(topText); - if (ImGui::BeginTable(tableName, columns)) + for (int y = 0; y < rows; ++y) { - for (int y = 0; y < rows; ++y) + ImGui::TableNextRow(); + for (int x = 0; x < columns; ++x) { - ImGui::TableNextRow(); - for (int x = 0; x < columns; ++x) - { - ImGui::TableSetColumnIndex(x); + ImGui::TableSetColumnIndex(x); + if (pointer) ImGui::Text("%.3f", *(pointer + (y * columns) + x)); - } + else + ImGui::Text("-"); } - ImGui::EndTable(); } + ImGui::EndTable(); + } + ImGui::PopStyleColor(2); + if (withSeparator) + ImGui::Separator(); + }; - if (withSeparator) - ImGui::Separator(); - }; + // Scene Model Object + { + ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); + ImGui::Begin("Object Info and Model Matrix", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); - auto& camera = cameraz.front(); - const auto& orientation = cameraz.front()->getGimbal().getOrthonornalMatrix(); + ImGui::Text("Type: \"%s\"", hook.meta.name.data()); + ImGui::Separator(); - addMatrixTable("Object's Model Matrix", "ModelMatrixTable", 3, 4, &scene->object.model[0][0]); - addMatrixTable("Camera's Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); - addMatrixTable("Camera's Orientation Quat", "OrientationQuatTable", 1, 4, &camera->getGimbal().getOrientation()[0]); - addMatrixTable("Camera's View Matrix", "ViewMatrixTable", 3, 4, &view.matrix[0][0]); - addMatrixTable("Bound Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); + ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); + ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered)); - ImGui::End(); + addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, &scene->object.model[0][0]); + + ImGui::PopStyleColor(2); + ImGui::End(); + } + + // Cameraz + { + size_t cameraCount = cameraz.size(); + if (cameraCount > 0) + { + size_t columns = std::max(1, static_cast(std::sqrt(static_cast(cameraCount)))); + size_t rows = (cameraCount + columns - 1) / columns; + + ImGui::Begin("Camera Matrices", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4.0f, 2.0f)); + + for (size_t row = 0; row < rows; ++row) + { + for (size_t col = 0; col < columns; ++col) + { + size_t cameraIndex = row * columns + col; + if (cameraIndex >= cameraCount) + break; + + auto& camera = cameraz[cameraIndex]; + if (!camera) + continue; + + auto& gimbal = camera->getGimbal(); + const auto& position = gimbal.getPosition(); + const auto& orientation = gimbal.getOrientation(); + const auto& viewMatrix = gimbal.getView().matrix; + + ImGui::Text("ID: %zu", cameraIndex); + ImGui::Separator(); + + ImGui::Text("Type: %s", camera->getIdentifier().data()); + ImGui::Separator(); + + ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg)); + ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg)); + + if (ImGui::BeginTable(("CameraTable" + std::to_string(cameraIndex)).c_str(), 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame)) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + + addMatrixTable("Position", ("PositionTable" + std::to_string(cameraIndex)).c_str(), 1, 3, &position[0], false); + addMatrixTable("Orientation (Quaternion)", ("OrientationTable" + std::to_string(cameraIndex)).c_str(), 1, 4, &orientation[0], false); + addMatrixTable("View Matrix", ("ViewMatrixTable" + std::to_string(cameraIndex)).c_str(), 3, 4, &viewMatrix[0][0], true); + + ImGui::EndTable(); + } + + ImGui::PopStyleColor(2); + } + } + + ImGui::PopStyleVar(); + ImGui::End(); + } + else + ImGui::Text("No camera properties to display."); + } } + // Nabla Imgui backend MDI buffer info // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, // so unless you do that, don't use this basic info to optimize the size of your IMGUI buffer. diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 92943d01e..477fa8df4 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -90,6 +90,11 @@ class CFPSCamera final : public ICamera } } + virtual const std::string_view getIdentifier() override + { + return "FPS Camera"; + } + private: typename base_t::CGimbal m_gimbal; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index db5cf969d..b9a5eca8c 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -89,6 +89,9 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted // VirtualEventType bitmask for a camera view gimbal manipulation requests filtering virtual const uint32_t getAllowedVirtualEvents(ManipulationMode mode) = 0u; + + // Identifier of a camera type + virtual const std::string_view getIdentifier() = 0u; }; } From df0d70210a02f8bbc47aaaab750d4b06c505a248 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 2 Dec 2024 19:31:36 +0100 Subject: [PATCH 42/84] one step ahead to imguizmo controller - display the scene from second camera perspective & deploy imguizmo controller to manipulate second camera (first tests, a lot of bugs there to fix) --- 61_UI/main.cpp | 444 +++++++++++++++--------- common/include/CFPSCamera.hpp | 6 +- common/include/CGeomtryCreatorScene.hpp | 1 - 3 files changed, 287 insertions(+), 164 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index dadc1d7bc..59afdb870 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -390,7 +390,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication params.resources.samplersInfo = { .setIx = 0u, .bindingIx = 1u }; params.assetManager = m_assetManager; params.pipelineCache = nullptr; - params.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, TexturesAmount); + params.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, TotalUISampleTexturesAmount); params.renderpass = smart_refctd_ptr(m_renderpass); params.streamingBuffer = nullptr; params.subpassIx = 0u; @@ -408,7 +408,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication IDescriptorPool::SCreateInfo descriptorPoolInfo = {}; descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = (uint32_t)nbl::ext::imgui::UI::DefaultSamplerIx::COUNT; - descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = TexturesAmount; + descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = TotalUISampleTexturesAmount; descriptorPoolInfo.maxSets = 1u; descriptorPoolInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT; @@ -421,6 +421,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication m_ui.manager->registerListener([this]() -> void { imguiListen(); }); } + for (auto& projection : projections) + projection = make_smart_refctd_ptr(); + // Geometry Creator Render Scenes { resources = ResourcesBundle::create(m_device.get(), m_logger.get(), getGraphicsQueue(), m_assetManager->getGeometryCreator()); @@ -431,11 +434,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - for (auto& camera : cameraz) + // TOOD: we should be able to load position & orientation from json file, support multiple cameraz + const float32_t3 iPosition[CamerazCount] = { float32_t3{ -2.238f, 1.438f, -1.558f }, float32_t3{ -2.017f, 0.386f, 0.684f } }; + // order important for glm::quat, the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) but memory layout is x,y,z,w + const glm::quat iOrientation[CamerazCount] = { glm::quat(0.888f, 0.253f, 0.368f, -0.105f), glm::quat(0.55f, 0.047f, 0.830f, -0.072f) }; + + for (uint32_t i = 0u; i < cameraz.size(); ++i) { + auto& camera = cameraz[i]; + // lets use key map presets to update the controller - camera = make_smart_refctd_ptr(float32_t3{ -2.017f, 0.386f, 0.684f }, glm::quat(0.55f, 0.047f, 0.830f, -0.072f)); // order important for quat, the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) + camera = make_smart_refctd_ptr(iPosition[i], iOrientation[i]); // init keyboard map camera->updateKeyboardMapping([&](auto& keys) @@ -456,14 +466,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication }); } - scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources)); - - if (!scene) + for (uint32_t i = 0u; i < scenez.size(); ++i) { - m_logger->log("Could not create geometry creator scene!", ILogger::ELL_ERROR); - return false; + auto& scene = scenez[i]; + scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources)); + + if (!scene) + { + m_logger->log("Could not create geometry creator scene[%d]!", ILogger::ELL_ERROR, i); + return false; + } } - } oracle.reportBeginFrameRecord(); @@ -472,8 +485,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication TESTS, TODO: remove all once finished work & integrate with the example properly */ - aspectRatio = float(m_window->getWidth()) / float(m_window->getHeight()); - invAspectRatio = float(m_window->getHeight()) / float(m_window->getWidth()); + const auto iAspectRatio = float(m_window->getWidth()) / float(m_window->getHeight()); + const auto iInvAspectRatio = float(m_window->getHeight()) / float(m_window->getWidth()); + + for (uint32_t i = 0u; i < ProjectionsCount; ++i) + { + aspectRatio[i] = iAspectRatio; + invAspectRatio[i] = iInvAspectRatio; + } if (base_t::argv.size() >= 3 && argv[1] == "-timeout_seconds") timeout = std::chrono::seconds(std::atoi(argv[2].c_str())); @@ -483,15 +502,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication bool updateGUIDescriptorSet() { - // texture atlas + our scene texture, note we don't create info & write pair for the font sampler because UI extension's is immutable and baked into DS layout - static std::array descriptorInfo; - static IGPUDescriptorSet::SWriteDescriptorSet writes[TexturesAmount]; + // UI texture atlas + our camera scene textures, note we don't create info & write pair for the font sampler because UI extension's is immutable and baked into DS layout + static std::array descriptorInfo; + static IGPUDescriptorSet::SWriteDescriptorSet writes[TotalUISampleTexturesAmount]; descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].desc = core::smart_refctd_ptr(m_ui.manager->getFontAtlasView()); - descriptorInfo[OfflineSceneTextureIx].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - descriptorInfo[OfflineSceneTextureIx].desc = scene->getColorAttachment(); + descriptorInfo[OfflineSceneFirstCameraTextureIx].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + descriptorInfo[OfflineSceneFirstCameraTextureIx].desc = scenez[0]->getColorAttachment(); + + descriptorInfo[OfflineSceneSecondCameraTextureIx].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + descriptorInfo[OfflineSceneSecondCameraTextureIx].desc = scenez[1]->getColorAttachment(); for (uint32_t i = 0; i < descriptorInfo.size(); ++i) { @@ -501,7 +523,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication writes[i].count = 1u; } writes[nbl::ext::imgui::UI::FontAtlasTexId].info = descriptorInfo.data() + nbl::ext::imgui::UI::FontAtlasTexId; - writes[OfflineSceneTextureIx].info = descriptorInfo.data() + OfflineSceneTextureIx; + writes[OfflineSceneFirstCameraTextureIx].info = descriptorInfo.data() + OfflineSceneFirstCameraTextureIx; + writes[OfflineSceneSecondCameraTextureIx].info = descriptorInfo.data() + OfflineSceneSecondCameraTextureIx; return m_device->updateDescriptorSets(writes, {}); } @@ -547,13 +570,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication // render geometry creator scene to offline frame buffer & submit // TODO: OK with TRI buffer this thing is retarded now // (**) <- a note why bellow before submit - scene->begin(); + + for (auto scene : scenez) { - scene->update(); - scene->record(); - scene->end(); + scene->begin(); + { + scene->update(); + scene->record(); + scene->end(); + } + scene->submit(getGraphicsQueue()); } - scene->submit(getGraphicsQueue()); willSubmit &= cmdbuf->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); willSubmit &= cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); @@ -679,13 +706,21 @@ class UISampleApp final : public examples::SimpleWindowedApplication } }; - // (**) -> wait on offline framebuffer + // (**) -> wait on offline framebuffers { const nbl::video::ISemaphore::SWaitInfo waitInfos[] = - { { - .semaphore = scene->semaphore.progress.get(), - .value = scene->semaphore.finishedValue - } }; + { + // wait for first camera scene view fb + { + .semaphore = scenez[0]->semaphore.progress.get(), + .value = scenez[0]->semaphore.finishedValue + }, + // and second one too + { + .semaphore = scenez[1]->semaphore.progress.get(), + .value = scenez[1]->semaphore.finishedValue + }, + }; m_device->blockForSemaphores(waitInfos); updateGUIDescriptorSet(); @@ -738,7 +773,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication return timestamp; }; - const auto nextPresentationTimestamp = updatePresentationTimestamp(); + m_nextPresentationTimestamp = updatePresentationTimestamp(); struct { @@ -780,10 +815,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication // TODO: testing auto& camera = cameraz.front().get(); - std::vector virtualEvents(0x45); // TODO: tmp + static std::vector virtualEvents(0x45); uint32_t vCount; - camera->beginInputProcessing(nextPresentationTimestamp); + camera->beginInputProcessing(m_nextPresentationTimestamp); { camera->process(nullptr, vCount); @@ -793,7 +828,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication camera->process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); } camera->endInputProcessing(); - camera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); } @@ -816,34 +850,41 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); ImGui::Begin("Editor"); - if (ImGui::RadioButton("Full view", !useWindow)) - useWindow = false; + //if (ImGui::RadioButton("Full view", !useWindow)) + // useWindow = false; // TODO: I need this logic per viewport we will render a scene from a point of view of its bound camera { - if (isPerspective) + for (uint32_t i = 0u; i < projections.size(); ++i) { - if (isLH) - projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio, zNear, zFar)); - else - projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio, zNear, zFar)); - } - else - { - float viewHeight = viewWidth * invAspectRatio; + auto& projection = projections[i]; - if (isLH) - projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); + if (isPerspective) + { + if (isLH) + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio[i], zNear, zFar)); + else + projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio[i], zNear, zFar)); + } else - projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); + { + float viewHeight = viewWidth * invAspectRatio[i]; + + if (isLH) + projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); + else + projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); + } } + + } ImGui::SameLine(); - if (ImGui::RadioButton("Window", useWindow)) - useWindow = true; + //if (ImGui::RadioButton("Window", useWindow)) + // useWindow = true; ImGui::Text("Camera"); @@ -855,13 +896,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::RadioButton("RH", !isLH)) isLH = false; - if (ImGui::RadioButton("Perspective", isPerspective)) - isPerspective = true; + //if (ImGui::RadioButton("Perspective", isPerspective)) + // isPerspective = true; - ImGui::SameLine(); + //ImGui::SameLine(); - if (ImGui::RadioButton("Orthographic", !isPerspective)) - isPerspective = false; + //if (ImGui::RadioButton("Orthographic", !isPerspective)) + // isPerspective = false; ImGui::Checkbox("Enable camera movement", &move); ImGui::SliderFloat("Move speed", &moveSpeed, 0.1f, 10.f); @@ -934,82 +975,171 @@ class UISampleApp final : public examples::SimpleWindowedApplication = transpose() * - * the ViewManipulate final call (inside EditTransform) returns world space column major matrix for an object, - * note it also modifies input view matrix but projection matrix is immutable */ static struct { - float32_t4x4 view, projection, model[2u], deltaTRS[2u]; + float32_t4x4 view[CamerazCount], projection[ProjectionsCount], inModel[2u], outModel[2u], outDeltaTRS[2u]; } imguizmoM16InOut; - const auto& projectionMatrix = projection->getMatrix(); - const auto& view = cameraz.front()->getGimbal().getView(); + //const auto& projectionMatrix = projection->getMatrix(); + + const auto& firstcamera = cameraz.front(); + const auto& secondcamera = cameraz.back(); ImGuizmo::SetID(0u); - imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(view.matrix)); - imguizmoM16InOut.projection = transpose(projectionMatrix); - imguizmoM16InOut.model[0] = transpose(getMatrix3x4As4x4(scene->object.model)); - imguizmoM16InOut.model[1] = transpose(getMatrix3x4As4x4(cameraz.back()->getGimbal()())); + + imguizmoM16InOut.view[0u] = transpose(getMatrix3x4As4x4(firstcamera->getGimbal().getView().matrix)); + imguizmoM16InOut.view[1u] = transpose(getMatrix3x4As4x4(secondcamera->getGimbal().getView().matrix)); + + for(uint32_t i = 0u; i < ProjectionsCount; ++i) + imguizmoM16InOut.projection[i] = transpose(projections[i]->getMatrix()); + + // we will transform a scene object's model + imguizmoM16InOut.inModel[0] = transpose(getMatrix3x4As4x4(m_model)); + // and second camera's model too + imguizmoM16InOut.inModel[1] = transpose(getMatrix3x4As4x4(secondcamera->getGimbal()())); { + float* views[] + { + &imguizmoM16InOut.view[0u][0][0], // first camera + &imguizmoM16InOut.view[1u][0][0] // second camera + }; + + float* projections[] + { + &imguizmoM16InOut.projection[0u][0][0], // full screen, tmp off + &imguizmoM16InOut.projection[1u][0][0], // first camera + &imguizmoM16InOut.projection[2u][0][0] // second camera + }; + if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates - imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ + for(uint32_t i = 0u; i < ProjectionsCount; ++i) + imguizmoM16InOut.projection[i][1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ + + float* lastUsedModel = &imguizmoM16InOut.inModel[lastUsing][0][0]; - // imguizmo manipulations + // ImGuizmo manipulations on the last used model matrix + TransformEditor(lastUsedModel); + + for (uint32_t windowIndex = 0; windowIndex < 2u; ++windowIndex) { - auto* cameraView = &imguizmoM16InOut.view[0][0]; - auto* cameraProjection = &imguizmoM16InOut.projection[0][0]; + auto* cameraView = views[windowIndex]; + auto* cameraProjection = projections[1u + windowIndex]; + + TransformStart(windowIndex); - TransformStart(cameraView, cameraProjection, &imguizmoM16InOut.model[lastUsing][0][0]); - for (int matId = 0; matId < 2u; matId++) + for (uint32_t matId = 0; matId < 2; matId++) { + if(matId == 0) + ImGuizmo::AllowAxisFlip(true); + else + ImGuizmo::AllowAxisFlip(false); + + // we must work on copies otherwise we enter bug zone, you need to be careful to not edit the same model twice since we share them with camera fbos + auto model = imguizmoM16InOut.inModel[matId]; + float32_t4x4 deltaOutputTRS; + ImGuizmo::PushID(matId); - EditTransform(cameraView, cameraProjection, &imguizmoM16InOut.model[matId][0][0], &imguizmoM16InOut.deltaTRS[matId][0][0]); + ImGuiIO& io = ImGui::GetIO(); + bool success = ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, &model[0][0], &deltaOutputTRS[0][0], useSnap ? &snap[0] : NULL); + + imguizmoM16InOut.outModel[matId] = model; + imguizmoM16InOut.outDeltaTRS[matId] = deltaOutputTRS; + if (ImGuizmo::IsUsing()) lastUsing = matId; + ImGuizmo::PopID(); } - TransformEnd(); - //ImGui::End(); + TransformEnd(); } } - auto& hook = scene->object; + /* + testing imguizmo controller, we use deltra TRS matrix to generate virtual events + */ + + { + static std::vector virtualEvents(0x45); + uint32_t vCount; - // to Nabla + update camera & model matrices + secondcamera->beginInputProcessing(m_nextPresentationTimestamp); + { + secondcamera->process(nullptr, vCount); + + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); + + IGimbalController::SUpdateParameters params; + + params.imguizmoEvents = { { imguizmoM16InOut.outDeltaTRS[1u] } }; + + secondcamera->process(virtualEvents.data(), vCount, params); + } + secondcamera->endInputProcessing(); + + // TOOD: this needs debugging + secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); + /* + if(vCount) + switch (mCurrentGizmoMode) + { + case ImGuizmo::MODE::LOCAL: + secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); break; + case ImGuizmo::MODE::WORLD: + secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); break; + default: break; + } + */ + } + // update scenes data + // to Nabla + update camera & model matrices + // TODO: make it more nicely - const_cast(view.matrix) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) + std::string_view mName; + const_cast(firstcamera->getGimbal().getView().matrix) = float32_t3x4(transpose(imguizmoM16InOut.view[0u])); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) { - static float32_t3x4 modelView, normal; - static float32_t4x4 modelViewProjection; + m_model = float32_t3x4(transpose(imguizmoM16InOut.outModel[0])); + + const float32_t3x4* views[] = + { + &firstcamera->getGimbal().getView().matrix, + &secondcamera->getGimbal().getView().matrix + }; + const auto& references = resources->objects; + const auto type = static_cast(gcIndex); + const auto& [gpu, meta] = references[type]; - hook.model = float32_t3x4(transpose(imguizmoM16InOut.model[0])); + for (uint32_t i = 0u; i < cameraz.size(); ++i) { - const auto& references = resources->objects; - const auto type = static_cast(gcIndex); + auto& scene = scenez[i]; + auto& hook = scene->object; - const auto& [gpu, meta] = references[type]; hook.meta.type = type; - hook.meta.name = meta.name; - } - - auto& ubo = hook.viewParameters; + mName = hook.meta.name = meta.name; + { + float32_t3x4 modelView, normal; + float32_t4x4 modelViewProjection; - modelView = concatenateBFollowedByA(view.matrix, hook.model); + const auto& viewMatrix = *views[i]; + modelView = concatenateBFollowedByA(viewMatrix, m_model); - // TODO - //modelView.getSub3x3InverseTranspose(normal); + // TODO + //modelView.getSub3x3InverseTranspose(normal); - auto concatMatrix = mul(projectionMatrix, getMatrix3x4As4x4(view.matrix)); - modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(hook.model)); + auto concatMatrix = mul(projections[i]->getMatrix(), getMatrix3x4As4x4(viewMatrix)); + modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(m_model)); - memcpy(ubo.MVP, &modelViewProjection[0][0], sizeof(ubo.MVP)); - memcpy(ubo.MV, &modelView[0][0], sizeof(ubo.MV)); - memcpy(ubo.NormalMat, &normal[0][0], sizeof(ubo.NormalMat)); + memcpy(hook.viewParameters.MVP, &modelViewProjection[0][0], sizeof(hook.viewParameters.MVP)); + memcpy(hook.viewParameters.MV, &modelView[0][0], sizeof(hook.viewParameters.MV)); + memcpy(hook.viewParameters.NormalMat, &normal[0][0], sizeof(hook.viewParameters.NormalMat)); + } + } } { @@ -1044,13 +1174,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); ImGui::Begin("Object Info and Model Matrix", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); - ImGui::Text("Type: \"%s\"", hook.meta.name.data()); + ImGui::Text("Type: \"%s\"", mName.data()); ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered)); - addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, &scene->object.model[0][0]); + addMatrixTable("Scene Object Model Matrix", "ModelMatrixTable", 3, 4, &m_model[0][0]); ImGui::PopStyleColor(2); ImGui::End(); @@ -1084,6 +1214,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto& position = gimbal.getPosition(); const auto& orientation = gimbal.getOrientation(); const auto& viewMatrix = gimbal.getView().matrix; + const auto trsMatrix = gimbal(); ImGui::Text("ID: %zu", cameraIndex); ImGui::Separator(); @@ -1099,9 +1230,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - addMatrixTable("Position", ("PositionTable" + std::to_string(cameraIndex)).c_str(), 1, 3, &position[0], false); - addMatrixTable("Orientation (Quaternion)", ("OrientationTable" + std::to_string(cameraIndex)).c_str(), 1, 4, &orientation[0], false); - addMatrixTable("View Matrix", ("ViewMatrixTable" + std::to_string(cameraIndex)).c_str(), 3, 4, &viewMatrix[0][0], true); + const auto idxstr = std::to_string(cameraIndex); + addMatrixTable("Position", ("PositionTable" + idxstr).c_str(), 1, 3, &position[0], false); + addMatrixTable("Orientation (Quaternion)", ("OrientationTable" + idxstr).c_str(), 1, 4, &orientation[0], false); + addMatrixTable("View Matrix", ("ViewMatrixTable" + idxstr).c_str(), 3, 4, &viewMatrix[0][0], false); + addMatrixTable("TRS Matrix", ("TRSMatrixTable" + idxstr).c_str(), 3, 4, &trsMatrix[0][0], true); ImGui::EndTable(); } @@ -1193,8 +1326,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } - - void TransformStart(float* cameraView, float* cameraProjection, float* matrix) + void TransformEditor(float* matrix) { static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; @@ -1247,48 +1379,50 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::InputFloat("Scale Snap", &snap[0]); break; } + } + void TransformStart(uint32_t wCameraIndex) + { ImGuiIO& io = ImGui::GetIO(); - float viewManipulateRight = io.DisplaySize.x; - float viewManipulateTop = 0; - static ImGuiWindowFlags gizmoWindowFlags = 0; - - /* - for the "useWindow" case we just render to a gui area, - otherwise to fake full screen transparent window - - note that for both cases we make sure gizmo being - rendered is aligned to our texture scene using - imgui "cursor" screen positions - */ + static ImGuiWindowFlags gizmoWindowFlags = ImGuiWindowFlags_NoMove; SImResourceInfo info; - info.textureID = OfflineSceneTextureIx; + switch (wCameraIndex) + { + case 0: + info.textureID = OfflineSceneFirstCameraTextureIx; + break; + case 1: + info.textureID = OfflineSceneSecondCameraTextureIx; + break; + default: + assert(false); // "wCameraIndex OOB!" + break; + } info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; + aspectRatio[0] = io.DisplaySize.x / io.DisplaySize.y; + invAspectRatio[0] = io.DisplaySize.y / io.DisplaySize.x; + if (useWindow) { ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); - ImGui::SetNextWindowPos(ImVec2(400, 20), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImVec2(400, 20 + wCameraIndex * 420), ImGuiCond_Appearing); ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); - ImGui::Begin("Gizmo", 0, gizmoWindowFlags); + std::string ident = "Active Camera ID: " + std::to_string(wCameraIndex); + ImGui::Begin(ident.data(), 0, gizmoWindowFlags); ImGuizmo::SetDrawlist(); - ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); - ImVec2 windowPos = ImGui::GetWindowPos(); - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + ImVec2 contentRegionSize, windowPos, cursorPos; + contentRegionSize = ImGui::GetContentRegionAvail(); + cursorPos = ImGui::GetCursorScreenPos(); + windowPos = ImGui::GetWindowPos(); - aspectRatio = contentRegionSize.x / contentRegionSize.y; - invAspectRatio = contentRegionSize.y / contentRegionSize.x; + aspectRatio[wCameraIndex + 1] = contentRegionSize.x / contentRegionSize.y; + invAspectRatio[wCameraIndex + 1] = contentRegionSize.y / contentRegionSize.x; ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - viewManipulateRight = cursorPos.x + contentRegionSize.x; - viewManipulateTop = cursorPos.y; - - ImGuiWindow* window = ImGui::GetCurrentWindow(); - gizmoWindowFlags = (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); } else { @@ -1297,34 +1431,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); - ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(); - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + ImVec2 contentRegionSize, windowPos, cursorPos; + contentRegionSize = ImGui::GetContentRegionAvail(); + cursorPos = ImGui::GetCursorScreenPos(); + windowPos = ImGui::GetWindowPos(); ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - viewManipulateRight = cursorPos.x + contentRegionSize.x; - viewManipulateTop = cursorPos.y; - - aspectRatio = io.DisplaySize.x / io.DisplaySize.y; - invAspectRatio = io.DisplaySize.y / io.DisplaySize.x; } - } - void EditTransform(float* cameraView, float* cameraProjection, float* matrix, float* deltaTRS) - { - ImGuiIO& io = ImGui::GetIO(); - float windowWidth = (float)ImGui::GetWindowWidth(); - float windowHeight = (float)ImGui::GetWindowHeight(); - if (!useWindow) - { - ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); - } - else - { - ImGuizmo::SetRect(ImGui::GetWindowPos().x, ImGui::GetWindowPos().y, windowWidth, windowHeight); - } - ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, matrix, deltaTRS, useSnap ? &snap[0] : NULL); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + gizmoWindowFlags = ImGuiWindowFlags_NoMove;//(ImGuizmo::IsUsing() && ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); } void TransformEnd() @@ -1367,8 +1484,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication InputSystem::ChannelReader mouse; // Handles keyboard events InputSystem::ChannelReader keyboard; + //! next presentation timestamp + std::chrono::microseconds m_nextPresentationTimestamp = {}; - constexpr static inline auto TexturesAmount = 2u; + // UI font atlas; first camera fb, second camera fb + constexpr static inline auto TotalUISampleTexturesAmount = 3u; core::smart_refctd_ptr m_descriptorSetPool; @@ -1384,35 +1504,37 @@ class UISampleApp final : public examples::SimpleWindowedApplication core::smart_refctd_ptr descriptorSet; }; - nbl::core::smart_refctd_ptr scene; - // lets first test 2 cameras & imguizmo controller, then we will add second scene to render into 2 frame buffers - std::array>, 2u> cameraz; + // one model object in the world, testing 2 cameraz for which view is rendered to separate frame buffers (so what they see) with new controller API including imguizmo + nbl::hlsl::float32_t3x4 m_model = nbl::hlsl::float32_t3x4(1.f); + static constexpr inline auto CamerazCount = 2u; + std::array, CamerazCount> scenez; + std::array>, CamerazCount> cameraz; nbl::core::smart_refctd_ptr resources; - CRenderUI m_ui; + static constexpr inline auto OfflineSceneFirstCameraTextureIx = 1u; + static constexpr inline auto OfflineSceneSecondCameraTextureIx = 2u; - smart_refctd_ptr projection = make_smart_refctd_ptr(); // TMP! + CRenderUI m_ui; video::CDumbPresentationOracle oracle; uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed - static constexpr inline auto OfflineSceneTextureIx = 1u; - - bool isPerspective = true, isLH = true, flipGizmoY = true, move = false; + static constexpr inline auto ProjectionsCount = 3u; + std::array, ProjectionsCount> projections; // TMP! + bool isPerspective[ProjectionsCount] = { true, true, true }, isLH = true, flipGizmoY = true, move = false; float fov = 60.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; float viewWidth = 10.f; float camYAngle = 165.f / 180.f * 3.14159f; float camXAngle = 32.f / 180.f * 3.14159f; - bool firstFrame = true; - - // gizmo stuff - float camDistance = 8.f, aspectRatio = {}, invAspectRatio = {}; + float camDistance = 8.f, aspectRatio[ProjectionsCount] = {}, invAspectRatio[ProjectionsCount] = {}; bool useWindow = true, useSnap = false; ImGuizmo::OPERATION mCurrentGizmoOperation = ImGuizmo::TRANSLATE; ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::WORLD; float snap[3] = { 1.f, 1.f, 1.f }; int lastUsing = 0; + + bool firstFrame = true; }; NBL_MAIN_FUNC(UISampleApp) \ No newline at end of file diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 477fa8df4..90e01b4e7 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -98,8 +98,8 @@ class CFPSCamera final : public ICamera private: typename base_t::CGimbal m_gimbal; - static inline constexpr auto AllowedLocalVirtualEvents = CVirtualGimbalEvent::MoveForward | CVirtualGimbalEvent::MoveBackward | CVirtualGimbalEvent::MoveRight | CVirtualGimbalEvent::MoveLeft | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; - static inline constexpr auto AllowedWorldVirtualEvents = AllowedLocalVirtualEvents | CVirtualGimbalEvent::Translate; + static inline constexpr auto AllowedLocalVirtualEvents = CVirtualGimbalEvent::Translate | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; + static inline constexpr auto AllowedWorldVirtualEvents = AllowedLocalVirtualEvents; static inline const auto m_keyboard_to_virtual_events_preset = []() { @@ -137,6 +137,8 @@ class CFPSCamera final : public ICamera preset[CVirtualGimbalEvent::MoveBackward] = CVirtualGimbalEvent::MoveBackward; preset[CVirtualGimbalEvent::MoveLeft] = CVirtualGimbalEvent::MoveLeft; preset[CVirtualGimbalEvent::MoveRight] = CVirtualGimbalEvent::MoveRight; + preset[CVirtualGimbalEvent::MoveUp] = CVirtualGimbalEvent::MoveUp; + preset[CVirtualGimbalEvent::MoveDown] = CVirtualGimbalEvent::MoveDown; preset[CVirtualGimbalEvent::TiltDown] = CVirtualGimbalEvent::TiltDown; preset[CVirtualGimbalEvent::TiltUp] = CVirtualGimbalEvent::TiltUp; preset[CVirtualGimbalEvent::PanLeft] = CVirtualGimbalEvent::PanLeft; diff --git a/common/include/CGeomtryCreatorScene.hpp b/common/include/CGeomtryCreatorScene.hpp index 07f54b19a..e744b795e 100644 --- a/common/include/CGeomtryCreatorScene.hpp +++ b/common/include/CGeomtryCreatorScene.hpp @@ -500,7 +500,6 @@ struct ResourcesBundle : public virtual nbl::core::IReferenceCounted struct ObjectInstance { - nbl::hlsl::float32_t3x4 model = nbl::hlsl::float32_t3x4(1.f); nbl::asset::SBasicViewParameters viewParameters; ObjectMeta meta; }; From 6cd5e18f6a62f6387d40a4137ac577ca42140613 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 3 Dec 2024 16:44:46 +0100 Subject: [PATCH 43/84] fix gizmo manipulation bugs, make sure a model is manipulated only once regardless GUI window amount --- 61_UI/main.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 59afdb870..620939966 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1021,7 +1021,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication // ImGuizmo manipulations on the last used model matrix TransformEditor(lastUsedModel); + uint32_t gizmoID = {}; + bool manipulatedFromAnyWindow = false; + // we have 2 GUI windows we render into with FBOs for (uint32_t windowIndex = 0; windowIndex < 2u; ++windowIndex) { auto* cameraView = views[windowIndex]; @@ -1029,6 +1032,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication TransformStart(windowIndex); + // but only 2 objects with matrices we will try to manipulate & we can do this from both windows! + // however note it only makes sense to obsly assume we cannot manipulate 2 objects at the same time for (uint32_t matId = 0; matId < 2; matId++) { if(matId == 0) @@ -1036,22 +1041,34 @@ class UISampleApp final : public examples::SimpleWindowedApplication else ImGuizmo::AllowAxisFlip(false); - // we must work on copies otherwise we enter bug zone, you need to be careful to not edit the same model twice since we share them with camera fbos + // and because of imguizmo API usage to achive it we must work on copies & filter the output (unless we try enable/disable logic described bellow) + // -> in general again we need to be careful to not edit the same model twice auto model = imguizmoM16InOut.inModel[matId]; float32_t4x4 deltaOutputTRS; - ImGuizmo::PushID(matId); + // note we also need to take care of unique gizmo IDs, we have in total 4 gizmos even though we only want to manipulate 2 objects in total + ImGuizmo::PushID(gizmoID); ImGuiIO& io = ImGui::GetIO(); - bool success = ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, &model[0][0], &deltaOutputTRS[0][0], useSnap ? &snap[0] : NULL); + const bool success = ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, &model[0][0], &deltaOutputTRS[0][0], useSnap ? &snap[0] : NULL); - imguizmoM16InOut.outModel[matId] = model; - imguizmoM16InOut.outDeltaTRS[matId] = deltaOutputTRS; + // we manipulated a gizmo from a X-th window, now we update output matrices and assume no more gizmos can be manipulated at the same frame + // here we could also extend the logic & keep track of the gizmo being handled and as long as its manipulated (hold by mouse) -> then given ID of this gizmo we would + // enable it & disable all other gizmos till we finish the manipulation + if (!manipulatedFromAnyWindow) + { + imguizmoM16InOut.outModel[matId] = model; + imguizmoM16InOut.outDeltaTRS[matId] = deltaOutputTRS; + } + + if (success) + manipulatedFromAnyWindow = true; if (ImGuizmo::IsUsing()) lastUsing = matId; ImGuizmo::PopID(); + ++gizmoID; } TransformEnd(); From e678f22b9e7532a141138ef16502bb85ec105126 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 4 Dec 2024 10:02:44 +0100 Subject: [PATCH 44/84] add toRetardedImguizmoTRSInput, have nicely aligned *local base* orientation of my gimbals to imguizmo --- 61_UI/main.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 620939966..764b15b6a 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -995,10 +995,25 @@ class UISampleApp final : public examples::SimpleWindowedApplication for(uint32_t i = 0u; i < ProjectionsCount; ++i) imguizmoM16InOut.projection[i] = transpose(projections[i]->getMatrix()); + // wth its kinda column major but seems to interprete RS part as row major? + // will think of it later and check what its doing under the hood, now it + // seems to match my local base orientation correctly + auto toRetardedImguizmoTRSInput = [&](const float32_t3x4& nablaTrs) -> float32_t4x4 + { + // *do not transpose whole matrix*, only the translate part + float32_t4x4 trs = getMatrix3x4As4x4(nablaTrs); + trs[3] = float32_t4(nablaTrs[0][3], nablaTrs[1][3], nablaTrs[2][3], 1.f); + trs[0][3] = 0.f; + trs[1][3] = 0.f; + trs[2][3] = 0.f; + + return trs; + }; + // we will transform a scene object's model - imguizmoM16InOut.inModel[0] = transpose(getMatrix3x4As4x4(m_model)); + imguizmoM16InOut.inModel[0] = toRetardedImguizmoTRSInput(m_model); // and second camera's model too - imguizmoM16InOut.inModel[1] = transpose(getMatrix3x4As4x4(secondcamera->getGimbal()())); + imguizmoM16InOut.inModel[1] = toRetardedImguizmoTRSInput(secondcamera->getGimbal()()); { float* views[] { @@ -1036,9 +1051,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication // however note it only makes sense to obsly assume we cannot manipulate 2 objects at the same time for (uint32_t matId = 0; matId < 2; matId++) { - if(matId == 0) - ImGuizmo::AllowAxisFlip(true); - else + //if(matId == 0) + // ImGuizmo::AllowAxisFlip(true); + //else ImGuizmo::AllowAxisFlip(false); // and because of imguizmo API usage to achive it we must work on copies & filter the output (unless we try enable/disable logic described bellow) @@ -1186,7 +1201,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Separator(); }; - // Scene Model Object + // Scene Models { ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); ImGui::Begin("Object Info and Model Matrix", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); @@ -1203,6 +1218,30 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } + // ImGuizmo inputs + { + ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); + ImGui::Begin("ImGuizmo model inputs", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); + + ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); + ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered)); + + ImGui::Text("Type: GC Object"); + ImGui::Separator(); + + addMatrixTable("Model", "GCIMGUIZMOTRS", 4, 4, &imguizmoM16InOut.inModel[0][0][0]); + + ImGui::Text("Type: Camera ID \"1\" object"); + ImGui::Separator(); + + addMatrixTable("Model", "CAMERASECIMGUIZMOTRS", 4, 4, &imguizmoM16InOut.inModel[1][0][0]); + + ImGui::PopStyleColor(2); + ImGui::End(); + } + + //imguizmoM16InOut.inModel + // Cameraz { size_t cameraCount = cameraz.size(); From f157b4f2333d41f2d1a47ee71f5233864a6e7893 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 4 Dec 2024 14:37:55 +0100 Subject: [PATCH 45/84] make the imguizmo controller work for translations --- 61_UI/main.cpp | 109 +++++++++++++------- common/include/camera/IGimbalController.hpp | 56 +++++++--- 2 files changed, 112 insertions(+), 53 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 764b15b6a..b3a211ee6 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -982,8 +982,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication float32_t4x4 view[CamerazCount], projection[ProjectionsCount], inModel[2u], outModel[2u], outDeltaTRS[2u]; } imguizmoM16InOut; - //const auto& projectionMatrix = projection->getMatrix(); - const auto& firstcamera = cameraz.front(); const auto& secondcamera = cameraz.back(); @@ -995,10 +993,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication for(uint32_t i = 0u; i < ProjectionsCount; ++i) imguizmoM16InOut.projection[i] = transpose(projections[i]->getMatrix()); - // wth its kinda column major but seems to interprete RS part as row major? - // will think of it later and check what its doing under the hood, now it - // seems to match my local base orientation correctly - auto toRetardedImguizmoTRSInput = [&](const float32_t3x4& nablaTrs) -> float32_t4x4 + // TODO: need to inspect where I'm wrong, workaround + auto gimbalToImguizmoTRS = [&](const float32_t3x4& nablaTrs) -> float32_t4x4 { // *do not transpose whole matrix*, only the translate part float32_t4x4 trs = getMatrix3x4As4x4(nablaTrs); @@ -1010,10 +1006,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication return trs; }; + const auto secondCameraGimbalModel = secondcamera->getGimbal()(); + // we will transform a scene object's model - imguizmoM16InOut.inModel[0] = toRetardedImguizmoTRSInput(m_model); + imguizmoM16InOut.inModel[0] = transpose(getMatrix3x4As4x4(m_model)); // and second camera's model too - imguizmoM16InOut.inModel[1] = toRetardedImguizmoTRSInput(secondcamera->getGimbal()()); + imguizmoM16InOut.inModel[1] = gimbalToImguizmoTRS(secondCameraGimbalModel); { float* views[] { @@ -1032,13 +1030,16 @@ class UISampleApp final : public examples::SimpleWindowedApplication for(uint32_t i = 0u; i < ProjectionsCount; ++i) imguizmoM16InOut.projection[i][1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ - float* lastUsedModel = &imguizmoM16InOut.inModel[lastUsing][0][0]; + float* lastUsedModel = &imguizmoM16InOut.inModel[lastManipulatedModelIx][0][0]; // ImGuizmo manipulations on the last used model matrix TransformEditor(lastUsedModel); uint32_t gizmoID = {}; bool manipulatedFromAnyWindow = false; + ImGuizmo::AllowAxisFlip(false); + ImGuizmo::Enable(true); + // we have 2 GUI windows we render into with FBOs for (uint32_t windowIndex = 0; windowIndex < 2u; ++windowIndex) { @@ -1051,13 +1052,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication // however note it only makes sense to obsly assume we cannot manipulate 2 objects at the same time for (uint32_t matId = 0; matId < 2; matId++) { - //if(matId == 0) - // ImGuizmo::AllowAxisFlip(true); - //else - ImGuizmo::AllowAxisFlip(false); - - // and because of imguizmo API usage to achive it we must work on copies & filter the output (unless we try enable/disable logic described bellow) - // -> in general again we need to be careful to not edit the same model twice + // future logic will need to filter gizmos which represents gimbal of camera which view matrix we use to render scene with + if (gizmoID == 3) + continue; + + // and because of imguizmo API usage to achieve it we must work on copies & filter the output (unless we try gizmo enable/disable) + // -> in general we need to be careful to not edit the same model twice auto model = imguizmoM16InOut.inModel[matId]; float32_t4x4 deltaOutputTRS; @@ -1068,19 +1068,49 @@ class UISampleApp final : public examples::SimpleWindowedApplication const bool success = ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, &model[0][0], &deltaOutputTRS[0][0], useSnap ? &snap[0] : NULL); // we manipulated a gizmo from a X-th window, now we update output matrices and assume no more gizmos can be manipulated at the same frame - // here we could also extend the logic & keep track of the gizmo being handled and as long as its manipulated (hold by mouse) -> then given ID of this gizmo we would - // enable it & disable all other gizmos till we finish the manipulation if (!manipulatedFromAnyWindow) { - imguizmoM16InOut.outModel[matId] = model; - imguizmoM16InOut.outDeltaTRS[matId] = deltaOutputTRS; + // TMP, imguizmo controller doesnt support rotation & scale yet + auto discard = gizmoID == 1 && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; + if (!discard) + { + imguizmoM16InOut.outModel[matId] = model; + imguizmoM16InOut.outDeltaTRS[matId] = deltaOutputTRS; + } } if (success) manipulatedFromAnyWindow = true; - + if (ImGuizmo::IsUsing()) - lastUsing = matId; + { + lastManipulatedModelIx = matId; + lastManipulatedGizmoIx = gizmoID; + } + + if (ImGuizmo::IsOver()) + { + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); + + ImVec2 mousePos = io.MousePos; + ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); + + ImGui::Begin("InfoOverlay", NULL, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings); + + ImGui::Text("Type: %s", matId == 0 ? "Geometry Creator Object" : "Camera FPS"); + ImGui::Text("Model ID: %u", matId); + ImGui::Text("Gizmo ID: %u", gizmoID); + + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } ImGuizmo::PopID(); ++gizmoID; @@ -1091,7 +1121,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } /* - testing imguizmo controller, we use deltra TRS matrix to generate virtual events + testing imguizmo controller for second camera, we use delta world imguizmo TRS matrix to generate virtual events */ { @@ -1107,25 +1137,31 @@ class UISampleApp final : public examples::SimpleWindowedApplication IGimbalController::SUpdateParameters params; - params.imguizmoEvents = { { imguizmoM16InOut.outDeltaTRS[1u] } }; + auto getOrientationBasis = [&]() + { + auto orientationBasis = (float32_t3x3(1.f)); + + switch (mCurrentGizmoMode) + { + case ImGuizmo::LOCAL: + { + orientationBasis = (float32_t3x3(secondCameraGimbalModel)); + } break; + + case ImGuizmo::WORLD: break; + default: break; + } + return orientationBasis; + }; + + params.imguizmoEvents = { { std::make_pair(imguizmoM16InOut.outDeltaTRS[1u], getOrientationBasis()) } }; secondcamera->process(virtualEvents.data(), vCount, params); } secondcamera->endInputProcessing(); // TOOD: this needs debugging secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); - /* - if(vCount) - switch (mCurrentGizmoMode) - { - case ImGuizmo::MODE::LOCAL: - secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); break; - case ImGuizmo::MODE::WORLD: - secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); break; - default: break; - } - */ } // update scenes data @@ -1588,7 +1624,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::OPERATION mCurrentGizmoOperation = ImGuizmo::TRANSLATE; ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::WORLD; float snap[3] = { 1.f, 1.f, 1.f }; - int lastUsing = 0; + int lastManipulatedModelIx = 0; + int lastManipulatedGizmoIx = 0; bool firstFrame = true; }; diff --git a/common/include/camera/IGimbalController.hpp b/common/include/camera/IGimbalController.hpp index 1aae6617b..b3f3b5917 100644 --- a/common/include/camera/IGimbalController.hpp +++ b/common/include/camera/IGimbalController.hpp @@ -101,7 +101,7 @@ class IGimbalController : public IGimbalManipulateEncoder using input_mouse_event_t = ui::SMouseEvent; //! input of ImGuizmo gimbal controller process utility - ImGuizmo manipulate utility produces "delta (TRS) matrix" events - using input_imguizmo_event_t = float32_t4x4; + using input_imguizmo_event_t = std::pair; void beginInputProcessing(const std::chrono::microseconds nextPresentationTimeStamp) { @@ -292,31 +292,53 @@ class IGimbalController : public IGimbalManipulateEncoder { preprocess(m_imguizmoVirtualEventMap); - for (const auto& deltaMatrix : events) + for (const auto& ev : events) { - std::array dTranslation, dRotation, dScale; + // TODO: debug assert "orientationBasis" is orthonormal + const auto& [deltaWorldTRS, orientationBasis] = ev; - // TODO: since I assume the delta matrix is from imguizmo I must follow its API (but this one I could actually steal & rewrite to Nabla to not call imguizmo stuff here) - ImGuizmo::DecomposeMatrixToComponents(&deltaMatrix[0][0], dTranslation.data(), dRotation.data(), dScale.data()); + struct + { + float32_t3 dTranslation, dRotation, dScale; + } world, local; // its important to notice our imguizmo deltas are written in world base, so I will assume you generate events with respect for input local basis + + // TODO: since I assume the delta matrix is from imguizmo I must follow its API (but this one I could actually steal & rewrite to Nabla to not call imguizmo stuff here <- its TEMP) + ImGuizmo::DecomposeMatrixToComponents(&deltaWorldTRS[0][0], &world.dTranslation[0], &world.dRotation[0], &world.dScale[0]); + + // FP precision threshold, lets filter small artifacts for this event generator to be more accurate + constexpr float threshold = 1e-8f; - // note that translating imguizmo delta matrix to gimbal_event_t events is indeed hardcoded, but what - // *isn't* is those gimbal_event_t events are then translated according to m_imguizmoVirtualEventMap - // user defined map to + auto filterDelta = [](const float32_t3& in) -> float32_t3 + { + return + { + (std::abs(in[0]) > threshold) ? in[0] : 0.0f, + (std::abs(in[1]) > threshold) ? in[1] : 0.0f, + (std::abs(in[2]) > threshold) ? in[2] : 0.0f + }; + }; + + local.dTranslation = filterDelta(mul(orientationBasis, world.dTranslation)); + + // TODO + local.dRotation = { 0.f, 0.f, 0.f }; + // TODO + local.dScale = { 1.f, 1.f, 1.f }; // Delta translation impulse - requestMagnitudeUpdateWithScalar(0.f, dTranslation[0], dTranslation[0], gimbal_event_t::MoveRight, gimbal_event_t::MoveLeft, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(0.f, dTranslation[1], dTranslation[1], gimbal_event_t::MoveUp, gimbal_event_t::MoveDown, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(0.f, dTranslation[2], dTranslation[2], gimbal_event_t::MoveForward, gimbal_event_t::MoveBackward, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, local.dTranslation[0], std::abs(local.dTranslation[0]), gimbal_event_t::MoveRight, gimbal_event_t::MoveLeft, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, local.dTranslation[1], std::abs(local.dTranslation[1]), gimbal_event_t::MoveUp, gimbal_event_t::MoveDown, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, local.dTranslation[2], std::abs(local.dTranslation[2]), gimbal_event_t::MoveForward, gimbal_event_t::MoveBackward, m_imguizmoVirtualEventMap); // Delta rotation impulse - requestMagnitudeUpdateWithScalar(0.f, dRotation[0], dRotation[0], gimbal_event_t::PanRight, gimbal_event_t::PanLeft, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(0.f, dRotation[1], dRotation[1], gimbal_event_t::TiltUp, gimbal_event_t::TiltDown, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(0.f, dRotation[2], dRotation[2], gimbal_event_t::RollRight, gimbal_event_t::RollLeft, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, local.dRotation[0], std::abs(local.dRotation[0]), gimbal_event_t::PanRight, gimbal_event_t::PanLeft, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, local.dRotation[1], std::abs(local.dRotation[1]), gimbal_event_t::TiltUp, gimbal_event_t::TiltDown, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, local.dRotation[2], std::abs(local.dRotation[2]), gimbal_event_t::RollRight, gimbal_event_t::RollLeft, m_imguizmoVirtualEventMap); // Delta scale impulse - requestMagnitudeUpdateWithScalar(1.f, dScale[0], dScale[0], gimbal_event_t::ScaleXInc, gimbal_event_t::ScaleXDec, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(1.f, dScale[1], dScale[1], gimbal_event_t::ScaleYInc, gimbal_event_t::ScaleYDec, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(1.f, dScale[2], dScale[2], gimbal_event_t::ScaleZInc, gimbal_event_t::ScaleZDec, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(1.f, local.dScale[0], std::abs(local.dScale[0]), gimbal_event_t::ScaleXInc, gimbal_event_t::ScaleXDec, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(1.f, local.dScale[1], std::abs(local.dScale[1]), gimbal_event_t::ScaleYInc, gimbal_event_t::ScaleYDec, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(1.f, local.dScale[2], std::abs(local.dScale[2]), gimbal_event_t::ScaleZInc, gimbal_event_t::ScaleZDec, m_imguizmoVirtualEventMap); } postprocess(m_imguizmoVirtualEventMap, output, count); From 8324b19ecd53c0db71dc64843dd9e1f3b22d05f1 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 4 Dec 2024 15:19:58 +0100 Subject: [PATCH 46/84] fix ManipulationMode::World manipulations, treat impulses as relative deltas regardless different world basis --- 61_UI/main.cpp | 21 +++++++++------------ common/include/CFPSCamera.hpp | 27 +++++++-------------------- common/include/ICamera.hpp | 2 +- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index b3a211ee6..e420ffc81 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -894,7 +894,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SameLine(); if (ImGui::RadioButton("RH", !isLH)) - isLH = false; + isLH = true; // TMP WIP + //isLH = false; //if (ImGui::RadioButton("Perspective", isPerspective)) // isPerspective = true; @@ -1070,7 +1071,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // we manipulated a gizmo from a X-th window, now we update output matrices and assume no more gizmos can be manipulated at the same frame if (!manipulatedFromAnyWindow) { - // TMP, imguizmo controller doesnt support rotation & scale yet + // TMP WIP, our imguizmo controller doesnt support rotation & scale yet auto discard = gizmoID == 1 && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; if (!discard) { @@ -1127,6 +1128,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { static std::vector virtualEvents(0x45); uint32_t vCount; + auto nblManipulationMode = ICamera::Local; secondcamera->beginInputProcessing(m_nextPresentationTimestamp); { @@ -1143,13 +1145,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication switch (mCurrentGizmoMode) { - case ImGuizmo::LOCAL: - { - orientationBasis = (float32_t3x3(secondCameraGimbalModel)); - } break; - - case ImGuizmo::WORLD: break; - default: break; + case ImGuizmo::LOCAL: orientationBasis = (float32_t3x3(secondCameraGimbalModel)); break; + case ImGuizmo::WORLD: nblManipulationMode = ICamera::World; break; + default: assert(false); break; } return orientationBasis; @@ -1160,8 +1158,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } secondcamera->endInputProcessing(); - // TOOD: this needs debugging - secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); + secondcamera->manipulate({ virtualEvents.data(), vCount }, nblManipulationMode); } // update scenes data @@ -1326,7 +1323,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication addMatrixTable("Position", ("PositionTable" + idxstr).c_str(), 1, 3, &position[0], false); addMatrixTable("Orientation (Quaternion)", ("OrientationTable" + idxstr).c_str(), 1, 4, &orientation[0], false); addMatrixTable("View Matrix", ("ViewMatrixTable" + idxstr).c_str(), 3, 4, &viewMatrix[0][0], false); - addMatrixTable("TRS Matrix", ("TRSMatrixTable" + idxstr).c_str(), 3, 4, &trsMatrix[0][0], true); + //addMatrixTable("TRS Matrix", ("TRSMatrixTable" + idxstr).c_str(), 3, 4, &trsMatrix[0][0], true); ImGui::EndTable(); } diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp index 90e01b4e7..ae887aa0c 100644 --- a/common/include/CFPSCamera.hpp +++ b/common/include/CFPSCamera.hpp @@ -42,26 +42,13 @@ class CFPSCamera final : public ICamera glm::quat newOrientation; vector newPosition; - switch (mode) - { - case base_t::Local: - { - const auto impulse = m_gimbal.accumulate(virtualEvents); - const auto newPitch = std::clamp(gPitch + impulse.dVirtualRotation.x * RotateSpeedScale, MinVerticalAngle, MaxVerticalAngle), newYaw = gYaw + impulse.dVirtualRotation.y * RotateSpeedScale; - newOrientation = glm::quat(glm::vec3(newPitch, newYaw, 0.0f)); newPosition = m_gimbal.getPosition() + impulse.dVirtualTranslate * MoveSpeedScale; - } break; - - case base_t::World: - { - // TODO: I make assumption what world base is now (at least temporary), I need to think of this but maybe each ITransformObject should know what its world is - // in ideal scenario we would define this crazy enum with all possible standard bases but in Nabla we only really care about LH/RH coordinate systems - const auto transform = m_gimbal.accumulate(virtualEvents, { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }); - const auto newPitch = std::clamp(transform.dVirtualRotation.x, MinVerticalAngle, MaxVerticalAngle), newYaw = transform.dVirtualRotation.y; - newOrientation = glm::quat(glm::vec3(newPitch, newYaw, 0.0f)); newPosition = transform.dVirtualTranslate; - } break; - - default: return false; - } + // TODO: I make assumption what world base is now (at least temporary), I need to think of this but maybe each ITransformObject should know what its world is + // in ideal scenario we would define this crazy enum with all possible standard bases + const auto impulse = mode == base_t::Local ? m_gimbal.accumulate(virtualEvents) + : m_gimbal.accumulate(virtualEvents, { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }); + + const auto newPitch = std::clamp(gPitch + impulse.dVirtualRotation.x * RotateSpeedScale, MinVerticalAngle, MaxVerticalAngle), newYaw = gYaw + impulse.dVirtualRotation.y * RotateSpeedScale; + newOrientation = glm::quat(glm::vec3(newPitch, newYaw, 0.0f)); newPosition = m_gimbal.getPosition() + impulse.dVirtualTranslate * MoveSpeedScale; bool manipulated = true; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp index b9a5eca8c..c2980676c 100644 --- a/common/include/ICamera.hpp +++ b/common/include/ICamera.hpp @@ -24,7 +24,7 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted // Interpret virtual events as accumulated impulse representing relative manipulation with respect to view gimbal base Local, - // Interpret virtual events as accumulated absolute manipulation with respect to world base + // Interpret virtual events as accumulated impulse representing relative manipulation with respect to world base World }; From 5fd1d45c53bd89d851564d1e59bdd983ab6889bb Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 5 Dec 2024 21:57:20 +0100 Subject: [PATCH 47/84] save work to continue tomorrow - start adjusting to comments regarding projections and re-think the design. `IProjection` is now indeed for any type of projection & only provides interface to project/unproject (inverse can fail we return a bool); `ILinearProjection` & `IQuadProjection` provide interfaces for span of their `CProjection`s (custom projection structs inheriting from `IProjection` allowing C class types to have any generic container/range type to store projections. `CCubeProjection` inherits from `IQuadProjection` and we have 6 quads each represented by a projection which is pre-transform nonlinear/skewed projection matrix concatenated with linear projection matrix (viewport) --- 61_UI/include/common.hpp | 3 + 61_UI/main.cpp | 11 +- common/include/camera/CCubeProjection.hpp | 39 +++---- common/include/{ => camera}/CFPSCamera.hpp | 0 common/include/camera/CLinearProjection.hpp | 30 ++++++ common/include/{ => camera}/ICamera.hpp | 13 ++- common/include/camera/ILinearProjection.hpp | 82 ++++++++++++--- common/include/camera/IProjection.hpp | 108 +++++++++----------- common/include/camera/IQuadProjection.hpp | 43 ++++++++ common/include/{ => camera}/IRange.hpp | 0 10 files changed, 227 insertions(+), 102 deletions(-) rename common/include/{ => camera}/CFPSCamera.hpp (100%) create mode 100644 common/include/camera/CLinearProjection.hpp rename common/include/{ => camera}/ICamera.hpp (90%) create mode 100644 common/include/camera/IQuadProjection.hpp rename common/include/{ => camera}/IRange.hpp (100%) diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index c5a1860da..f9e297858 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -10,6 +10,9 @@ #include "SimpleWindowedApplication.hpp" #include "InputSystem.hpp" +#include "camera/ILinearProjection.hpp" +#include "camera/IQuadProjection.hpp" + // the example's headers #include "nbl/ui/ICursorControl.h" #include "nbl/ext/ImGui/ImGui.h" diff --git a/61_UI/main.cpp b/61_UI/main.cpp index e420ffc81..9affd29f1 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -905,7 +905,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication //if (ImGui::RadioButton("Orthographic", !isPerspective)) // isPerspective = false; - ImGui::Checkbox("Enable camera movement", &move); ImGui::SliderFloat("Move speed", &moveSpeed, 0.1f, 10.f); ImGui::SliderFloat("Rotate speed", &rotateSpeed, 0.1f, 10.f); @@ -926,6 +925,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } else { + /* ImGui::Text(ImGuizmo::IsOver() ? "Over gizmo" : ""); ImGui::SameLine(); ImGui::Text(ImGuizmo::IsOver(ImGuizmo::TRANSLATE) ? "Over translate gizmo" : ""); @@ -933,6 +933,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Text(ImGuizmo::IsOver(ImGuizmo::ROTATE) ? "Over rotate gizmo" : ""); ImGui::SameLine(); ImGui::Text(ImGuizmo::IsOver(ImGuizmo::SCALE) ? "Over scale gizmo" : ""); + */ } ImGui::Separator(); @@ -995,11 +996,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication imguizmoM16InOut.projection[i] = transpose(projections[i]->getMatrix()); // TODO: need to inspect where I'm wrong, workaround - auto gimbalToImguizmoTRS = [&](const float32_t3x4& nablaTrs) -> float32_t4x4 + auto gimbalToImguizmoTRS = [&](const float32_t3x4& nblGimbalTrs) -> float32_t4x4 { // *do not transpose whole matrix*, only the translate part - float32_t4x4 trs = getMatrix3x4As4x4(nablaTrs); - trs[3] = float32_t4(nablaTrs[0][3], nablaTrs[1][3], nablaTrs[2][3], 1.f); + float32_t4x4 trs = getMatrix3x4As4x4(nblGimbalTrs); + trs[3] = float32_t4(nblGimbalTrs[0][3], nblGimbalTrs[1][3], nblGimbalTrs[2][3], 1.f); trs[0][3] = 0.f; trs[1][3] = 0.f; trs[2][3] = 0.f; @@ -1498,7 +1499,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(400, 20 + wCameraIndex * 420), ImGuiCond_Appearing); ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); - std::string ident = "Active Camera ID: " + std::to_string(wCameraIndex); + std::string ident = "Rendered from Camera \"" + std::to_string(wCameraIndex) + "\" Ix perspective"; ImGui::Begin(ident.data(), 0, gizmoWindowFlags); ImGuizmo::SetDrawlist(); diff --git a/common/include/camera/CCubeProjection.hpp b/common/include/camera/CCubeProjection.hpp index e09d01dce..0bd6e19a7 100644 --- a/common/include/camera/CCubeProjection.hpp +++ b/common/include/camera/CCubeProjection.hpp @@ -1,34 +1,37 @@ #ifndef _NBL_CCUBE_PROJECTION_HPP_ #define _NBL_CCUBE_PROJECTION_HPP_ -#include "ILinearProjection.hpp" +#include "IRange.hpp" +#include "IQuadProjection.hpp" namespace nbl::hlsl { -template -struct CCubeProjectionConstraints -{ - using matrix_t = typename T; - using projection_t = typename IProjection; - using projection_range_t = std::array, 6u>; - using base_t = ILinearProjection; -}; - -//! Class providing linear cube projections with projection matrix per face of a cube, each projection matrix represents a single view-port -template -class CCubeProjection : public CCubeProjectionConstraints::base_t +// A projection where each cube face is a quad we project onto +class CCubeProjection final : public IQuadProjection { public: - using constraints_t = CCubeProjectionConstraints; - using base_t = typename constraints_t::base_t; + static inline constexpr auto CubeFaces = 6u; + + inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) + { + if (!camera) + return nullptr; - CCubeProjection(constraints_t::projection_range_t&& projections = {}) : base_t(std::move(projections)) {} + return core::smart_refctd_ptr(new CCubeProjection(core::smart_refctd_ptr(camera)), core::dont_grab); + } - constraints_t::projection_range_t& getCubeFaceProjections() + virtual std::span getQuadProjections() const { - return base_t::getViewportProjections(); + return m_cubefaceProjections; } + +private: + CCubeProjection(core::smart_refctd_ptr&& camera) + : IQuadProjection(core::smart_refctd_ptr(camera)) {} + virtual ~CCubeProjection() = default; + + std::array m_cubefaceProjections; }; } // nbl::hlsl namespace diff --git a/common/include/CFPSCamera.hpp b/common/include/camera/CFPSCamera.hpp similarity index 100% rename from common/include/CFPSCamera.hpp rename to common/include/camera/CFPSCamera.hpp diff --git a/common/include/camera/CLinearProjection.hpp b/common/include/camera/CLinearProjection.hpp new file mode 100644 index 000000000..6dbe41b82 --- /dev/null +++ b/common/include/camera/CLinearProjection.hpp @@ -0,0 +1,30 @@ +#ifndef _NBL_C_LINEAR_PROJECTION_HPP_ +#define _NBL_C_LINEAR_PROJECTION_HPP_ + +#include "ILinearProjection.hpp" + +namespace nbl::hlsl +{ + +class CLinearProjection : public ILinearProjection +{ +public: + using ILinearProjection::ILinearProjection; + + inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) + { + if (!camera) + return nullptr; + + return core::smart_refctd_ptr(new CLinearProjection(core::smart_refctd_ptr(camera)), core::dont_grab); + } + +private: + CLinearProjection(core::smart_refctd_ptr&& camera) + : ILinearProjection(core::smart_refctd_ptr(camera)) {} + virtual ~CLinearProjection() = default; +}; + +} // nbl::hlsl namespace + +#endif // _NBL_C_LINEAR_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/ICamera.hpp b/common/include/camera/ICamera.hpp similarity index 90% rename from common/include/ICamera.hpp rename to common/include/camera/ICamera.hpp index c2980676c..d4b3dbe8c 100644 --- a/common/include/ICamera.hpp +++ b/common/include/camera/ICamera.hpp @@ -10,12 +10,11 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { -template class ICamera : public IGimbalController, virtual public core::IReferenceCounted { public: + using precision_t = float64_t; using IGimbalController::IGimbalController; - using precision_t = T; //! Manipulation mode for virtual events //! TODO: this should belong to IObjectTransform or something @@ -46,17 +45,17 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted { const auto& gRight = base_t::getXAxis(), gUp = base_t::getYAxis(), gForward = base_t::getZAxis(); - auto isNormalized = [](const auto& v, float epsilon) -> bool + auto isNormalized = [](const auto& v, precision_t epsilon) -> bool { - return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); + return glm::epsilonEqual(glm::length(v), 1.0, epsilon); }; - auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool + auto isOrthogonal = [](const auto& a, const auto& b, precision_t epsilon) -> bool { - return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); + return glm::epsilonEqual(glm::dot(a, b), 0.0, epsilon); }; - auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool + auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, precision_t epsilon = 1e-6) -> bool { return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp index 30a4db358..a03e15cb4 100644 --- a/common/include/camera/ILinearProjection.hpp +++ b/common/include/camera/ILinearProjection.hpp @@ -1,29 +1,83 @@ -#ifndef _NBL_ILINEAR_PROJECTION_HPP_ -#define _NBL_ILINEAR_PROJECTION_HPP_ +#ifndef _NBL_I_LINEAR_PROJECTION_HPP_ +#define _NBL_I_LINEAR_PROJECTION_HPP_ #include "IProjection.hpp" +#include "ICamera.hpp" namespace nbl::hlsl { -//! Interface class for linear projections range storage - a projection matrix represents single view-port -template -class ILinearProjection : protected IProjectionRange +//! Interface class for linear projections like Perspective, Orthographic, Oblique, Axonometric, Shear projections or any custom linear transformation - any linear projection represents transform to a viewport +class ILinearProjection { public: - using base_t = typename IProjectionRange; - using range_t = typename base_t::range_t; - using projection_t = typename base_t::projection_t; + struct CViewportProjection : public IProjection + { + using IProjection::IProjection; + + //! underlying type for linear projection matrix type + using projection_matrix_t = float64_t4x4; + + inline void setProjectionMatrix(const projection_matrix_t& matrix) + { + m_projectionMatrix = matrix; + const auto det = hlsl::determinant(m_projectionMatrix); + + // no singularity for linear projections, but we need to handle it somehow! + m_isProjectionSingular = not det; + + if (m_isProjectionSingular) + { + m_isProjectionLeftHanded = std::nullopt; + m_invProjectionMatrix = std::nullopt; + } + else + { + m_isProjectionLeftHanded = det < 0.0; + m_invProjectionMatrix = inverse(m_projectionMatrix); + } + } + + inline const projection_matrix_t& getProjectionMatrix() const { return m_projectionMatrix; } + inline const std::optional& getInvProjectionMatrix() const { return m_invProjectionMatrix; } + inline const std::optional& isProjectionLeftHanded() const { return m_isProjectionLeftHanded; } + inline bool isProjectionSingular() const { return m_isProjectionSingular; } + virtual ProjectionType getProjectionType() const override { return ProjectionType::Linear; } + + virtual void project(const projection_vector_t& vecToProjectionSpace, projection_vector_t& output) const override + { + output = mul(m_projectionMatrix, vecToProjectionSpace); + } - ILinearProjection(range_t&& projections) : base_t(std::move(projections)) {} + virtual bool unproject(const projection_vector_t& vecFromProjectionSpace, projection_vector_t& output) const override + { + if (m_isProjectionSingular) + return false; + + output = mul(m_invProjectionMatrix.value(), vecFromProjectionSpace); + + return true; + } + + private: + projection_matrix_t m_projectionMatrix; + std::optional m_invProjectionMatrix; + std::optional m_isProjectionLeftHanded; + bool m_isProjectionSingular; + }; + + using CProjection = CViewportProjection; + + virtual std::span getViewportProjections() const = 0; protected: - inline range_t& getViewportProjections() - { - return base_t::m_projectionRange; - } + ILinearProjection(core::smart_refctd_ptr&& camera) + : m_camera(core::smart_refctd_ptr(camera)) {} + virtual ~ILinearProjection() = default; + + core::smart_refctd_ptr m_camera; }; } // nbl::hlsl namespace -#endif // _NBL_ILINEAR_PROJECTION_HPP_ \ No newline at end of file +#endif // _NBL_I_LINEAR_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index 8de5c19f6..eb985c1b0 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -1,70 +1,62 @@ -#ifndef _NBL_IPROJECTION_HPP_ -#define _NBL_IPROJECTION_HPP_ +#ifndef _NBL_I_PROJECTION_HPP_ +#define _NBL_I_PROJECTION_HPP_ -#include "nbl/builtin/hlsl/cpp_compat/matrix.hlsl" -#include "IRange.hpp" +#include namespace nbl::hlsl { -template -concept ProjectionMatrix = is_any_of_v; - -//! Interface class for projection -template -class IProjection : virtual public core::IReferenceCounted +//! Interface class for any type of projection +class IProjection { public: - using value_t = T; - - IProjection(const value_t& matrix = {}) : m_projectionMatrix(matrix) { updateHandnessState(); } - - inline void setMatrix(const value_t& projectionMatrix) - { - m_projectionMatrix = projectionMatrix; - updateHandnessState(); - } - - inline const value_t& getMatrix() { return m_projectionMatrix; } - inline bool isLeftHanded() { return m_isLeftHanded; } + //! underlying type for all vectors we project or un-project (inverse), projections *may* transform vectors in less dimensions + using projection_vector_t = float64_t4; -private: - inline void updateHandnessState() + enum class ProjectionType { - m_isLeftHanded = hlsl::determinant(m_projectionMatrix) < 0.f; - } - - value_t m_projectionMatrix; - bool m_isLeftHanded; -}; - -template -struct is_projection : std::false_type {}; - -template -struct is_projection> : std::true_type {}; - -template -inline constexpr bool is_projection_v = is_projection::value; - -template -concept ProjectionRange = GeneralPurposeRange && requires -{ - requires core::is_smart_refctd_ptr_v>; - requires is_projection_v::pointee>; -}; - -//! Interface class for a range of IProjection projections -template>, 1u>> -class IProjectionRange : public IRange -{ -public: - using base_t = IRange; - using range_t = typename base_t::range_t; - using projection_t = typename base_t::range_value_t; - - IProjectionRange(range_t&& projections) : base_t(std::move(projections)) {} - const range_t& getProjections() const { return base_t::m_range; } + //! Perspective, Orthographic, Oblique, Axonometric, Shear projections or any custom linear transformation - a linear projection represents transform to a view-port + Linear, + + //! Represents a non-linear/skewed pre-transform *concatenated* with linear view-port transform + Quad, + + Spherical, + ThinLens, + + Count + }; + + IProjection() = default; + virtual ~IProjection() = default; + + /** + * @brief Transforms a vector from its input space into the projection space. + * + * @param "vecToProjectionSpace" is a vector to transform from its space into projection space. + * @param "output" is a vector which is "vecToProjectionSpace" transformed into projection space. + * @return The vector in projection space. + */ + virtual void project(const projection_vector_t& vecToProjectionSpace, projection_vector_t& output) const = 0; + + /** + * @brief Transforms a vector from the projection space back to the original space. + * Note the inverse transform may fail because original projection may be singular. + * + * @param "vecFromProjectionSpace" is a vector in the projection space to transform back to original space. + * @param "output" is a vector which is "vecFromProjectionSpace" transformed back to its original space. + * @return The vector in the original space. + */ + virtual bool unproject(const projection_vector_t& vecFromProjectionSpace, projection_vector_t& output) const = 0; + + /** + * @brief Returns the specific type of the projection + * (e.g., linear, spherical, thin-lens) as defined by the + * ProjectionType enumeration. + * + * @return The type of this projection. + */ + virtual ProjectionType getProjectionType() const = 0; }; } // namespace nbl::hlsl diff --git a/common/include/camera/IQuadProjection.hpp b/common/include/camera/IQuadProjection.hpp new file mode 100644 index 000000000..28e973572 --- /dev/null +++ b/common/include/camera/IQuadProjection.hpp @@ -0,0 +1,43 @@ +#ifndef _NBL_I_QUAD_PROJECTION_HPP_ +#define _NBL_I_QUAD_PROJECTION_HPP_ + +#include "ILinearProjection.hpp" + +namespace nbl::hlsl +{ + +//! Interface class for quad projections, basically represents a non-linear/skewed pre-transform concatenated with linear viewport transform, think of it as single quad of a cave designer +class IQuadProjection +{ +public: + struct CCaveFaceProjection : public ILinearProjection::CViewportProjection + { + using base_t = ILinearProjection::CViewportProjection; + + //! underlying type for pre-transform projection matrix type + using pretransform_matrix_t = float64_t3x4; + + inline void setProjectionMatrix(const pretransform_matrix_t& pretransform, const base_t::projection_matrix_t& viewport) + { + auto projection = mul(pretransform, viewport); + base_t::setProjectionMatrix(getMatrix3x4As4x4(projection)); + } + + // TODO: could store "pretransform" & "viewport", may be useful to combine with camera and extract matrices + }; + + using CProjection = CCaveFaceProjection; + + virtual std::span getQuadProjections() const = 0; + +protected: + IQuadProjection(core::smart_refctd_ptr&& camera) + : m_camera(core::smart_refctd_ptr(camera)) {} + virtual ~IQuadProjection() = default; + + core::smart_refctd_ptr m_camera; +}; + +} // nbl::hlsl namespace + +#endif // _NBL_I_QUAD_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/IRange.hpp b/common/include/camera/IRange.hpp similarity index 100% rename from common/include/IRange.hpp rename to common/include/camera/IRange.hpp From c5f32872a07eb4fc0c79b446b13276404fdaca49 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 6 Dec 2024 10:40:17 +0100 Subject: [PATCH 48/84] make CCubeProjection use IQuadProjection & IProjection interface, update ILinearProjection to split out `MVP`, `MV`, `MV-1`, `MVP-1` matrices (`P`, `P-1` are returned by `ILinearProjection::CProjection` getters) --- 61_UI/include/common.hpp | 2 +- common/include/camera/CCubeProjection.hpp | 67 ++++++++++-- common/include/camera/IGimbal.hpp | 4 +- common/include/camera/ILinearProjection.hpp | 113 +++++++++++++++++--- common/include/camera/IProjection.hpp | 3 + common/include/camera/IQuadProjection.hpp | 46 ++++---- 6 files changed, 192 insertions(+), 43 deletions(-) diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index f9e297858..92335efa8 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -6,7 +6,7 @@ #include // common api -#include "CFPSCamera.hpp" +#include "camera/CFPSCamera.hpp" #include "SimpleWindowedApplication.hpp" #include "InputSystem.hpp" diff --git a/common/include/camera/CCubeProjection.hpp b/common/include/camera/CCubeProjection.hpp index 0bd6e19a7..9c3fae30b 100644 --- a/common/include/camera/CCubeProjection.hpp +++ b/common/include/camera/CCubeProjection.hpp @@ -7,11 +7,40 @@ namespace nbl::hlsl { -// A projection where each cube face is a quad we project onto -class CCubeProjection final : public IQuadProjection +/** +* @brief A projection where each cube face is a quad we project onto. +* +* Represents a cube projection given direction vector where each face of +* the cube is treated as a quad. The projection onto the cube is done using +* these quads and each face has its own unique pre-transform and +* view-port linear matrix. +*/ +class CCubeProjection final : public IQuadProjection, public IProjection { public: - static inline constexpr auto CubeFaces = 6u; + //! Represents six face identifiers of a cube. + enum CubeFaces : uint8_t + { + //! Cube face in the +X base direction + PositiveX = 0, + + //! Cube face in the -X base direction + NegativeX, + + //! Cube face in the +Y base direction + PositiveY, + + //! Cube face in the -Y base direction + NegativeY, + + //! Cube face in the +Z base direction + PositiveZ, + + //! Cube face in the -Z base direction + NegativeZ, + + CubeFacesCount + }; inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) { @@ -21,9 +50,35 @@ class CCubeProjection final : public IQuadProjection return core::smart_refctd_ptr(new CCubeProjection(core::smart_refctd_ptr(camera)), core::dont_grab); } - virtual std::span getQuadProjections() const + virtual std::span getLinearProjections() const override + { + return { reinterpret_cast(m_quads.data()), m_quads.size() }; + } + + void transformCube() + { + // TODO: update m_quads + } + + virtual ProjectionType getProjectionType() const override { return ProjectionType::Cube; } + + virtual void project(const projection_vector_t& vecToProjectionSpace, projection_vector_t& output) const override + { + auto direction = normalize(vecToProjectionSpace); + + // TODO: project onto cube using quads representing faces + } + + virtual bool unproject(const projection_vector_t& vecFromProjectionSpace, projection_vector_t& output) const override + { + // TODO: return back direction vector? + } + + template + requires (FaceIx != CubeFacesCount) + inline const CProjection& getQuad() { - return m_cubefaceProjections; + return m_quads[FaceIx]; } private: @@ -31,7 +86,7 @@ class CCubeProjection final : public IQuadProjection : IQuadProjection(core::smart_refctd_ptr(camera)) {} virtual ~CCubeProjection() = default; - std::array m_cubefaceProjections; + std::array m_quads; }; } // nbl::hlsl namespace diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index a3d153b92..3fe98d3eb 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -96,6 +96,8 @@ namespace nbl::hlsl { public: using precision_t = T; + //! underlying type for world matrix (TRS) + using model_matrix_t = matrix; struct VirtualImpulse { @@ -303,7 +305,7 @@ namespace nbl::hlsl inline const auto& getScale() const { return m_scale; } //! World matrix (TRS) - inline const matrix operator()() const + inline const model_matrix_t operator()() const { const auto& position = getPosition(); const auto& rotation = getOrthonornalMatrix(); diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp index a03e15cb4..069507f01 100644 --- a/common/include/camera/ILinearProjection.hpp +++ b/common/include/camera/ILinearProjection.hpp @@ -1,4 +1,4 @@ -#ifndef _NBL_I_LINEAR_PROJECTION_HPP_ +#ifndef _NBL_I_LINEAR_PROJECTION_HPP_ #define _NBL_I_LINEAR_PROJECTION_HPP_ #include "IProjection.hpp" @@ -7,23 +7,43 @@ namespace nbl::hlsl { -//! Interface class for linear projections like Perspective, Orthographic, Oblique, Axonometric, Shear projections or any custom linear transformation - any linear projection represents transform to a viewport +/** + * @brief Interface class for any custom linear projection transformation (matrix elements are already evaluated scalars) + * referencing a camera, great for Perspective, Orthographic, Oblique, Axonometric and Shear projections + */ class ILinearProjection { +protected: + ILinearProjection(core::smart_refctd_ptr&& camera) + : m_camera(core::smart_refctd_ptr(camera)) {} + virtual ~ILinearProjection() = default; + + core::smart_refctd_ptr m_camera; public: - struct CViewportProjection : public IProjection + //! underlying type for linear world TRS matrix + using model_matrix_t = typename decltype(m_camera)::pointee::CGimbal::model_matrix_t; + + //! underlying type for linear concatenated matrix + using concatenated_matrix_t = float64_t4x4; + + //! underlying type for linear inverse of concatenated matrix + using inv_concatenated_matrix_t = std::optional; + + struct CProjection : public IProjection { using IProjection::IProjection; + using projection_matrix_t = concatenated_matrix_t; + using inv_projection_matrix_t = inv_concatenated_matrix_t; - //! underlying type for linear projection matrix type - using projection_matrix_t = float64_t4x4; + CProjection(const projection_matrix_t& matrix) { setProjectionMatrix(matrix); } inline void setProjectionMatrix(const projection_matrix_t& matrix) { m_projectionMatrix = matrix; const auto det = hlsl::determinant(m_projectionMatrix); - // no singularity for linear projections, but we need to handle it somehow! + // we will allow you to lose a dimension since such a projection itself *may* + // be valid, however then you cannot un-project because the inverse doesn't exist! m_isProjectionSingular = not det; if (m_isProjectionSingular) @@ -38,8 +58,12 @@ class ILinearProjection } } + //! Returns P (Projection matrix) inline const projection_matrix_t& getProjectionMatrix() const { return m_projectionMatrix; } - inline const std::optional& getInvProjectionMatrix() const { return m_invProjectionMatrix; } + + //! Returns P⁻¹ (Inverse of Projection matrix) *if it exists* + inline const inv_projection_matrix_t& getInvProjectionMatrix() const { return m_invProjectionMatrix; } + inline const std::optional& isProjectionLeftHanded() const { return m_isProjectionLeftHanded; } inline bool isProjectionSingular() const { return m_isProjectionSingular; } virtual ProjectionType getProjectionType() const override { return ProjectionType::Linear; } @@ -61,21 +85,76 @@ class ILinearProjection private: projection_matrix_t m_projectionMatrix; - std::optional m_invProjectionMatrix; + inv_projection_matrix_t m_invProjectionMatrix; std::optional m_isProjectionLeftHanded; bool m_isProjectionSingular; }; - using CProjection = CViewportProjection; - - virtual std::span getViewportProjections() const = 0; + virtual std::span getLinearProjections() const = 0; -protected: - ILinearProjection(core::smart_refctd_ptr&& camera) - : m_camera(core::smart_refctd_ptr(camera)) {} - virtual ~ILinearProjection() = default; - - core::smart_refctd_ptr m_camera; + /** + * @brief Computes Model View (MV) matrix + * @param "model" is world TRS matrix + * @return Returns MV matrix + */ + inline concatenated_matrix_t getMV(const model_matrix_t& model) const + { + const auto& v = m_camera->getGimbal().getView().matrix; + return mul(getMatrix3x4As4x4(v), getMatrix3x4As4x4(model)); + } + + /** + * @brief Computes Model View Projection (MVP) matrix + * @param "projection" is linear projection + * @param "model" is world TRS matrix + * @return Returns MVP matrix + */ + inline concatenated_matrix_t getMVP(const CProjection& projection, const model_matrix_t& model) const + { + const auto& v = m_camera->getGimbal().getView().matrix; + const auto& p = projection.getProjectionMatrix(); + auto mv = mul(getMatrix3x4As4x4(v), getMatrix3x4As4x4(model)); + return mul(p, mv); + } + + /** + * @brief Computes Model View Projection (MVP) matrix + * @param "projection" is linear projection + * @param "mv" is Model View (MV) matrix + * @return Returns MVP matrix + */ + inline concatenated_matrix_t getMVP(const CProjection& projection, const concatenated_matrix_t& mv) const + { + const auto& p = projection.getProjectionMatrix(); + return mul(p, mv); + } + + /** + * @brief Computes Inverse of Model View ((MV)⁻¹) matrix + * @param "mv" is Model View (MV) matrix + * @return Returns ((MV)⁻¹) matrix *if it exists*, otherwise returns std::nullopt + */ + inline inv_concatenated_matrix_t getMVInverse(const model_matrix_t& model) const + { + const auto mv = getMV(model); + if (auto det = determinant(mv); det) + return inverse(mv); + return std::nullopt; + } + + /** + * @brief Computes Inverse of Model View Projection ((MVP)⁻¹) matrix + * @param "projection" is linear projection + * @param "model" is world TRS matrix + * @return Returns ((MVP)⁻¹) matrix *if it exists*, otherwise returns std::nullopt + */ + inline inv_concatenated_matrix_t getMVPInverse(const CProjection& projection, const model_matrix_t& model) const + { + const auto mvp = getMVP(projection, model); + if (auto det = determinant(mvp); det) + return inverse(mvp); + return std::nullopt; + } }; } // nbl::hlsl namespace diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index eb985c1b0..8b3b9337c 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -21,6 +21,9 @@ class IProjection //! Represents a non-linear/skewed pre-transform *concatenated* with linear view-port transform Quad, + //! Represents a projection onto cube consisting of 6 quad cube faces + Cube, + Spherical, ThinLens, diff --git a/common/include/camera/IQuadProjection.hpp b/common/include/camera/IQuadProjection.hpp index 28e973572..7a4d5ebaa 100644 --- a/common/include/camera/IQuadProjection.hpp +++ b/common/include/camera/IQuadProjection.hpp @@ -6,36 +6,46 @@ namespace nbl::hlsl { -//! Interface class for quad projections, basically represents a non-linear/skewed pre-transform concatenated with linear viewport transform, think of it as single quad of a cave designer -class IQuadProjection +/** +* @brief Interface class for quad projections. +* +* This projection transforms a vector into the **model space of a quad** +* (defined by the pre-transform matrix) and then projects it onto the quad using +* the linear view-port transform. +* +* A quad projection is represented by: +* - A **pre-transform matrix** (non-linear/skewed transformation). +* - A **linear view-port transform matrix**. +* +* The final projection matrix is the concatenation of the pre-transform and the linear view-port transform. +* +* @note Single quad projection can represent a face quad of a CAVE-like system. +*/ +class IQuadProjection : public ILinearProjection { public: - struct CCaveFaceProjection : public ILinearProjection::CViewportProjection + struct CProjection : ILinearProjection::CProjection { - using base_t = ILinearProjection::CViewportProjection; + using base_t = ILinearProjection::CProjection; - //! underlying type for pre-transform projection matrix type - using pretransform_matrix_t = float64_t3x4; - - inline void setProjectionMatrix(const pretransform_matrix_t& pretransform, const base_t::projection_matrix_t& viewport) + inline void setQuadTransform(const ILinearProjection::model_matrix_t& pretransform, ILinearProjection::concatenated_matrix_t viewport) { - auto projection = mul(pretransform, viewport); - base_t::setProjectionMatrix(getMatrix3x4As4x4(projection)); + auto concatenated = mul(getMatrix3x4As4x4(pretransform), viewport); + base_t::setProjectionMatrix(concatenated); + + m_pretransform = pretransform; + m_viewport = viewport; } - // TODO: could store "pretransform" & "viewport", may be useful to combine with camera and extract matrices + private: + ILinearProjection::model_matrix_t m_pretransform; + ILinearProjection::concatenated_matrix_t m_viewport; }; - using CProjection = CCaveFaceProjection; - - virtual std::span getQuadProjections() const = 0; - protected: IQuadProjection(core::smart_refctd_ptr&& camera) - : m_camera(core::smart_refctd_ptr(camera)) {} + : ILinearProjection(core::smart_refctd_ptr(camera)) {} virtual ~IQuadProjection() = default; - - core::smart_refctd_ptr m_camera; }; } // nbl::hlsl namespace From 10e08f01abbf02ac83105123a4b4823b44089f67 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 10 Dec 2024 14:37:35 +0100 Subject: [PATCH 49/84] add some getters to IQuadProjection --- common/include/camera/IProjection.hpp | 2 +- common/include/camera/IQuadProjection.hpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index 8b3b9337c..7e145ffee 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -15,7 +15,7 @@ class IProjection enum class ProjectionType { - //! Perspective, Orthographic, Oblique, Axonometric, Shear projections or any custom linear transformation - a linear projection represents transform to a view-port + //! Perspective, Orthographic, Oblique, Axonometric, Shear projections or any custom linear transformation Linear, //! Represents a non-linear/skewed pre-transform *concatenated* with linear view-port transform diff --git a/common/include/camera/IQuadProjection.hpp b/common/include/camera/IQuadProjection.hpp index 7a4d5ebaa..43ba680ec 100644 --- a/common/include/camera/IQuadProjection.hpp +++ b/common/include/camera/IQuadProjection.hpp @@ -28,6 +28,11 @@ class IQuadProjection : public ILinearProjection { using base_t = ILinearProjection::CProjection; + CProjection(const ILinearProjection::model_matrix_t& pretransform, ILinearProjection::concatenated_matrix_t viewport) + { + setQuadTransform(pretransform, viewport); + } + inline void setQuadTransform(const ILinearProjection::model_matrix_t& pretransform, ILinearProjection::concatenated_matrix_t viewport) { auto concatenated = mul(getMatrix3x4As4x4(pretransform), viewport); @@ -37,6 +42,9 @@ class IQuadProjection : public ILinearProjection m_viewport = viewport; } + inline const ILinearProjection::model_matrix_t& getPretransform() const { return m_pretransform; } + inline const ILinearProjection::concatenated_matrix_t& getViewportProjection() const { return m_viewport; } + private: ILinearProjection::model_matrix_t m_pretransform; ILinearProjection::concatenated_matrix_t m_viewport; From 11585e2355b15b6c22cdbdde81c785ed9b8e6b01 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 12 Dec 2024 11:06:38 +0100 Subject: [PATCH 50/84] remove templates from ICamera interface, hardcode 64bit for its gimbal and vector/matrix data, add `getCastedMatrix` & `getCastedVector` to obtain data with specified precision, rename `IQuadProjection` to `IPerspectiveProjection`. Create ContiguousGeneralPurposeRangeOf concept and use in `CLinearProjection` class, adjust main.cpp to new changes and use `CLinearProjection` with fixed `std::array` range in the example --- 61_UI/include/common.hpp | 6 +- 61_UI/include/keysmapping.hpp | 8 +-- 61_UI/main.cpp | 70 ++++++++++--------- common/include/camera/CCubeProjection.hpp | 10 +-- common/include/camera/CFPSCamera.hpp | 17 +++-- common/include/camera/CLinearProjection.hpp | 16 +++++ common/include/camera/ICamera.hpp | 17 +++-- common/include/camera/ILinearProjection.hpp | 18 ++++- ...jection.hpp => IPerspectiveProjection.hpp} | 21 +++--- common/include/camera/IProjection.hpp | 6 +- common/include/camera/IRange.hpp | 17 ++--- 11 files changed, 112 insertions(+), 94 deletions(-) rename common/include/camera/{IQuadProjection.hpp => IPerspectiveProjection.hpp} (71%) diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index 92335efa8..0c33a5813 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -10,8 +10,8 @@ #include "SimpleWindowedApplication.hpp" #include "InputSystem.hpp" -#include "camera/ILinearProjection.hpp" -#include "camera/IQuadProjection.hpp" +#include "camera/CCubeProjection.hpp" +#include "camera/CLinearProjection.hpp" // the example's headers #include "nbl/ui/ICursorControl.h" @@ -30,6 +30,4 @@ using namespace video; using namespace scene; using namespace geometrycreator; -using matrix_precision_t = float32_t; - #endif // __NBL_THIS_EXAMPLE_COMMON_H_INCLUDED__ \ No newline at end of file diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp index 69872c4ca..eebfa263f 100644 --- a/61_UI/include/keysmapping.hpp +++ b/61_UI/include/keysmapping.hpp @@ -2,10 +2,9 @@ #define __NBL_KEYSMAPPING_H_INCLUDED__ #include "common.hpp" -#include "ICamera.hpp" +#include "camera/ICamera.hpp" -template -void handleAddMapping(const char* tableID, ICamera* camera, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) +void handleAddMapping(const char* tableID, ICamera* camera, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) { ImGui::BeginTable(tableID, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.33f); @@ -76,8 +75,7 @@ void handleAddMapping(const char* tableID, ICamera* camera, IGimbalManipulate ImGui::EndTable(); } -template -void displayKeyMappingsAndVirtualStates(ICamera* camera) +void displayKeyMappingsAndVirtualStates(ICamera* camera) { static bool addMode = false, pendingChanges = false; static CVirtualGimbalEvent::VirtualEventType selectedEventType = CVirtualGimbalEvent::VirtualEventType::MoveForward; diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 9affd29f1..7a8e366a7 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -7,10 +7,6 @@ #include "camera/CCubeProjection.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING -// FPS Camera, TESTS -using camera_t = CFPSCamera; -using projection_t = IProjection>; // TODO: temporary -> projections will own/reference cameras - constexpr IGPUImage::SSubresourceRange TripleBufferUsedSubresourceRange = { .aspectMask = IGPUImage::EAF_COLOR_BIT, @@ -421,9 +417,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication m_ui.manager->registerListener([this]() -> void { imguiListen(); }); } - for (auto& projection : projections) - projection = make_smart_refctd_ptr(); - // Geometry Creator Render Scenes { resources = ResourcesBundle::create(m_device.get(), m_logger.get(), getGraphicsQueue(), m_assetManager->getGeometryCreator()); @@ -445,7 +438,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // lets use key map presets to update the controller - camera = make_smart_refctd_ptr(iPosition[i], iOrientation[i]); + camera = make_smart_refctd_ptr(iPosition[i], iOrientation[i]); // init keyboard map camera->updateKeyboardMapping([&](auto& keys) @@ -466,6 +459,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication }); } + + // projections + projections = linear_projection_t::create(smart_refctd_ptr(cameraz.front())); + for (uint32_t i = 0u; i < scenez.size(); ++i) { auto& scene = scenez[i]; @@ -828,7 +825,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication camera->process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); } camera->endInputProcessing(); - camera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); + camera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); } m_ui.manager->update(params); @@ -855,26 +852,27 @@ class UISampleApp final : public examples::SimpleWindowedApplication // TODO: I need this logic per viewport we will render a scene from a point of view of its bound camera { + auto projectionMatrices = smart_refctd_ptr_static_cast(projections)->getLinearProjections(); - for (uint32_t i = 0u; i < projections.size(); ++i) + for (uint32_t i = 0u; i < projectionMatrices.size(); ++i) { - auto& projection = projections[i]; + auto& projection = projectionMatrices[i]; if (isPerspective) { if (isLH) - projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio[i], zNear, zFar)); + projection.setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio[i], zNear, zFar)); else - projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio[i], zNear, zFar)); + projection.setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio[i], zNear, zFar)); } else { float viewHeight = viewWidth * invAspectRatio[i]; if (isLH) - projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); + projection.setProjectionMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); else - projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); + projection.setProjectionMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); } } @@ -989,11 +987,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::SetID(0u); - imguizmoM16InOut.view[0u] = transpose(getMatrix3x4As4x4(firstcamera->getGimbal().getView().matrix)); - imguizmoM16InOut.view[1u] = transpose(getMatrix3x4As4x4(secondcamera->getGimbal().getView().matrix)); + imguizmoM16InOut.view[0u] = getCastedMatrix(transpose(getMatrix3x4As4x4(firstcamera->getGimbal().getViewMatrix()))); + imguizmoM16InOut.view[1u] = getCastedMatrix(transpose(getMatrix3x4As4x4(secondcamera->getGimbal().getViewMatrix()))); + auto linearProjections = projections->getLinearProjections(); for(uint32_t i = 0u; i < ProjectionsCount; ++i) - imguizmoM16InOut.projection[i] = transpose(projections[i]->getMatrix()); + imguizmoM16InOut.projection[i] = getCastedMatrix(transpose(linearProjections[i].getProjectionMatrix())); // TODO: need to inspect where I'm wrong, workaround auto gimbalToImguizmoTRS = [&](const float32_t3x4& nblGimbalTrs) -> float32_t4x4 @@ -1013,7 +1012,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // we will transform a scene object's model imguizmoM16InOut.inModel[0] = transpose(getMatrix3x4As4x4(m_model)); // and second camera's model too - imguizmoM16InOut.inModel[1] = gimbalToImguizmoTRS(secondCameraGimbalModel); + imguizmoM16InOut.inModel[1] = gimbalToImguizmoTRS(getCastedMatrix(secondCameraGimbalModel)); { float* views[] { @@ -1129,7 +1128,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { static std::vector virtualEvents(0x45); uint32_t vCount; - auto nblManipulationMode = ICamera::Local; + auto nblManipulationMode = ICamera::Local; secondcamera->beginInputProcessing(m_nextPresentationTimestamp); { @@ -1146,8 +1145,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication switch (mCurrentGizmoMode) { - case ImGuizmo::LOCAL: orientationBasis = (float32_t3x3(secondCameraGimbalModel)); break; - case ImGuizmo::WORLD: nblManipulationMode = ICamera::World; break; + case ImGuizmo::LOCAL: orientationBasis = (float32_t3x3(getCastedMatrix(secondCameraGimbalModel))); break; + case ImGuizmo::WORLD: nblManipulationMode = ICamera::World; break; default: assert(false); break; } @@ -1167,14 +1166,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication // TODO: make it more nicely std::string_view mName; - const_cast(firstcamera->getGimbal().getView().matrix) = float32_t3x4(transpose(imguizmoM16InOut.view[0u])); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) + const_cast(firstcamera->getGimbal().getViewMatrix()) = float64_t3x4(getCastedMatrix(transpose(imguizmoM16InOut.view[0u]))); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) { m_model = float32_t3x4(transpose(imguizmoM16InOut.outModel[0])); - const float32_t3x4* views[] = + auto firstCameraView = getCastedMatrix(firstcamera->getGimbal().getViewMatrix()); + auto secondCameraView = getCastedMatrix(secondcamera->getGimbal().getViewMatrix()); + + const float32_t3x4* views[] = { - &firstcamera->getGimbal().getView().matrix, - &secondcamera->getGimbal().getView().matrix + &firstCameraView, + &secondCameraView }; const auto& references = resources->objects; @@ -1198,7 +1200,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // TODO //modelView.getSub3x3InverseTranspose(normal); - auto concatMatrix = mul(projections[i]->getMatrix(), getMatrix3x4As4x4(viewMatrix)); + auto concatMatrix = mul(getCastedMatrix(linearProjections[i].getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(m_model)); memcpy(hook.viewParameters.MVP, &modelViewProjection[0][0], sizeof(hook.viewParameters.MVP)); @@ -1301,9 +1303,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication continue; auto& gimbal = camera->getGimbal(); - const auto& position = gimbal.getPosition(); + const auto position = getCastedVector(gimbal.getPosition()); const auto& orientation = gimbal.getOrientation(); - const auto& viewMatrix = gimbal.getView().matrix; + const auto viewMatrix = getCastedMatrix(gimbal.getViewMatrix()); const auto trsMatrix = gimbal(); ImGui::Text("ID: %zu", cameraIndex); @@ -1598,7 +1600,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication nbl::hlsl::float32_t3x4 m_model = nbl::hlsl::float32_t3x4(1.f); static constexpr inline auto CamerazCount = 2u; std::array, CamerazCount> scenez; - std::array>, CamerazCount> cameraz; + std::array, CamerazCount> cameraz; nbl::core::smart_refctd_ptr resources; static constexpr inline auto OfflineSceneFirstCameraTextureIx = 1u; @@ -1606,11 +1608,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication CRenderUI m_ui; video::CDumbPresentationOracle oracle; - uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed - static constexpr inline auto ProjectionsCount = 3u; - std::array, ProjectionsCount> projections; // TMP! + static constexpr inline auto ProjectionsCount = 3u; // full screen, first & second GUI windows + using linear_projections_range_t = std::array; + using linear_projection_t = CLinearProjection; + nbl::core::smart_refctd_ptr projections; + bool isPerspective[ProjectionsCount] = { true, true, true }, isLH = true, flipGizmoY = true, move = false; float fov = 60.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; float viewWidth = 10.f; diff --git a/common/include/camera/CCubeProjection.hpp b/common/include/camera/CCubeProjection.hpp index 9c3fae30b..d47c5c6b8 100644 --- a/common/include/camera/CCubeProjection.hpp +++ b/common/include/camera/CCubeProjection.hpp @@ -2,20 +2,20 @@ #define _NBL_CCUBE_PROJECTION_HPP_ #include "IRange.hpp" -#include "IQuadProjection.hpp" +#include "IPerspectiveProjection.hpp" namespace nbl::hlsl { /** -* @brief A projection where each cube face is a quad we project onto. +* @brief A projection where each cube face is a perspective quad we project onto. * * Represents a cube projection given direction vector where each face of * the cube is treated as a quad. The projection onto the cube is done using * these quads and each face has its own unique pre-transform and * view-port linear matrix. */ -class CCubeProjection final : public IQuadProjection, public IProjection +class CCubeProjection final : public IPerspectiveProjection, public IProjection { public: //! Represents six face identifiers of a cube. @@ -76,14 +76,14 @@ class CCubeProjection final : public IQuadProjection, public IProjection template requires (FaceIx != CubeFacesCount) - inline const CProjection& getQuad() + inline const CProjection& getProjectionQuad() { return m_quads[FaceIx]; } private: CCubeProjection(core::smart_refctd_ptr&& camera) - : IQuadProjection(core::smart_refctd_ptr(camera)) {} + : IPerspectiveProjection(core::smart_refctd_ptr(camera)) {} virtual ~CCubeProjection() = default; std::array m_quads; diff --git a/common/include/camera/CFPSCamera.hpp b/common/include/camera/CFPSCamera.hpp index ae887aa0c..188a8a47f 100644 --- a/common/include/camera/CFPSCamera.hpp +++ b/common/include/camera/CFPSCamera.hpp @@ -2,8 +2,8 @@ // This file is part of the "Nabla Engine". // For conditions of distribution and use, see copyright notice in nabla.h -#ifndef _C_CAMERA_HPP_ -#define _C_CAMERA_HPP_ +#ifndef _C_FPS_CAMERA_HPP_ +#define _C_FPS_CAMERA_HPP_ #include "ICamera.hpp" @@ -11,13 +11,12 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE { // FPS Camera -template -class CFPSCamera final : public ICamera +class CFPSCamera final : public ICamera { public: - using base_t = ICamera; + using base_t = ICamera; - CFPSCamera(const vector& position, const glm::quat& orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + CFPSCamera(const float64_t3& position, const glm::quat& orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) : base_t(), m_gimbal({ .position = position, .orientation = orientation }) {} ~CFPSCamera() = default; @@ -32,7 +31,7 @@ class CFPSCamera final : public ICamera virtual bool manipulate(std::span virtualEvents, base_t::ManipulationMode mode) override { - constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; + constexpr double MoveSpeedScale = 0.01, RotateSpeedScale = 0.003, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; if (!virtualEvents.size()) return false; @@ -40,7 +39,7 @@ class CFPSCamera final : public ICamera const auto& gForward = m_gimbal.getZAxis(); const float gPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(), gYaw = atan2(gForward.x, gForward.z); - glm::quat newOrientation; vector newPosition; + glm::quat newOrientation; float64_t3 newPosition; // TODO: I make assumption what world base is now (at least temporary), I need to think of this but maybe each ITransformObject should know what its world is // in ideal scenario we would define this crazy enum with all possible standard bases @@ -137,4 +136,4 @@ class CFPSCamera final : public ICamera } -#endif // _C_CAMERA_HPP_ \ No newline at end of file +#endif // _C_FPS_CAMERA_HPP_ \ No newline at end of file diff --git a/common/include/camera/CLinearProjection.hpp b/common/include/camera/CLinearProjection.hpp index 6dbe41b82..77725125b 100644 --- a/common/include/camera/CLinearProjection.hpp +++ b/common/include/camera/CLinearProjection.hpp @@ -2,15 +2,19 @@ #define _NBL_C_LINEAR_PROJECTION_HPP_ #include "ILinearProjection.hpp" +#include "IRange.hpp" namespace nbl::hlsl { +template ProjectionsRange> class CLinearProjection : public ILinearProjection { public: using ILinearProjection::ILinearProjection; + CLinearProjection() = default; + inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) { if (!camera) @@ -19,10 +23,22 @@ class CLinearProjection : public ILinearProjection return core::smart_refctd_ptr(new CLinearProjection(core::smart_refctd_ptr(camera)), core::dont_grab); } + virtual std::span getLinearProjections() const override + { + return std::span(m_projections.data(), m_projections.size()); + } + + inline std::span getLinearProjections() + { + return std::span(m_projections.data(), m_projections.size()); + } + private: CLinearProjection(core::smart_refctd_ptr&& camera) : ILinearProjection(core::smart_refctd_ptr(camera)) {} virtual ~CLinearProjection() = default; + + ProjectionsRange m_projections; }; } // nbl::hlsl namespace diff --git a/common/include/camera/ICamera.hpp b/common/include/camera/ICamera.hpp index d4b3dbe8c..1f81903e5 100644 --- a/common/include/camera/ICamera.hpp +++ b/common/include/camera/ICamera.hpp @@ -13,7 +13,6 @@ namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE class ICamera : public IGimbalController, virtual public core::IReferenceCounted { public: - using precision_t = float64_t; using IGimbalController::IGimbalController; //! Manipulation mode for virtual events @@ -28,10 +27,10 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted }; // Gimbal with view parameters representing a camera in world space - class CGimbal : public IGimbal + class CGimbal : public IGimbal { public: - using base_t = IGimbal; + using base_t = IGimbal; CGimbal(typename base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) { updateView(); } ~CGimbal() = default; @@ -64,16 +63,16 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted assert(isOrthoBase(gRight, gUp, gForward)); const auto& position = base_t::getPosition(); - m_view.matrix[0u] = vector(gRight, -glm::dot(gRight, position)); - m_view.matrix[1u] = vector(gUp, -glm::dot(gUp, position)); - m_view.matrix[2u] = vector(gForward, -glm::dot(gForward, position)); + + m_viewMatrix[0u] = float64_t4(gRight, -glm::dot(gRight, position)); + m_viewMatrix[1u] = float64_t4(gUp, -glm::dot(gUp, position)); + m_viewMatrix[2u] = float64_t4(gForward, -glm::dot(gForward, position)); } - // Getter for gimbal's view - inline const SView& getView() const { return m_view; } + inline const float64_t3x4& getViewMatrix() const { return m_viewMatrix; } private: - SView m_view; + float64_t3x4 m_viewMatrix; }; ICamera() {} diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp index 069507f01..c51cb1780 100644 --- a/common/include/camera/ILinearProjection.hpp +++ b/common/include/camera/ILinearProjection.hpp @@ -11,7 +11,7 @@ namespace nbl::hlsl * @brief Interface class for any custom linear projection transformation (matrix elements are already evaluated scalars) * referencing a camera, great for Perspective, Orthographic, Oblique, Axonometric and Shear projections */ -class ILinearProjection +class ILinearProjection : virtual public core::IReferenceCounted { protected: ILinearProjection(core::smart_refctd_ptr&& camera) @@ -35,6 +35,7 @@ class ILinearProjection using projection_matrix_t = concatenated_matrix_t; using inv_projection_matrix_t = inv_concatenated_matrix_t; + CProjection() : CProjection(projection_matrix_t(1)) {} CProjection(const projection_matrix_t& matrix) { setProjectionMatrix(matrix); } inline void setProjectionMatrix(const projection_matrix_t& matrix) @@ -91,6 +92,17 @@ class ILinearProjection }; virtual std::span getLinearProjections() const = 0; + + bool setCamera(core::smart_refctd_ptr&& camera) + { + if (camera) + { + m_camera = camera; + return true; + } + + return false; + } /** * @brief Computes Model View (MV) matrix @@ -99,7 +111,7 @@ class ILinearProjection */ inline concatenated_matrix_t getMV(const model_matrix_t& model) const { - const auto& v = m_camera->getGimbal().getView().matrix; + const auto& v = m_camera->getGimbal().getViewMatrix(); return mul(getMatrix3x4As4x4(v), getMatrix3x4As4x4(model)); } @@ -111,7 +123,7 @@ class ILinearProjection */ inline concatenated_matrix_t getMVP(const CProjection& projection, const model_matrix_t& model) const { - const auto& v = m_camera->getGimbal().getView().matrix; + const auto& v = m_camera->getGimbal().getViewMatrix(); const auto& p = projection.getProjectionMatrix(); auto mv = mul(getMatrix3x4As4x4(v), getMatrix3x4As4x4(model)); return mul(p, mv); diff --git a/common/include/camera/IQuadProjection.hpp b/common/include/camera/IPerspectiveProjection.hpp similarity index 71% rename from common/include/camera/IQuadProjection.hpp rename to common/include/camera/IPerspectiveProjection.hpp index 43ba680ec..479db170e 100644 --- a/common/include/camera/IQuadProjection.hpp +++ b/common/include/camera/IPerspectiveProjection.hpp @@ -9,25 +9,26 @@ namespace nbl::hlsl /** * @brief Interface class for quad projections. * -* This projection transforms a vector into the **model space of a quad** -* (defined by the pre-transform matrix) and then projects it onto the quad using -* the linear view-port transform. +* This projection transforms a vector into the **model space of a perspective quad** +* (defined by the pre-transform matrix) and then projects it onto the perspective quad +* using the linear view-port transform. * -* A quad projection is represented by: +* A perspective quad projection is represented by: * - A **pre-transform matrix** (non-linear/skewed transformation). * - A **linear view-port transform matrix**. * * The final projection matrix is the concatenation of the pre-transform and the linear view-port transform. * -* @note Single quad projection can represent a face quad of a CAVE-like system. +* @note Single perspective quad projection can represent a face quad of a CAVE-like system. */ -class IQuadProjection : public ILinearProjection +class IPerspectiveProjection : public ILinearProjection { public: struct CProjection : ILinearProjection::CProjection { using base_t = ILinearProjection::CProjection; + CProjection() = default; CProjection(const ILinearProjection::model_matrix_t& pretransform, ILinearProjection::concatenated_matrix_t viewport) { setQuadTransform(pretransform, viewport); @@ -46,14 +47,14 @@ class IQuadProjection : public ILinearProjection inline const ILinearProjection::concatenated_matrix_t& getViewportProjection() const { return m_viewport; } private: - ILinearProjection::model_matrix_t m_pretransform; - ILinearProjection::concatenated_matrix_t m_viewport; + ILinearProjection::model_matrix_t m_pretransform = ILinearProjection::model_matrix_t(1); + ILinearProjection::concatenated_matrix_t m_viewport = ILinearProjection::concatenated_matrix_t(1); }; protected: - IQuadProjection(core::smart_refctd_ptr&& camera) + IPerspectiveProjection(core::smart_refctd_ptr&& camera) : ILinearProjection(core::smart_refctd_ptr(camera)) {} - virtual ~IQuadProjection() = default; + virtual ~IPerspectiveProjection() = default; }; } // nbl::hlsl namespace diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index 7e145ffee..7fa94e517 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -18,10 +18,10 @@ class IProjection //! Perspective, Orthographic, Oblique, Axonometric, Shear projections or any custom linear transformation Linear, - //! Represents a non-linear/skewed pre-transform *concatenated* with linear view-port transform - Quad, + //! Represents pre-transform *concatenated* with linear view-port transform, projects onto a quad + Perspective, - //! Represents a projection onto cube consisting of 6 quad cube faces + //! Represents a Perspective projection onto cube consisting of 6 quad cube faces Cube, Spherical, diff --git a/common/include/camera/IRange.hpp b/common/include/camera/IRange.hpp index 5c6fe751b..a6ed29270 100644 --- a/common/include/camera/IRange.hpp +++ b/common/include/camera/IRange.hpp @@ -10,19 +10,10 @@ concept GeneralPurposeRange = requires typename std::ranges::range_value_t; }; -//! Interface class for a general purpose range -template -class IRange -{ -public: - using range_t = Range; - using range_value_t = std::ranges::range_value_t; - - IRange(range_t&& range) : m_range(std::move(range)) {} - -protected: - range_t m_range; -}; +template +concept ContiguousGeneralPurposeRangeOf = GeneralPurposeRange && +std::ranges::contiguous_range && +std::same_as, T>; } // namespace nbl::hlsl From 7085418546cddea78679c237bd79db8754ecc88e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 12 Dec 2024 13:15:55 +0100 Subject: [PATCH 51/84] fix a typo which broke my projections, comment out a few debug windows --- 61_UI/main.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 7a8e366a7..0d582e8f2 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -850,29 +850,28 @@ class UISampleApp final : public examples::SimpleWindowedApplication //if (ImGui::RadioButton("Full view", !useWindow)) // useWindow = false; - // TODO: I need this logic per viewport we will render a scene from a point of view of its bound camera + auto projectionMatrices = projections->getLinearProjections(); { - auto projectionMatrices = smart_refctd_ptr_static_cast(projections)->getLinearProjections(); - - for (uint32_t i = 0u; i < projectionMatrices.size(); ++i) + auto mutableRange = smart_refctd_ptr_static_cast(projections)->getLinearProjections(); + for (uint32_t i = 0u; i < mutableRange.size(); ++i) { - auto& projection = projectionMatrices[i]; + auto projection = mutableRange.begin() + i; if (isPerspective) { if (isLH) - projection.setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio[i], zNear, zFar)); + projection->setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio[i], zNear, zFar)); else - projection.setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio[i], zNear, zFar)); + projection->setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio[i], zNear, zFar)); } else { float viewHeight = viewWidth * invAspectRatio[i]; if (isLH) - projection.setProjectionMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); + projection->setProjectionMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); else - projection.setProjectionMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); + projection->setProjectionMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); } } @@ -990,9 +989,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication imguizmoM16InOut.view[0u] = getCastedMatrix(transpose(getMatrix3x4As4x4(firstcamera->getGimbal().getViewMatrix()))); imguizmoM16InOut.view[1u] = getCastedMatrix(transpose(getMatrix3x4As4x4(secondcamera->getGimbal().getViewMatrix()))); - auto linearProjections = projections->getLinearProjections(); for(uint32_t i = 0u; i < ProjectionsCount; ++i) - imguizmoM16InOut.projection[i] = getCastedMatrix(transpose(linearProjections[i].getProjectionMatrix())); + imguizmoM16InOut.projection[i] = getCastedMatrix(transpose(projectionMatrices[i].getProjectionMatrix())); // TODO: need to inspect where I'm wrong, workaround auto gimbalToImguizmoTRS = [&](const float32_t3x4& nblGimbalTrs) -> float32_t4x4 @@ -1166,7 +1164,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // TODO: make it more nicely std::string_view mName; - const_cast(firstcamera->getGimbal().getViewMatrix()) = float64_t3x4(getCastedMatrix(transpose(imguizmoM16InOut.view[0u]))); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) + //const_cast(firstcamera->getGimbal().getViewMatrix()) = float64_t3x4(getCastedMatrix(transpose(imguizmoM16InOut.view[0u]))); // a hack for "view manipulate", correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) { m_model = float32_t3x4(transpose(imguizmoM16InOut.outModel[0])); @@ -1200,7 +1198,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // TODO //modelView.getSub3x3InverseTranspose(normal); - auto concatMatrix = mul(getCastedMatrix(linearProjections[i].getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); + auto concatMatrix = mul(getCastedMatrix(projectionMatrices[i + 1u].getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(m_model)); memcpy(hook.viewParameters.MVP, &modelViewProjection[0][0], sizeof(hook.viewParameters.MVP)); @@ -1254,6 +1252,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } + /* + // ImGuizmo inputs { ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); @@ -1276,6 +1276,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } + */ + //imguizmoM16InOut.inModel // Cameraz @@ -1343,6 +1345,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } + /* // Nabla Imgui backend MDI buffer info // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, @@ -1413,6 +1416,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } + */ + displayKeyMappingsAndVirtualStates(cameraz.front().get()); ImGui::End(); @@ -1532,6 +1537,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); } + ImGuiWindow* window = ImGui::GetCurrentWindow(); gizmoWindowFlags = ImGuiWindowFlags_NoMove;//(ImGuizmo::IsUsing() && ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); } From bf7bfed2631006caba59d3d50c1662f8c6a51b6a Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 12 Dec 2024 15:32:21 +0100 Subject: [PATCH 52/84] add camera selection with mouse disabled on move, display camera properties with node tree --- 61_UI/main.cpp | 137 ++++++++++++-------- common/include/camera/ILinearProjection.hpp | 7 +- 2 files changed, 88 insertions(+), 56 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 0d582e8f2..f14fbf51a 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -224,6 +224,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (m_surface) { m_window->getManager()->maximize(m_window.get()); + auto* cc = m_window->getCursorControl(); + cc->setVisible(false); + return { {m_surface->getSurface()/*,EQF_NONE*/} }; } @@ -459,7 +462,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication }); } - // projections projections = linear_projection_t::create(smart_refctd_ptr(cameraz.front())); @@ -807,10 +809,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication .keyboardEvents = { capturedEvents.keyboard.data(), capturedEvents.keyboard.size() } }; - if (move) + if (enableActiveCameraMovement) { - // TODO: testing - auto& camera = cameraz.front().get(); + auto& camera = cameraz[activeCameraIndex]; static std::vector virtualEvents(0x45); uint32_t vCount; @@ -874,8 +875,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication projection->setProjectionMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); } } - - } ImGui::SameLine(); @@ -1280,68 +1279,94 @@ class UISampleApp final : public examples::SimpleWindowedApplication //imguizmoM16InOut.inModel - // Cameraz { - size_t cameraCount = cameraz.size(); - if (cameraCount > 0) - { - size_t columns = std::max(1, static_cast(std::sqrt(static_cast(cameraCount)))); - size_t rows = (cameraCount + columns - 1) / columns; + ImGuiIO& io = ImGui::GetIO(); - ImGui::Begin("Camera Matrices", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); + if (ImGui::IsKeyPressed(ImGuiKey_C)) + enableActiveCameraMovement = !enableActiveCameraMovement; - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4.0f, 2.0f)); + if (enableActiveCameraMovement) + { + io.ConfigFlags |= ImGuiConfigFlags_NoMouse; + io.MouseDrawCursor = false; + io.WantCaptureMouse = false; + } + else + { + io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; + io.MouseDrawCursor = true; + io.WantCaptureMouse = true; + } - for (size_t row = 0; row < rows; ++row) - { - for (size_t col = 0; col < columns; ++col) - { - size_t cameraIndex = row * columns + col; - if (cameraIndex >= cameraCount) - break; + ImGui::Begin("Cameras", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); + ImGui::Text("Select Active Camera:"); + ImGui::Separator(); - auto& camera = cameraz[cameraIndex]; - if (!camera) - continue; + if (ImGui::BeginCombo("Active Camera", ("Camera " + std::to_string(activeCameraIndex)).c_str())) + { + for (uint32_t cameraIndex = 0; cameraIndex < CamerazCount; ++cameraIndex) + { + bool isSelected = (cameraIndex == activeCameraIndex); + std::string comboLabel = "Camera " + std::to_string(cameraIndex); - auto& gimbal = camera->getGimbal(); - const auto position = getCastedVector(gimbal.getPosition()); - const auto& orientation = gimbal.getOrientation(); - const auto viewMatrix = getCastedMatrix(gimbal.getViewMatrix()); - const auto trsMatrix = gimbal(); + if (ImGui::Selectable(comboLabel.c_str(), isSelected)) + activeCameraIndex = cameraIndex; - ImGui::Text("ID: %zu", cameraIndex); - ImGui::Separator(); + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } - ImGui::Text("Type: %s", camera->getIdentifier().data()); - ImGui::Separator(); + ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg)); - ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg)); + if (enableActiveCameraMovement) + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Active Camera Movement: Enabled"); + else + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Active Camera Movement: Disabled"); - if (ImGui::BeginTable(("CameraTable" + std::to_string(cameraIndex)).c_str(), 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame)) - { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); + if (ImGui::IsItemHovered()) + { + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); + ImVec2 mousePos = ImGui::GetMousePos(); + ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); + ImGui::Begin("InfoOverlay", NULL, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings); + ImGui::Text("Press 'C' to enable/disable selected camera movement."); + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } - const auto idxstr = std::to_string(cameraIndex); - addMatrixTable("Position", ("PositionTable" + idxstr).c_str(), 1, 3, &position[0], false); - addMatrixTable("Orientation (Quaternion)", ("OrientationTable" + idxstr).c_str(), 1, 4, &orientation[0], false); - addMatrixTable("View Matrix", ("ViewMatrixTable" + idxstr).c_str(), 3, 4, &viewMatrix[0][0], false); - //addMatrixTable("TRS Matrix", ("TRSMatrixTable" + idxstr).c_str(), 3, 4, &trsMatrix[0][0], true); + ImGui::Separator(); - ImGui::EndTable(); - } + for (size_t cameraIndex = 0; cameraIndex < CamerazCount; ++cameraIndex) + { + auto& camera = cameraz[cameraIndex]; + if (!camera) + continue; - ImGui::PopStyleColor(2); - } + std::string treeNodeLabel = "Camera " + std::to_string(cameraIndex); + if (ImGui::TreeNode(treeNodeLabel.c_str())) + { + auto& gimbal = camera->getGimbal(); + const auto position = getCastedVector(gimbal.getPosition()); + const auto& orientation = gimbal.getOrientation(); + const auto viewMatrix = getCastedMatrix(gimbal.getViewMatrix()); + ImGui::Text("Type: %s", camera->getIdentifier().data()); + ImGui::Separator(); + addMatrixTable("Position", ("PositionTable_" + std::to_string(cameraIndex)).c_str(), 1, 3, &position[0], false); + addMatrixTable("Orientation (Quaternion)", ("OrientationTable_" + std::to_string(cameraIndex)).c_str(), 1, 4, &orientation[0], false); + addMatrixTable("View Matrix", ("ViewMatrixTable_" + std::to_string(cameraIndex)).c_str(), 3, 4, &viewMatrix[0][0], false); + ImGui::TreePop(); } - - ImGui::PopStyleVar(); - ImGui::End(); } - else - ImGui::Text("No camera properties to display."); + + ImGui::End(); } } @@ -1506,7 +1531,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(400, 20 + wCameraIndex * 420), ImGuiCond_Appearing); ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); - std::string ident = "Rendered from Camera \"" + std::to_string(wCameraIndex) + "\" Ix perspective"; + std::string ident = "Camera \"" + std::to_string(wCameraIndex) + "\" View"; ImGui::Begin(ident.data(), 0, gizmoWindowFlags); ImGuizmo::SetDrawlist(); @@ -1607,6 +1632,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication static constexpr inline auto CamerazCount = 2u; std::array, CamerazCount> scenez; std::array, CamerazCount> cameraz; + uint32_t activeCameraIndex = 0; + bool enableActiveCameraMovement = false; nbl::core::smart_refctd_ptr resources; static constexpr inline auto OfflineSceneFirstCameraTextureIx = 1u; @@ -1621,7 +1648,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication using linear_projection_t = CLinearProjection; nbl::core::smart_refctd_ptr projections; - bool isPerspective[ProjectionsCount] = { true, true, true }, isLH = true, flipGizmoY = true, move = false; + bool isPerspective[ProjectionsCount] = { true, true, true }, isLH = true, flipGizmoY = true; float fov = 60.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; float viewWidth = 10.f; float camYAngle = 165.f / 180.f * 3.14159f; diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp index c51cb1780..6222a58b4 100644 --- a/common/include/camera/ILinearProjection.hpp +++ b/common/include/camera/ILinearProjection.hpp @@ -93,7 +93,7 @@ class ILinearProjection : virtual public core::IReferenceCounted virtual std::span getLinearProjections() const = 0; - bool setCamera(core::smart_refctd_ptr&& camera) + inline bool setCamera(core::smart_refctd_ptr&& camera) { if (camera) { @@ -104,6 +104,11 @@ class ILinearProjection : virtual public core::IReferenceCounted return false; } + inline ICamera* getCamera() + { + return m_camera.get(); + } + /** * @brief Computes Model View (MV) matrix * @param "model" is world TRS matrix From ded92d8f53b10c9ab87dde982cf19e37a0cc29df Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 12 Dec 2024 17:53:24 +0100 Subject: [PATCH 53/84] have virtual events mapping per camera in node tree, disable all gizmos on active camera movement, a few updates & clean-ups --- 61_UI/include/keysmapping.hpp | 71 ++++++++++---------- 61_UI/main.cpp | 119 ++++++++++++++++++---------------- 2 files changed, 99 insertions(+), 91 deletions(-) diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp index eebfa263f..053acff6e 100644 --- a/61_UI/include/keysmapping.hpp +++ b/61_UI/include/keysmapping.hpp @@ -75,31 +75,41 @@ void handleAddMapping(const char* tableID, ICamera* camera, IGimbalManipulateEnc ImGui::EndTable(); } -void displayKeyMappingsAndVirtualStates(ICamera* camera) +void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow = false) { - static bool addMode = false, pendingChanges = false; - static CVirtualGimbalEvent::VirtualEventType selectedEventType = CVirtualGimbalEvent::VirtualEventType::MoveForward; - static ui::E_KEY_CODE newKey = ui::E_KEY_CODE::EKC_A; - static ui::E_MOUSE_CODE newMouseCode = ui::EMC_LEFT_BUTTON; - static IGimbalManipulateEncoder::EncoderType activeController = IGimbalManipulateEncoder::Keyboard; + if (!camera) return; + + // Per-camera state for UI rendering + struct MappingState + { + bool addMode = false; + CVirtualGimbalEvent::VirtualEventType selectedEventType = CVirtualGimbalEvent::VirtualEventType::MoveForward; + ui::E_KEY_CODE newKey = ui::E_KEY_CODE::EKC_A; + ui::E_MOUSE_CODE newMouseCode = ui::EMC_LEFT_BUTTON; + IGimbalManipulateEncoder::EncoderType activeController = IGimbalManipulateEncoder::Keyboard; + }; + + static std::unordered_map cameraStates; + auto& state = cameraStates[camera]; const auto& keyboardMappings = camera->getKeyboardVirtualEventMap(); const auto& mouseMappings = camera->getMouseVirtualEventMap(); - ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSizeConstraints(ImVec2(600, 400), ImVec2(600, 69000)); - - ImGui::Begin("Controller Mappings & Virtual States", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysVerticalScrollbar); + if (spawnWindow) + { + ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); + ImGui::Begin("Controller Mappings & Virtual States", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysVerticalScrollbar); + } if (ImGui::BeginTabBar("ControllersTabBar")) { if (ImGui::BeginTabItem("Keyboard")) { - activeController = IGimbalManipulateEncoder::Keyboard; + state.activeController = IGimbalManipulateEncoder::Keyboard; ImGui::Separator(); - if (ImGui::Button("Add key", ImVec2(100, 30))) - addMode = !addMode; + if (ImGui::Button("Add Key", ImVec2(100, 30))) + state.addMode = !state.addMode; ImGui::Separator(); @@ -114,7 +124,6 @@ void displayKeyMappingsAndVirtualStates(ICamera* camera) for (const auto& [keyboardCode, hash] : keyboardMappings) { ImGui::TableNextRow(); - const char* eventName = CVirtualGimbalEvent::virtualEventToString(hash.event.type).data(); ImGui::TableSetColumnIndex(0); ImGui::AlignTextToFramePadding(); @@ -131,25 +140,21 @@ void displayKeyMappingsAndVirtualStates(ICamera* camera) ImGui::TextColored(statusColor, "%s", isActive ? "Active" : "Inactive"); ImGui::TableSetColumnIndex(3); - std::array deltaText; - snprintf(deltaText.data(), deltaText.size(), "%.2f", hash.event.magnitude); - ImGui::AlignTextToFramePadding(); - ImGui::TextWrapped("%s", deltaText.data()); + ImGui::Text("%.2f", hash.event.magnitude); ImGui::TableSetColumnIndex(4); if (ImGui::Button(("Delete##deleteKey" + std::to_string(static_cast(hash.event.type))).c_str())) { - camera->updateKeyboardMapping([&](auto& keys) { keys.erase(keyboardCode); }); - pendingChanges = true; + camera->updateKeyboardMapping([keyboardCode](auto& keys) { keys.erase(keyboardCode); }); break; } } ImGui::EndTable(); - if (addMode) + if (state.addMode) { ImGui::Separator(); - handleAddMapping("AddKeyboardMappingTable", camera, activeController, selectedEventType, newKey, newMouseCode, addMode); + handleAddMapping("AddKeyboardMappingTable", camera, state.activeController, state.selectedEventType, state.newKey, state.newMouseCode, state.addMode); } ImGui::EndTabItem(); @@ -157,11 +162,11 @@ void displayKeyMappingsAndVirtualStates(ICamera* camera) if (ImGui::BeginTabItem("Mouse")) { - activeController = IGimbalManipulateEncoder::Mouse; + state.activeController = IGimbalManipulateEncoder::Mouse; ImGui::Separator(); - if (ImGui::Button("Add key", ImVec2(100, 30))) - addMode = !addMode; + if (ImGui::Button("Add Key", ImVec2(100, 30))) + state.addMode = !state.addMode; ImGui::Separator(); @@ -176,7 +181,6 @@ void displayKeyMappingsAndVirtualStates(ICamera* camera) for (const auto& [mouseCode, hash] : mouseMappings) { ImGui::TableNextRow(); - const char* eventName = CVirtualGimbalEvent::virtualEventToString(hash.event.type).data(); ImGui::TableSetColumnIndex(0); ImGui::AlignTextToFramePadding(); @@ -193,25 +197,21 @@ void displayKeyMappingsAndVirtualStates(ICamera* camera) ImGui::TextColored(statusColor, "%s", isActive ? "Active" : "Inactive"); ImGui::TableSetColumnIndex(3); - std::array deltaText; - snprintf(deltaText.data(), deltaText.size(), "%.2f", hash.event.magnitude); - ImGui::AlignTextToFramePadding(); - ImGui::TextWrapped("%s", deltaText.data()); + ImGui::Text("%.2f", hash.event.magnitude); ImGui::TableSetColumnIndex(4); if (ImGui::Button(("Delete##deleteMouse" + std::to_string(static_cast(hash.event.type))).c_str())) { - camera->updateMouseMapping([&](auto& mouse) { mouse.erase(mouseCode); }); - pendingChanges = true; + camera->updateMouseMapping([mouseCode](auto& mouse) { mouse.erase(mouseCode); }); break; } } ImGui::EndTable(); - if (addMode) + if (state.addMode) { ImGui::Separator(); - handleAddMapping("AddMouseMappingTable", camera, activeController, selectedEventType, newKey, newMouseCode, addMode); + handleAddMapping("AddMouseMappingTable", camera, state.activeController, state.selectedEventType, state.newKey, state.newMouseCode, state.addMode); } ImGui::EndTabItem(); } @@ -219,7 +219,8 @@ void displayKeyMappingsAndVirtualStates(ICamera* camera) ImGui::EndTabBar(); } - ImGui::End(); + if (spawnWindow) + ImGui::End(); } #endif // __NBL_KEYSMAPPING_H_INCLUDED__ \ No newline at end of file diff --git a/61_UI/main.cpp b/61_UI/main.cpp index f14fbf51a..14cc93a80 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1036,7 +1036,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication bool manipulatedFromAnyWindow = false; ImGuizmo::AllowAxisFlip(false); - ImGuizmo::Enable(true); + + if(enableActiveCameraMovement) + ImGuizmo::Enable(false); + else + ImGuizmo::Enable(true); // we have 2 GUI windows we render into with FBOs for (uint32_t windowIndex = 0; windowIndex < 2u; ++windowIndex) @@ -1124,38 +1128,42 @@ class UISampleApp final : public examples::SimpleWindowedApplication { static std::vector virtualEvents(0x45); - uint32_t vCount; - auto nblManipulationMode = ICamera::Local; - - secondcamera->beginInputProcessing(m_nextPresentationTimestamp); + + if (ImGuizmo::IsUsingAny()) { - secondcamera->process(nullptr, vCount); + uint32_t vCount; + auto nblManipulationMode = ICamera::Local; - if (virtualEvents.size() < vCount) - virtualEvents.resize(vCount); + secondcamera->beginInputProcessing(m_nextPresentationTimestamp); + { + secondcamera->process(nullptr, vCount); - IGimbalController::SUpdateParameters params; + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); - auto getOrientationBasis = [&]() - { - auto orientationBasis = (float32_t3x3(1.f)); + IGimbalController::SUpdateParameters params; - switch (mCurrentGizmoMode) - { - case ImGuizmo::LOCAL: orientationBasis = (float32_t3x3(getCastedMatrix(secondCameraGimbalModel))); break; - case ImGuizmo::WORLD: nblManipulationMode = ICamera::World; break; - default: assert(false); break; - } + auto getOrientationBasis = [&]() + { + auto orientationBasis = (float32_t3x3(1.f)); - return orientationBasis; - }; + switch (mCurrentGizmoMode) + { + case ImGuizmo::LOCAL: orientationBasis = (float32_t3x3(getCastedMatrix(secondCameraGimbalModel))); break; + case ImGuizmo::WORLD: nblManipulationMode = ICamera::World; break; + default: assert(false); break; + } - params.imguizmoEvents = { { std::make_pair(imguizmoM16InOut.outDeltaTRS[1u], getOrientationBasis()) } }; - secondcamera->process(virtualEvents.data(), vCount, params); - } - secondcamera->endInputProcessing(); + return orientationBasis; + }; + + params.imguizmoEvents = { { std::make_pair(imguizmoM16InOut.outDeltaTRS[1u], getOrientationBasis()) } }; + secondcamera->process(virtualEvents.data(), vCount, params); + } + secondcamera->endInputProcessing(); - secondcamera->manipulate({ virtualEvents.data(), vCount }, nblManipulationMode); + secondcamera->manipulate({ virtualEvents.data(), vCount }, nblManipulationMode); + } } // update scenes data @@ -1325,23 +1333,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication else ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Active Camera Movement: Disabled"); - if (ImGui::IsItemHovered()) - { - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); - ImVec2 mousePos = ImGui::GetMousePos(); - ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); - ImGui::Begin("InfoOverlay", NULL, - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings); - ImGui::Text("Press 'C' to enable/disable selected camera movement."); - ImGui::End(); - ImGui::PopStyleVar(); - ImGui::PopStyleColor(2); - } - ImGui::Separator(); for (size_t cameraIndex = 0; cameraIndex < CamerazCount; ++cameraIndex) @@ -1350,24 +1341,39 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (!camera) continue; + const auto flags = (activeCameraIndex == cameraIndex) ? ImGuiTreeNodeFlags_DefaultOpen : ImGuiTreeNodeFlags_None; std::string treeNodeLabel = "Camera " + std::to_string(cameraIndex); - if (ImGui::TreeNode(treeNodeLabel.c_str())) + + if (ImGui::TreeNodeEx(treeNodeLabel.c_str(), flags)) { - auto& gimbal = camera->getGimbal(); - const auto position = getCastedVector(gimbal.getPosition()); - const auto& orientation = gimbal.getOrientation(); - const auto viewMatrix = getCastedMatrix(gimbal.getViewMatrix()); - ImGui::Text("Type: %s", camera->getIdentifier().data()); - ImGui::Separator(); - addMatrixTable("Position", ("PositionTable_" + std::to_string(cameraIndex)).c_str(), 1, 3, &position[0], false); - addMatrixTable("Orientation (Quaternion)", ("OrientationTable_" + std::to_string(cameraIndex)).c_str(), 1, 4, &orientation[0], false); - addMatrixTable("View Matrix", ("ViewMatrixTable_" + std::to_string(cameraIndex)).c_str(), 3, 4, &viewMatrix[0][0], false); + if (ImGui::TreeNodeEx("Data", ImGuiTreeNodeFlags_None)) + { + auto& gimbal = camera->getGimbal(); + const auto position = getCastedVector(gimbal.getPosition()); + const auto& orientation = gimbal.getOrientation(); + const auto viewMatrix = getCastedMatrix(gimbal.getViewMatrix()); + + ImGui::Text("Type: %s", camera->getIdentifier().data()); + ImGui::Separator(); + addMatrixTable("Position", ("PositionTable_" + std::to_string(cameraIndex)).c_str(), 1, 3, &position[0], false); + addMatrixTable("Orientation (Quaternion)", ("OrientationTable_" + std::to_string(cameraIndex)).c_str(), 1, 4, &orientation[0], false); + addMatrixTable("View Matrix", ("ViewMatrixTable_" + std::to_string(cameraIndex)).c_str(), 3, 4, &viewMatrix[0][0], false); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Virtual Event Mappings", ImGuiTreeNodeFlags_DefaultOpen)) + { + displayKeyMappingsAndVirtualStatesInline(camera.get()); + ImGui::TreePop(); + } + ImGui::TreePop(); } } ImGui::End(); } + } /* @@ -1443,8 +1449,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication */ - displayKeyMappingsAndVirtualStates(cameraz.front().get()); - ImGui::End(); } @@ -1455,12 +1459,15 @@ class UISampleApp final : public examples::SimpleWindowedApplication static bool boundSizing = false; static bool boundSizingSnap = false; + /* if (ImGui::IsKeyPressed(ImGuiKey_T)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::ROTATE; - if (ImGui::IsKeyPressed(ImGuiKey_R)) // r Key + if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::SCALE; + */ + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; ImGui::SameLine(); @@ -1657,7 +1664,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication float camDistance = 8.f, aspectRatio[ProjectionsCount] = {}, invAspectRatio[ProjectionsCount] = {}; bool useWindow = true, useSnap = false; ImGuizmo::OPERATION mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::WORLD; + ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::LOCAL; float snap[3] = { 1.f, 1.f, 1.f }; int lastManipulatedModelIx = 0; int lastManipulatedGizmoIx = 0; From 5e5c6814c1446073387b76296f7565afbb415134 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 13 Dec 2024 10:59:13 +0100 Subject: [PATCH 54/84] modify projection parameters independently of camera, improve TRS editor, fix a bug with removing virtual events from hash map --- 61_UI/include/keysmapping.hpp | 4 +- 61_UI/main.cpp | 235 +++++++++++++++------------------- 2 files changed, 107 insertions(+), 132 deletions(-) diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp index 053acff6e..474827b77 100644 --- a/61_UI/include/keysmapping.hpp +++ b/61_UI/include/keysmapping.hpp @@ -143,7 +143,7 @@ void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow ImGui::Text("%.2f", hash.event.magnitude); ImGui::TableSetColumnIndex(4); - if (ImGui::Button(("Delete##deleteKey" + std::to_string(static_cast(hash.event.type))).c_str())) + if (ImGui::Button(("Delete##deleteKey" + std::to_string(static_cast(keyboardCode))).c_str())) { camera->updateKeyboardMapping([keyboardCode](auto& keys) { keys.erase(keyboardCode); }); break; @@ -200,7 +200,7 @@ void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow ImGui::Text("%.2f", hash.event.magnitude); ImGui::TableSetColumnIndex(4); - if (ImGui::Button(("Delete##deleteMouse" + std::to_string(static_cast(hash.event.type))).c_str())) + if (ImGui::Button(("Delete##deleteMouse" + std::to_string(static_cast(mouseCode))).c_str())) { camera->updateMouseMapping([mouseCode](auto& mouse) { mouse.erase(mouseCode); }); break; diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 14cc93a80..58ea61747 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -846,7 +846,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // create a window and insert the inspector ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); - ImGui::Begin("Editor"); + ImGui::Begin("TRS Editor"); //if (ImGui::RadioButton("Full view", !useWindow)) // useWindow = false; @@ -858,21 +858,21 @@ class UISampleApp final : public examples::SimpleWindowedApplication { auto projection = mutableRange.begin() + i; - if (isPerspective) + if (isPerspective[i]) { - if (isLH) - projection->setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio[i], zNear, zFar)); + if (isLH[i]) + projection->setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov[i]), aspectRatio[i], zNear[i], zFar[i])); else - projection->setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio[i], zNear, zFar)); + projection->setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov[i]), aspectRatio[i], zNear[i], zFar[i])); } else { - float viewHeight = viewWidth * invAspectRatio[i]; + float viewHeight = viewWidth[i] * invAspectRatio[i]; - if (isLH) - projection->setProjectionMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); + if (isLH[i]) + projection->setProjectionMatrix(buildProjectionMatrixOrthoLH(viewWidth[i], viewHeight, zNear[i], zFar[i])); else - projection->setProjectionMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); + projection->setProjectionMatrix(buildProjectionMatrixOrthoRH(viewWidth[i], viewHeight, zNear[i], zFar[i])); } } } @@ -882,38 +882,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication //if (ImGui::RadioButton("Window", useWindow)) // useWindow = true; - ImGui::Text("Camera"); - - if (ImGui::RadioButton("LH", isLH)) - isLH = true; - - ImGui::SameLine(); - - if (ImGui::RadioButton("RH", !isLH)) - isLH = true; // TMP WIP - //isLH = false; - - //if (ImGui::RadioButton("Perspective", isPerspective)) - // isPerspective = true; - - //ImGui::SameLine(); - - //if (ImGui::RadioButton("Orthographic", !isPerspective)) - // isPerspective = false; - - ImGui::SliderFloat("Move speed", &moveSpeed, 0.1f, 10.f); - ImGui::SliderFloat("Rotate speed", &rotateSpeed, 0.1f, 10.f); - // ImGui::Checkbox("Flip Gizmo's Y axis", &flipGizmoY); // let's not expose it to be changed in UI but keep the logic in case - if (isPerspective) - ImGui::SliderFloat("Fov", &fov, 20.f, 150.f); - else - ImGui::SliderFloat("Ortho width", &viewWidth, 1, 20); - - ImGui::SliderFloat("zNear", &zNear, 0.1f, 100.f); - ImGui::SliderFloat("zFar", &zFar, 110.f, 10000.f); - ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); if (ImGuizmo::IsUsing()) { @@ -1088,6 +1058,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { lastManipulatedModelIx = matId; lastManipulatedGizmoIx = gizmoID; + lastManipulatedModelIdentifier = lastManipulatedModelIx == 0 ? "Geometry Creator Object" : "Camera FPS"; } if (ImGuizmo::IsOver()) @@ -1104,9 +1075,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); - ImGui::Text("Type: %s", matId == 0 ? "Geometry Creator Object" : "Camera FPS"); - ImGui::Text("Model ID: %u", matId); - ImGui::Text("Gizmo ID: %u", gizmoID); + ImGui::Text("Identifier: %s", lastManipulatedModelIdentifier.c_str()); + ImGui::Text("Object Ix: %u", matId); + //ImGui::Text("Gizmo Ix: %u", gizmoID); ImGui::End(); @@ -1169,8 +1140,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // update scenes data // to Nabla + update camera & model matrices - // TODO: make it more nicely - std::string_view mName; + // TODO: make it more nicely once view manipulate supported //const_cast(firstcamera->getGimbal().getViewMatrix()) = float64_t3x4(getCastedMatrix(transpose(imguizmoM16InOut.view[0u]))); // a hack for "view manipulate", correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) { m_model = float32_t3x4(transpose(imguizmoM16InOut.outModel[0])); @@ -1194,7 +1164,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto& hook = scene->object; hook.meta.type = type; - mName = hook.meta.name = meta.name; + hook.meta.name = meta.name; { float32_t3x4 modelView, normal; float32_t4x4 modelViewProjection; @@ -1216,81 +1186,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication } { - auto addMatrixTable = [&](const char* topText, const char* tableName, int rows, int columns, const float* pointer, bool withSeparator = true) - { - ImGui::Text(topText); - ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg)); - ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg)); - if (ImGui::BeginTable(tableName, columns, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame)) - { - for (int y = 0; y < rows; ++y) - { - ImGui::TableNextRow(); - for (int x = 0; x < columns; ++x) - { - ImGui::TableSetColumnIndex(x); - if (pointer) - ImGui::Text("%.3f", *(pointer + (y * columns) + x)); - else - ImGui::Text("-"); - } - } - ImGui::EndTable(); - } - ImGui::PopStyleColor(2); - if (withSeparator) - ImGui::Separator(); - }; - - // Scene Models - { - ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); - ImGui::Begin("Object Info and Model Matrix", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); - - ImGui::Text("Type: \"%s\"", mName.data()); - ImGui::Separator(); - - ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); - ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered)); - - addMatrixTable("Scene Object Model Matrix", "ModelMatrixTable", 3, 4, &m_model[0][0]); - - ImGui::PopStyleColor(2); - ImGui::End(); - } - - /* - - // ImGuizmo inputs - { - ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); - ImGui::Begin("ImGuizmo model inputs", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); - - ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); - ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_FrameBgHovered)); - - ImGui::Text("Type: GC Object"); - ImGui::Separator(); - - addMatrixTable("Model", "GCIMGUIZMOTRS", 4, 4, &imguizmoM16InOut.inModel[0][0][0]); - - ImGui::Text("Type: Camera ID \"1\" object"); - ImGui::Separator(); - - addMatrixTable("Model", "CAMERASECIMGUIZMOTRS", 4, 4, &imguizmoM16InOut.inModel[1][0][0]); - - ImGui::PopStyleColor(2); - ImGui::End(); - } - - */ - - //imguizmoM16InOut.inModel - { ImGuiIO& io = ImGui::GetIO(); - if (ImGui::IsKeyPressed(ImGuiKey_C)) + if (ImGui::IsKeyPressed(ImGuiKey_LeftShift)) enableActiveCameraMovement = !enableActiveCameraMovement; if (enableActiveCameraMovement) @@ -1346,6 +1245,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::TreeNodeEx(treeNodeLabel.c_str(), flags)) { + ImGui::SliderFloat("Move speed", &moveSpeed[cameraIndex], 0.1f, 10.f); + ImGui::SliderFloat("Rotate speed", &rotateSpeed[cameraIndex], 0.1f, 10.f); + if (ImGui::TreeNodeEx("Data", ImGuiTreeNodeFlags_None)) { auto& gimbal = camera->getGimbal(); @@ -1376,6 +1278,39 @@ class UISampleApp final : public examples::SimpleWindowedApplication } + // Projections + { + ImGui::Begin("Projection"); + + ImGui::Text("Ix: %s", std::to_string(lastProjectionIx).c_str()); + + if (ImGui::RadioButton("Perspective", isPerspective[lastProjectionIx])) + isPerspective[lastProjectionIx] = true; + + ImGui::SameLine(); + + if (ImGui::RadioButton("Orthographic", !isPerspective[lastProjectionIx])) + isPerspective[lastProjectionIx] = false; + + if (ImGui::RadioButton("LH", isLH[lastProjectionIx])) + isLH[lastProjectionIx] = true; + + ImGui::SameLine(); + + if (ImGui::RadioButton("RH", !isLH[lastProjectionIx])) + isLH[lastProjectionIx] = false; + + if (isPerspective[lastProjectionIx]) + ImGui::SliderFloat("Fov", &fov[lastProjectionIx], 20.f, 150.f, "%.1f"); + else + ImGui::SliderFloat("Ortho width", &viewWidth[lastProjectionIx], 1.f, 20.f, "%.1f"); + + ImGui::SliderFloat("zNear", &zNear[lastProjectionIx], 0.1f, 100.f, "%.2f"); + ImGui::SliderFloat("zFar", &zFar[lastProjectionIx], 110.f, 10000.f, "%.1f"); + + ImGui::End(); + } + /* // Nabla Imgui backend MDI buffer info @@ -1452,13 +1387,21 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } - void TransformEditor(float* matrix) + inline void TransformEditor(float* matrix) { static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; static bool boundSizing = false; static bool boundSizingSnap = false; + ImGui::Text("Object Ix: \"%s\"", std::to_string(lastManipulatedModelIx).c_str()); + ImGui::Separator(); + + ImGui::Text("Identifier: \"%s\"", lastManipulatedModelIdentifier.c_str()); + ImGui::Separator(); + + addMatrixTable("Model (TRS) Matrix", "ModelMatrixTable", 4, 4, matrix); + /* if (ImGui::IsKeyPressed(ImGuiKey_T)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; @@ -1492,8 +1435,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication mCurrentGizmoMode = ImGuizmo::WORLD; } - if (ImGui::IsKeyPressed(ImGuiKey_S)) - useSnap = !useSnap; + //if (ImGui::IsKeyPressed(ImGuiKey_S)) + // useSnap = !useSnap; + ImGui::Checkbox(" ", &useSnap); ImGui::SameLine(); switch (mCurrentGizmoOperation) @@ -1510,7 +1454,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - void TransformStart(uint32_t wCameraIndex) + inline void TransformStart(uint32_t wCameraIndex) { ImGuiIO& io = ImGui::GetIO(); static ImGuiWindowFlags gizmoWindowFlags = ImGuiWindowFlags_NoMove; @@ -1552,6 +1496,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) + lastProjectionIx = (int)(wCameraIndex + 1u); } else { @@ -1567,14 +1514,15 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - } + lastProjectionIx = 0; + } ImGuiWindow* window = ImGui::GetCurrentWindow(); gizmoWindowFlags = ImGuiWindowFlags_NoMove;//(ImGuizmo::IsUsing() && ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); } - void TransformEnd() + inline void TransformEnd() { if (useWindow) { @@ -1583,6 +1531,32 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::PopStyleColor(1); } + inline void addMatrixTable(const char* topText, const char* tableName, int rows, int columns, const float* pointer, bool withSeparator = true) + { + ImGui::Text(topText); + ImGui::PushStyleColor(ImGuiCol_TableRowBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg)); + ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg)); + if (ImGui::BeginTable(tableName, columns, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame)) + { + for (int y = 0; y < rows; ++y) + { + ImGui::TableNextRow(); + for (int x = 0; x < columns; ++x) + { + ImGui::TableSetColumnIndex(x); + if (pointer) + ImGui::Text("%.3f", *(pointer + (y * columns) + x)); + else + ImGui::Text("-"); + } + } + ImGui::EndTable(); + } + ImGui::PopStyleColor(2); + if (withSeparator) + ImGui::Separator(); + } + std::chrono::seconds timeout = std::chrono::seconds(0x7fffFFFFu); clock_t::time_point start; @@ -1655,19 +1629,20 @@ class UISampleApp final : public examples::SimpleWindowedApplication using linear_projection_t = CLinearProjection; nbl::core::smart_refctd_ptr projections; - bool isPerspective[ProjectionsCount] = { true, true, true }, isLH = true, flipGizmoY = true; - float fov = 60.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; - float viewWidth = 10.f; + bool flipGizmoY = true; + std::array isPerspective = { true, true, true }, isLH = { true, true, true }; + std::array fov = { 60.f, 60.f, 60.f }, zNear = { 0.1f, 0.1f, 0.1f }, zFar = { 10000.f, 10000.f, 10000.f }, viewWidth = { 10.f, 10.f, 10.f }, aspectRatio = {}, invAspectRatio = {}; + std::array moveSpeed = { 1.f, 1.f }, rotateSpeed = { 1.f, 1.f }; + float camYAngle = 165.f / 180.f * 3.14159f; float camXAngle = 32.f / 180.f * 3.14159f; - - float camDistance = 8.f, aspectRatio[ProjectionsCount] = {}, invAspectRatio[ProjectionsCount] = {}; + float camDistance = 8.f; bool useWindow = true, useSnap = false; ImGuizmo::OPERATION mCurrentGizmoOperation = ImGuizmo::TRANSLATE; ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::LOCAL; float snap[3] = { 1.f, 1.f, 1.f }; - int lastManipulatedModelIx = 0; - int lastManipulatedGizmoIx = 0; + int lastManipulatedModelIx = 0, lastManipulatedGizmoIx = 0, lastProjectionIx = 0; + std::string lastManipulatedModelIdentifier = "Geometry Creator Object"; bool firstFrame = true; }; From 2242bf19cd1438a84f45c1e07cda47b643ac579d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 13 Dec 2024 15:46:24 +0100 Subject: [PATCH 55/84] increase FBOs resolution, make full screen mode work again after updates --- 61_UI/main.cpp | 481 +++++++++--------------- common/include/CGeomtryCreatorScene.hpp | 58 +-- 2 files changed, 221 insertions(+), 318 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 58ea61747..af88e8a8a 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -185,10 +185,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication using base_t = examples::SimpleWindowedApplication; using clock_t = std::chrono::steady_clock; - //_NBL_STATIC_INLINE_CONSTEXPR uint32_t WIN_W = 1280, WIN_H = 720; - constexpr static inline clock_t::duration DisplayImageDuration = std::chrono::milliseconds(900); + enum CameraRenderImguiTextureIx + { + OfflineSceneFirstCameraTextureIx = 1u, + OfflineSceneSecondCameraTextureIx = 2u + }; + public: using base_t::base_t; @@ -465,10 +469,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication // projections projections = linear_projection_t::create(smart_refctd_ptr(cameraz.front())); + const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); for (uint32_t i = 0u; i < scenez.size(); ++i) { auto& scene = scenez[i]; - scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources)); + scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources), dpyInfo.resX, dpyInfo.resY); if (!scene) { @@ -566,11 +571,15 @@ class UISampleApp final : public examples::SimpleWindowedApplication bool willSubmit = true; { + willSubmit &= cmdbuf->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); + willSubmit &= cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + willSubmit &= cmdbuf->beginDebugMarker("UIApp Frame"); + // render geometry creator scene to offline frame buffer & submit // TODO: OK with TRI buffer this thing is retarded now // (**) <- a note why bellow before submit - for (auto scene : scenez) + auto renderOfflineScene = [&](auto& scene) { scene->begin(); { @@ -579,11 +588,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication scene->end(); } scene->submit(getGraphicsQueue()); - } + }; - willSubmit &= cmdbuf->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); - willSubmit &= cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); - willSubmit &= cmdbuf->beginDebugMarker("UIApp Frame"); + if (useWindow) + for (auto scene : scenez) + renderOfflineScene(scene); + else + renderOfflineScene(scenez.front().get()); // just to not render to all at once const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; const IGPUCommandBuffer::SRenderpassBeginInfo info = { @@ -705,10 +716,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication } }; - // (**) -> wait on offline framebuffers + // (**) -> wait on offline framebuffers in window mode { const nbl::video::ISemaphore::SWaitInfo waitInfos[] = - { + { // wait for first camera scene view fb { .semaphore = scenez[0]->semaphore.progress.get(), @@ -721,7 +732,38 @@ class UISampleApp final : public examples::SimpleWindowedApplication }, }; - m_device->blockForSemaphores(waitInfos); + if (useWindow) + { + m_device->blockForSemaphores(std::to_array + ( + { + // wait for first camera scene view fb + { + .semaphore = scenez[0]->semaphore.progress.get(), + .value = scenez[0]->semaphore.finishedValue + }, + // and second one too + { + .semaphore = scenez[1]->semaphore.progress.get(), + .value = scenez[1]->semaphore.finishedValue + }, + } + )); + } + else + { + m_device->blockForSemaphores(std::to_array + ( + { + // wait for first only, we use it temporary for FS render mode + { + .semaphore = scenez.front()->semaphore.progress.get(), + .value = scenez.front()->semaphore.finishedValue + } + } + )); + } + updateGUIDescriptorSet(); } @@ -839,17 +881,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::SetOrthographic(false); ImGuizmo::BeginFrame(); - - ImGui::SetNextWindowPos(ImVec2(1024, 100), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(256, 256), ImGuiCond_Appearing); - - // create a window and insert the inspector - ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); - ImGui::Begin("TRS Editor"); - - //if (ImGui::RadioButton("Full view", !useWindow)) - // useWindow = false; auto projectionMatrices = projections->getLinearProjections(); { @@ -876,33 +907,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } } - - ImGui::SameLine(); - - //if (ImGui::RadioButton("Window", useWindow)) - // useWindow = true; - - // ImGui::Checkbox("Flip Gizmo's Y axis", &flipGizmoY); // let's not expose it to be changed in UI but keep the logic in case - - ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); - if (ImGuizmo::IsUsing()) - { - ImGui::Text("Using gizmo"); - } - else - { - /* - ImGui::Text(ImGuizmo::IsOver() ? "Over gizmo" : ""); - ImGui::SameLine(); - ImGui::Text(ImGuizmo::IsOver(ImGuizmo::TRANSLATE) ? "Over translate gizmo" : ""); - ImGui::SameLine(); - ImGui::Text(ImGuizmo::IsOver(ImGuizmo::ROTATE) ? "Over rotate gizmo" : ""); - ImGui::SameLine(); - ImGui::Text(ImGuizmo::IsOver(ImGuizmo::SCALE) ? "Over scale gizmo" : ""); - */ - } - ImGui::Separator(); - + /* * ImGuizmo expects view & perspective matrix to be column major both with 4x4 layout * and Nabla uses row major matricies - 3x4 matrix for view & 4x4 for projection @@ -981,30 +986,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication // and second camera's model too imguizmoM16InOut.inModel[1] = gimbalToImguizmoTRS(getCastedMatrix(secondCameraGimbalModel)); { - float* views[] - { - &imguizmoM16InOut.view[0u][0][0], // first camera - &imguizmoM16InOut.view[1u][0][0] // second camera - }; - - float* projections[] - { - &imguizmoM16InOut.projection[0u][0][0], // full screen, tmp off - &imguizmoM16InOut.projection[1u][0][0], // first camera - &imguizmoM16InOut.projection[2u][0][0] // second camera - }; - if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates for(uint32_t i = 0u; i < ProjectionsCount; ++i) imguizmoM16InOut.projection[i][1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ - float* lastUsedModel = &imguizmoM16InOut.inModel[lastManipulatedModelIx][0][0]; - - // ImGuizmo manipulations on the last used model matrix - TransformEditor(lastUsedModel); - uint32_t gizmoID = {}; - bool manipulatedFromAnyWindow = false; - ImGuizmo::AllowAxisFlip(false); if(enableActiveCameraMovement) @@ -1012,84 +997,145 @@ class UISampleApp final : public examples::SimpleWindowedApplication else ImGuizmo::Enable(true); - // we have 2 GUI windows we render into with FBOs - for (uint32_t windowIndex = 0; windowIndex < 2u; ++windowIndex) + aspectRatio[0] = io.DisplaySize.x / io.DisplaySize.y; + invAspectRatio[0] = io.DisplaySize.y / io.DisplaySize.x; + + SImResourceInfo info; + info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; + + // render camera views onto GUIs + if (useWindow) { - auto* cameraView = views[windowIndex]; - auto* cameraProjection = projections[1u + windowIndex]; + // ImGuizmo manipulations on the last used model matrix in window mode + TransformEditor(&imguizmoM16InOut.inModel[lastManipulatedModelIx][0][0]); - TransformStart(windowIndex); + uint32_t gizmoIx = {}; + bool manipulatedFromAnyWindow = false; - // but only 2 objects with matrices we will try to manipulate & we can do this from both windows! - // however note it only makes sense to obsly assume we cannot manipulate 2 objects at the same time - for (uint32_t matId = 0; matId < 2; matId++) + // we have 2 GUI windows we render into with FBOs + for (uint32_t windowIndex = 0; windowIndex < 2u; ++windowIndex) { - // future logic will need to filter gizmos which represents gimbal of camera which view matrix we use to render scene with - if (gizmoID == 3) - continue; - - // and because of imguizmo API usage to achieve it we must work on copies & filter the output (unless we try gizmo enable/disable) - // -> in general we need to be careful to not edit the same model twice - auto model = imguizmoM16InOut.inModel[matId]; - float32_t4x4 deltaOutputTRS; + const auto& cameraIx = windowIndex; // tmp bound & hardcoded, we will extend it later + const auto projectionIx = cameraIx + 1u; // offset because first projection belongs to full screen (**) + info.textureID = projectionIx; + + ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImVec2(400, 20 + cameraIx * 420), ImGuiCond_Appearing); + ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); + std::string ident = "Camera \"" + std::to_string(cameraIx) + "\" View"; + ImGui::Begin(ident.data(), 0, ImGuiWindowFlags_NoMove); + ImGuizmo::SetDrawlist(); + + ImVec2 contentRegionSize, windowPos, cursorPos; + contentRegionSize = ImGui::GetContentRegionAvail(); + cursorPos = ImGui::GetCursorScreenPos(); + windowPos = ImGui::GetWindowPos(); + + // (**) + aspectRatio[projectionIx] = contentRegionSize.x / contentRegionSize.y; + invAspectRatio[projectionIx] = contentRegionSize.y / contentRegionSize.x; + + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) + lastProjectionIx = projectionIx; + + // but only 2 objects with matrices we will try to manipulate & we can do this from both windows! + // however note it only makes sense to obsly assume we cannot manipulate 2 objects at the same time + for (uint32_t modelIx = 0; modelIx < 2; modelIx++) + { + // future logic will need to filter gizmos which represents gimbal of camera which view matrix we use to render scene with + if (gizmoIx == 3) + continue; - // note we also need to take care of unique gizmo IDs, we have in total 4 gizmos even though we only want to manipulate 2 objects in total - ImGuizmo::PushID(gizmoID); + // and because of imguizmo API usage to achieve it we must work on copies & filter the output (unless we try gizmo enable/disable) + // -> in general we need to be careful to not edit the same model twice + + auto model = imguizmoM16InOut.inModel[modelIx]; + float32_t4x4 deltaOutputTRS; - ImGuiIO& io = ImGui::GetIO(); - const bool success = ImGuizmo::Manipulate(cameraView, cameraProjection, mCurrentGizmoOperation, mCurrentGizmoMode, &model[0][0], &deltaOutputTRS[0][0], useSnap ? &snap[0] : NULL); + // note we also need to take care of unique gizmo IDs, we have in total 4 gizmos even though we only want to manipulate 2 objects in total + ImGuizmo::PushID(gizmoIx); - // we manipulated a gizmo from a X-th window, now we update output matrices and assume no more gizmos can be manipulated at the same frame - if (!manipulatedFromAnyWindow) - { - // TMP WIP, our imguizmo controller doesnt support rotation & scale yet - auto discard = gizmoID == 1 && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; - if (!discard) + ImGuiIO& io = ImGui::GetIO(); + + const bool success = ImGuizmo::Manipulate(&imguizmoM16InOut.view[cameraIx][0][0], &imguizmoM16InOut.projection[projectionIx][0][0], mCurrentGizmoOperation, mCurrentGizmoMode, &model[0][0], &deltaOutputTRS[0][0], useSnap ? &snap[0] : nullptr); + + // we manipulated a gizmo from a X-th window, now we update output matrices and assume no more gizmos can be manipulated at the same frame + if (!manipulatedFromAnyWindow) { - imguizmoM16InOut.outModel[matId] = model; - imguizmoM16InOut.outDeltaTRS[matId] = deltaOutputTRS; + // TMP WIP, our imguizmo controller doesnt support rotation & scale yet + auto discard = gizmoIx == 1 && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; + if (!discard) + { + imguizmoM16InOut.outModel[modelIx] = model; + imguizmoM16InOut.outDeltaTRS[modelIx] = deltaOutputTRS; + } } - } - if (success) - manipulatedFromAnyWindow = true; + if (success) + manipulatedFromAnyWindow = true; - if (ImGuizmo::IsUsing()) - { - lastManipulatedModelIx = matId; - lastManipulatedGizmoIx = gizmoID; - lastManipulatedModelIdentifier = lastManipulatedModelIx == 0 ? "Geometry Creator Object" : "Camera FPS"; - } + if (ImGuizmo::IsUsing()) + { + lastManipulatedModelIx = modelIx; + lastManipulatedGizmoIx = gizmoIx; + lastManipulatedModelIdentifier = lastManipulatedModelIx == 0 ? "Geometry Creator Object" : "Camera FPS"; + } - if (ImGuizmo::IsOver()) - { - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); + if (ImGuizmo::IsOver()) + { + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); - ImVec2 mousePos = io.MousePos; - ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); + ImVec2 mousePos = io.MousePos; + ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); - ImGui::Begin("InfoOverlay", NULL, - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings); + ImGui::Begin("InfoOverlay", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings); - ImGui::Text("Identifier: %s", lastManipulatedModelIdentifier.c_str()); - ImGui::Text("Object Ix: %u", matId); - //ImGui::Text("Gizmo Ix: %u", gizmoID); + ImGui::Text("Identifier: %s", lastManipulatedModelIdentifier.c_str()); + ImGui::Text("Object Ix: %u", modelIx); - ImGui::End(); + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } - ImGui::PopStyleVar(); - ImGui::PopStyleColor(2); + ImGuizmo::PopID(); + ++gizmoIx; } - ImGuizmo::PopID(); - ++gizmoID; + ImGui::End(); + ImGui::PopStyleColor(1); } + } + // render selected camera view onto full screen + else + { + info.textureID = OfflineSceneFirstCameraTextureIx;; + lastProjectionIx = 0; + + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window + ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); + + ImVec2 contentRegionSize, windowPos, cursorPos; + contentRegionSize = ImGui::GetContentRegionAvail(); + cursorPos = ImGui::GetCursorScreenPos(); + windowPos = ImGui::GetWindowPos(); - TransformEnd(); + ImGui::Image(info, contentRegionSize); + ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + + ImGui::End(); + ImGui::PopStyleColor(1); } } @@ -1169,13 +1215,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication float32_t3x4 modelView, normal; float32_t4x4 modelViewProjection; - const auto& viewMatrix = *views[i]; + const auto& viewMatrix = *views[useWindow ? i : activeCameraIndex]; modelView = concatenateBFollowedByA(viewMatrix, m_model); // TODO //modelView.getSub3x3InverseTranspose(normal); - auto concatMatrix = mul(getCastedMatrix(projectionMatrices[i + 1u].getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); + auto concatMatrix = mul(getCastedMatrix(projectionMatrices[useWindow ? i + 1u : 0u].getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(m_model)); memcpy(hook.viewParameters.MVP, &modelViewProjection[0][0], sizeof(hook.viewParameters.MVP)); @@ -1206,6 +1252,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } ImGui::Begin("Cameras", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); + ImGui::Checkbox("Window mode##useWindow", &useWindow); ImGui::Text("Select Active Camera:"); ImGui::Separator(); @@ -1310,81 +1357,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } - - /* - - // Nabla Imgui backend MDI buffer info - // To be 100% accurate and not overly conservative we'd have to explicitly `cull_frees` and defragment each time, - // so unless you do that, don't use this basic info to optimize the size of your IMGUI buffer. - { - auto* streaminingBuffer = m_ui.manager->getStreamingBuffer(); - - const size_t total = streaminingBuffer->get_total_size(); // total memory range size for which allocation can be requested - const size_t freeSize = streaminingBuffer->getAddressAllocator().get_free_size(); // max total free bloock memory size we can still allocate from total memory available - const size_t consumedMemory = total - freeSize; // memory currently consumed by streaming buffer - - float freePercentage = 100.0f * (float)(freeSize) / (float)total; - float allocatedPercentage = (float)(consumedMemory) / (float)total; - - ImVec2 barSize = ImVec2(400, 30); - float windowPadding = 10.0f; - float verticalPadding = ImGui::GetStyle().FramePadding.y; - - ImGui::SetNextWindowSize(ImVec2(barSize.x + 2 * windowPadding, 110 + verticalPadding), ImGuiCond_Always); - ImGui::Begin("Nabla Imgui MDI Buffer Info", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar); - - ImGui::Text("Total Allocated Size: %zu bytes", total); - ImGui::Text("In use: %zu bytes", consumedMemory); - ImGui::Text("Buffer Usage:"); - - ImGui::SetCursorPosX(windowPadding); - - if (freePercentage > 70.0f) - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.0f, 1.0f, 0.0f, 0.4f)); // Green - else if (freePercentage > 30.0f) - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(1.0f, 1.0f, 0.0f, 0.4f)); // Yellow - else - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(1.0f, 0.0f, 0.0f, 0.4f)); // Red - - ImGui::ProgressBar(allocatedPercentage, barSize, ""); - - ImGui::PopStyleColor(); - - ImDrawList* drawList = ImGui::GetWindowDrawList(); - - ImVec2 progressBarPos = ImGui::GetItemRectMin(); - ImVec2 progressBarSize = ImGui::GetItemRectSize(); - - const char* text = "%.2f%% free"; - char textBuffer[64]; - snprintf(textBuffer, sizeof(textBuffer), text, freePercentage); - - ImVec2 textSize = ImGui::CalcTextSize(textBuffer); - ImVec2 textPos = ImVec2 - ( - progressBarPos.x + (progressBarSize.x - textSize.x) * 0.5f, - progressBarPos.y + (progressBarSize.y - textSize.y) * 0.5f - ); - - ImVec4 bgColor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); - drawList->AddRectFilled - ( - ImVec2(textPos.x - 5, textPos.y - 2), - ImVec2(textPos.x + textSize.x + 5, textPos.y + textSize.y + 2), - ImGui::GetColorU32(bgColor) - ); - - ImGui::SetCursorScreenPos(textPos); - ImGui::Text("%s", textBuffer); - - ImGui::Dummy(ImVec2(0.0f, verticalPadding)); - - ImGui::End(); - } - - */ - - ImGui::End(); } inline void TransformEditor(float* matrix) @@ -1394,6 +1366,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication static bool boundSizing = false; static bool boundSizingSnap = false; + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); + ImGui::Begin("TRS Editor"); + ImGui::SameLine(); + + ImGuiIO& io = ImGui::GetIO(); + ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); + if (ImGuizmo::IsUsing()) + ImGui::Text("Using gizmo"); + ImGui::Separator(); + ImGui::Text("Object Ix: \"%s\"", std::to_string(lastManipulatedModelIx).c_str()); ImGui::Separator(); @@ -1402,15 +1385,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication addMatrixTable("Model (TRS) Matrix", "ModelMatrixTable", 4, 4, matrix); - /* - if (ImGui::IsKeyPressed(ImGuiKey_T)) - mCurrentGizmoOperation = ImGuizmo::TRANSLATE; - if (ImGui::IsKeyPressed(ImGuiKey_E)) - mCurrentGizmoOperation = ImGuizmo::ROTATE; - if (ImGui::IsKeyPressed(ImGuiKey_R)) - mCurrentGizmoOperation = ImGuizmo::SCALE; - */ - if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; ImGui::SameLine(); @@ -1435,9 +1409,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication mCurrentGizmoMode = ImGuizmo::WORLD; } - //if (ImGui::IsKeyPressed(ImGuiKey_S)) - // useSnap = !useSnap; - ImGui::Checkbox(" ", &useSnap); ImGui::SameLine(); switch (mCurrentGizmoOperation) @@ -1452,83 +1423,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::InputFloat("Scale Snap", &snap[0]); break; } - } - - inline void TransformStart(uint32_t wCameraIndex) - { - ImGuiIO& io = ImGui::GetIO(); - static ImGuiWindowFlags gizmoWindowFlags = ImGuiWindowFlags_NoMove; - - SImResourceInfo info; - switch (wCameraIndex) - { - case 0: - info.textureID = OfflineSceneFirstCameraTextureIx; - break; - case 1: - info.textureID = OfflineSceneSecondCameraTextureIx; - break; - default: - assert(false); // "wCameraIndex OOB!" - break; - } - info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; - - aspectRatio[0] = io.DisplaySize.x / io.DisplaySize.y; - invAspectRatio[0] = io.DisplaySize.y / io.DisplaySize.x; - - if (useWindow) - { - ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); - ImGui::SetNextWindowPos(ImVec2(400, 20 + wCameraIndex * 420), ImGuiCond_Appearing); - ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); - std::string ident = "Camera \"" + std::to_string(wCameraIndex) + "\" View"; - ImGui::Begin(ident.data(), 0, gizmoWindowFlags); - ImGuizmo::SetDrawlist(); - - ImVec2 contentRegionSize, windowPos, cursorPos; - contentRegionSize = ImGui::GetContentRegionAvail(); - cursorPos = ImGui::GetCursorScreenPos(); - windowPos = ImGui::GetWindowPos(); - - aspectRatio[wCameraIndex + 1] = contentRegionSize.x / contentRegionSize.y; - invAspectRatio[wCameraIndex + 1] = contentRegionSize.y / contentRegionSize.x; - - ImGui::Image(info, contentRegionSize); - ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) - lastProjectionIx = (int)(wCameraIndex + 1u); - } - else - { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(io.DisplaySize); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window - ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); - - ImVec2 contentRegionSize, windowPos, cursorPos; - contentRegionSize = ImGui::GetContentRegionAvail(); - cursorPos = ImGui::GetCursorScreenPos(); - windowPos = ImGui::GetWindowPos(); - ImGui::Image(info, contentRegionSize); - ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - lastProjectionIx = 0; - } - - ImGuiWindow* window = ImGui::GetCurrentWindow(); - gizmoWindowFlags = ImGuiWindowFlags_NoMove;//(ImGuizmo::IsUsing() && ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max) ? ImGuiWindowFlags_NoMove : 0); - } - - inline void TransformEnd() - { - if (useWindow) - { - ImGui::End(); - } - ImGui::PopStyleColor(1); + ImGui::End(); } inline void addMatrixTable(const char* topText, const char* tableName, int rows, int columns, const float* pointer, bool withSeparator = true) @@ -1617,9 +1513,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication bool enableActiveCameraMovement = false; nbl::core::smart_refctd_ptr resources; - static constexpr inline auto OfflineSceneFirstCameraTextureIx = 1u; - static constexpr inline auto OfflineSceneSecondCameraTextureIx = 2u; - CRenderUI m_ui; video::CDumbPresentationOracle oracle; uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed diff --git a/common/include/CGeomtryCreatorScene.hpp b/common/include/CGeomtryCreatorScene.hpp index e744b795e..444a58b4d 100644 --- a/common/include/CGeomtryCreatorScene.hpp +++ b/common/include/CGeomtryCreatorScene.hpp @@ -19,7 +19,7 @@ using namespace system struct Traits { - static constexpr auto FramebufferW = 1280u, FramebufferH = 720u; + static constexpr auto DefaultFramebufferW = 1280u, DefaultFramebufferH = 720u; static constexpr auto ColorFboAttachmentFormat = nbl::asset::EF_R8G8B8A8_SRGB, DepthFboAttachmentFormat = nbl::asset::EF_D16_UNORM; static constexpr auto Samples = nbl::video::IGPUImage::ESCF_1_BIT; static constexpr nbl::video::IGPUCommandBuffer::SClearColorValue clearColor = { .float32 = {0.f,0.f,0.f,1.f} }; @@ -515,7 +515,7 @@ class CScene final : public nbl::core::IReferenceCounted nbl::core::smart_refctd_ptr progress; } semaphore; - static inline nbl::core::smart_refctd_ptr create(nbl::core::smart_refctd_ptr device, nbl::core::smart_refctd_ptr logger, nbl::video::CThreadSafeQueueAdapter* const transferCapableQueue, const nbl::core::smart_refctd_ptr resources) + static inline nbl::core::smart_refctd_ptr create(nbl::core::smart_refctd_ptr device, nbl::core::smart_refctd_ptr logger, nbl::video::CThreadSafeQueueAdapter* const transferCapableQueue, const nbl::core::smart_refctd_ptr resources, const uint32_t framebufferW = Traits::DefaultFramebufferW, const uint32_t framebufferH = Traits::DefaultFramebufferH) { EXPOSE_NABLA_NAMESPACES(); @@ -645,7 +645,7 @@ class CScene final : public nbl::core::IReferenceCounted .type = IGPUImage::ET_2D, .samples = Traits::Samples, .format = format, - .extent = { Traits::FramebufferW, Traits::FramebufferH, 1u }, + .extent = { framebufferW, framebufferH, 1u }, .mipLevels = 1u, .arrayLayers = 1u, .usage = USAGE @@ -742,7 +742,6 @@ class CScene final : public nbl::core::IReferenceCounted inline bool record() { EXPOSE_NABLA_NAMESPACES(); - bool valid = true; const struct @@ -783,24 +782,7 @@ class CScene final : public nbl::core::IReferenceCounted }; valid &= m_commandBuffer->beginRenderPass(info, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); - - const auto& [hook, meta] = m_resources->objects[object.meta.type]; - const auto* rawPipeline = hook.pipeline.get(); - - SBufferBinding vertex = hook.bindings.vertex, index = hook.bindings.index; - - valid &= m_commandBuffer->bindGraphicsPipeline(rawPipeline); - valid &= m_commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 1, 1, &m_ds.get()); - valid &= m_commandBuffer->bindVertexBuffers(0, 1, &vertex); - - if (index.buffer && hook.indexType != EIT_UNKNOWN) - { - valid &= m_commandBuffer->bindIndexBuffer(index, hook.indexType); - valid &= m_commandBuffer->drawIndexed(hook.indexCount, 1, 0, 0, 0); - } - else - valid &= m_commandBuffer->draw(hook.indexCount, 1, 0, 0); - + valid &= draw(m_commandBuffer.get()); valid &= m_commandBuffer->endRenderPass(); return valid; @@ -837,7 +819,7 @@ class CScene final : public nbl::core::IReferenceCounted return queue->submit(infos) == IQueue::RESULT::SUCCESS; } - inline void update() + inline bool update(nbl::video::IGPUCommandBuffer* cmdbuf = nullptr) { EXPOSE_NABLA_NAMESPACES(); @@ -845,12 +827,40 @@ class CScene final : public nbl::core::IReferenceCounted range.buffer = smart_refctd_ptr(m_ubo.buffer); range.size = m_ubo.buffer->getSize(); - m_commandBuffer->updateBuffer(range, &object.viewParameters); + if(cmdbuf) + return cmdbuf->updateBuffer(range, &object.viewParameters); + + return m_commandBuffer->updateBuffer(range, &object.viewParameters); } inline auto getColorAttachment() { return nbl::core::smart_refctd_ptr(m_color); } private: + inline bool draw(nbl::video::IGPUCommandBuffer* cmdbuf) + { + EXPOSE_NABLA_NAMESPACES(); + bool valid = true; + + const auto& [hook, meta] = m_resources->objects[object.meta.type]; + const auto* rawPipeline = hook.pipeline.get(); + + SBufferBinding vertex = hook.bindings.vertex, index = hook.bindings.index; + + valid &= cmdbuf->bindGraphicsPipeline(rawPipeline); + valid &= cmdbuf->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 1, 1, &m_ds.get()); + valid &= cmdbuf->bindVertexBuffers(0, 1, &vertex); + + if (index.buffer && hook.indexType != EIT_UNKNOWN) + { + valid &= cmdbuf->bindIndexBuffer(index, hook.indexType); + valid &= cmdbuf->drawIndexed(hook.indexCount, 1, 0, 0, 0); + } + else + valid &= cmdbuf->draw(hook.indexCount, 1, 0, 0); + + return valid; + } + CScene(nbl::core::smart_refctd_ptr device, nbl::core::smart_refctd_ptr logger, nbl::core::smart_refctd_ptr commandBuffer, nbl::core::smart_refctd_ptr frameBuffer, nbl::core::smart_refctd_ptr ds, nbl::asset::SBufferBinding ubo, nbl::core::smart_refctd_ptr color, nbl::core::smart_refctd_ptr depth, const nbl::core::smart_refctd_ptr resources) : m_device(nbl::core::smart_refctd_ptr(device)), m_logger(nbl::core::smart_refctd_ptr(logger)), m_commandBuffer(nbl::core::smart_refctd_ptr(commandBuffer)), m_frameBuffer(nbl::core::smart_refctd_ptr(frameBuffer)), m_ds(nbl::core::smart_refctd_ptr(ds)), m_ubo(ubo), m_color(nbl::core::smart_refctd_ptr(color)), m_depth(nbl::core::smart_refctd_ptr(depth)), m_resources(nbl::core::smart_refctd_ptr(resources)) {} From 85747c6e23da43e29ca015e8d7bf794489167b6f Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 13 Dec 2024 16:01:12 +0100 Subject: [PATCH 56/84] disable WiP features (display on red or vanish) --- 61_UI/main.cpp | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index af88e8a8a..921ea2413 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1395,9 +1395,23 @@ class UISampleApp final : public examples::SimpleWindowedApplication mCurrentGizmoOperation = ImGuizmo::SCALE; float matrixTranslation[3], matrixRotation[3], matrixScale[3]; ImGuizmo::DecomposeMatrixToComponents(matrix, matrixTranslation, matrixRotation, matrixScale); - ImGui::InputFloat3("Tr", matrixTranslation); - ImGui::InputFloat3("Rt", matrixRotation); - ImGui::InputFloat3("Sc", matrixScale); + const bool isCameraModelBound = lastManipulatedModelIx == 1u; + { + ImGuiInputTextFlags flags = 0; + + if (isCameraModelBound) // TODO: cameras are WiP here + { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(1.0f, 0.0f, 0.0f, 0.5f)); + flags |= ImGuiInputTextFlags_ReadOnly; + } + + ImGui::InputFloat3("Tr", matrixTranslation, "%.3f", flags); + ImGui::InputFloat3("Rt", matrixRotation, "%.3f", flags); + ImGui::InputFloat3("Sc", matrixScale, "%.3f", flags); + + if(isCameraModelBound) + ImGui::PopStyleColor(); + } ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix); if (mCurrentGizmoOperation != ImGuizmo::SCALE) @@ -1409,19 +1423,22 @@ class UISampleApp final : public examples::SimpleWindowedApplication mCurrentGizmoMode = ImGuizmo::WORLD; } - ImGui::Checkbox(" ", &useSnap); - ImGui::SameLine(); - switch (mCurrentGizmoOperation) + if (not isCameraModelBound) // TODO: cameras are WiP here { - case ImGuizmo::TRANSLATE: - ImGui::InputFloat3("Snap", &snap[0]); - break; - case ImGuizmo::ROTATE: - ImGui::InputFloat("Angle Snap", &snap[0]); - break; - case ImGuizmo::SCALE: - ImGui::InputFloat("Scale Snap", &snap[0]); - break; + ImGui::Checkbox(" ", &useSnap); + ImGui::SameLine(); + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + ImGui::InputFloat3("Snap", &snap[0]); + break; + case ImGuizmo::ROTATE: + ImGui::InputFloat("Angle Snap", &snap[0]); + break; + case ImGuizmo::SCALE: + ImGui::InputFloat("Scale Snap", &snap[0]); + break; + } } ImGui::End(); From 1d799e1af6319f5989721cd38108ac8e3b12e93f Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 13 Dec 2024 16:05:37 +0100 Subject: [PATCH 57/84] add help text for disabling/enabling selected camera movement --- 61_UI/main.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 921ea2413..caf888e4e 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1279,6 +1279,28 @@ class UISampleApp final : public examples::SimpleWindowedApplication else ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Active Camera Movement: Disabled"); + if (ImGui::IsItemHovered()) + { + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); + + ImVec2 mousePos = ImGui::GetMousePos(); + ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); + + ImGui::Begin("HoverOverlay", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings); + + ImGui::Text("Press 'Left Shift' to Enable/Disable selected camera movement"); + + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + ImGui::Separator(); for (size_t cameraIndex = 0; cameraIndex < CamerazCount; ++cameraIndex) From 73c2a6a696c7c49ef444e0006c3901a8b36b19be Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 13 Dec 2024 17:20:21 +0100 Subject: [PATCH 58/84] Release & RWDI will need closer look, comment out more WiP --- 61_UI/main.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index caf888e4e..081c6e194 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1314,8 +1314,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::TreeNodeEx(treeNodeLabel.c_str(), flags)) { - ImGui::SliderFloat("Move speed", &moveSpeed[cameraIndex], 0.1f, 10.f); - ImGui::SliderFloat("Rotate speed", &rotateSpeed[cameraIndex], 0.1f, 10.f); + // WiP, does not affect camera yet + //ImGui::SliderFloat("Move speed", &moveSpeed[cameraIndex], 0.1f, 10.f); + //ImGui::SliderFloat("Rotate speed", &rotateSpeed[cameraIndex], 0.1f, 10.f); if (ImGui::TreeNodeEx("Data", ImGuiTreeNodeFlags_None)) { From 6ad873ee20c8bbf15f7e497907b9936f2f7f74e8 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 14 Dec 2024 14:50:51 +0100 Subject: [PATCH 59/84] allow to manipulate model gizmos in ortho mode, add sliders to move/rotation speed factors, fix issues with camera speed while being manipulated from imguizmo controller (write thoughts about sensitivity of event magnitudes), allow for axes flip for better visibility, bind camera movement into space instead of left shift, assume imguizmo controller generates events always from World space because of what its delta TRS matrix represents (no base map), add more docs --- 61_UI/main.cpp | 168 +++++++++++++------- common/include/camera/CFPSCamera.hpp | 22 ++- common/include/camera/ICamera.hpp | 2 +- common/include/camera/IGimbalController.hpp | 133 +++++++++++----- common/include/camera/IProjection.hpp | 4 +- 5 files changed, 231 insertions(+), 98 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 081c6e194..d5dc40d66 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -879,7 +879,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication { ImGuiIO& io = ImGui::GetIO(); - ImGuizmo::SetOrthographic(false); ImGuizmo::BeginFrame(); auto projectionMatrices = projections->getLinearProjections(); @@ -1007,7 +1006,39 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (useWindow) { // ImGuizmo manipulations on the last used model matrix in window mode - TransformEditor(&imguizmoM16InOut.inModel[lastManipulatedModelIx][0][0]); + IGimbalController::input_imguizmo_event_t deltaTRS; + TransformEditor(&imguizmoM16InOut.inModel[lastManipulatedModelIx][0][0], &deltaTRS); // TODO! DELTA TRS IS ONLY TRANSLATE TEMPORARY + + if (isCameraModelBound) + { + { + static std::vector virtualEvents(0x45); + + uint32_t vCount; + + secondcamera->beginInputProcessing(m_nextPresentationTimestamp); + { + secondcamera->process(nullptr, vCount); + + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); + + IGimbalController::SUpdateParameters params; + params.imguizmoEvents = { { deltaTRS } }; + secondcamera->process(virtualEvents.data(), vCount, params); + } + secondcamera->endInputProcessing(); + + // I start to think controller should be able to set sensitivity to scale magnitudes of generated events + // in order for camera to not keep any magnitude scalars like move or rotation speed scales + smart_refctd_ptr_static_cast(secondcamera)->setMoveSpeedScale(1); + smart_refctd_ptr_static_cast(secondcamera)->setRotationSpeedScale(1); + + // NOTE: generated events from ImGuizmo controller are always in world space! + if(vCount) + secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); + } + } uint32_t gizmoIx = {}; bool manipulatedFromAnyWindow = false; @@ -1019,6 +1050,16 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto projectionIx = cameraIx + 1u; // offset because first projection belongs to full screen (**) info.textureID = projectionIx; + if(isPerspective[projectionIx]) + ImGuizmo::SetOrthographic(false); + else + ImGuizmo::SetOrthographic(true); + + if (areAxesFlipped[projectionIx]) + ImGuizmo::AllowAxisFlip(true); + else + ImGuizmo::AllowAxisFlip(false); + ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(400, 20 + cameraIx * 420), ImGuiCond_Appearing); ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); @@ -1045,6 +1086,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication // however note it only makes sense to obsly assume we cannot manipulate 2 objects at the same time for (uint32_t modelIx = 0; modelIx < 2; modelIx++) { + const bool isCameraGizmoBound = gizmoIx == 1; + const bool discard = isCameraGizmoBound && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; + // future logic will need to filter gizmos which represents gimbal of camera which view matrix we use to render scene with if (gizmoIx == 3) continue; @@ -1065,8 +1109,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication // we manipulated a gizmo from a X-th window, now we update output matrices and assume no more gizmos can be manipulated at the same frame if (!manipulatedFromAnyWindow) { - // TMP WIP, our imguizmo controller doesnt support rotation & scale yet - auto discard = gizmoIx == 1 && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; + // TMP WIP, our imguizmo controller doesnt support rotation & scale yet and its because + // - there are numerical issues with imguizmo decompose/recompose TRS (and the author also says it) + // - in rotate mode delta TRS matrix contains translate part (how? no idea, maybe imguizmo bug) and it glitches everything + if (!discard) { imguizmoM16InOut.outModel[modelIx] = model; @@ -1081,7 +1127,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication { lastManipulatedModelIx = modelIx; lastManipulatedGizmoIx = gizmoIx; - lastManipulatedModelIdentifier = lastManipulatedModelIx == 0 ? "Geometry Creator Object" : "Camera FPS"; + isCameraModelBound = lastManipulatedModelIx == 1u; + + lastManipulatedModelIdentifier = isCameraModelBound ? "Camera FPS" : "Geometry Creator Object"; } if (ImGuizmo::IsOver()) @@ -1149,7 +1197,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGuizmo::IsUsingAny()) { uint32_t vCount; - auto nblManipulationMode = ICamera::Local; secondcamera->beginInputProcessing(m_nextPresentationTimestamp); { @@ -1159,30 +1206,28 @@ class UISampleApp final : public examples::SimpleWindowedApplication virtualEvents.resize(vCount); IGimbalController::SUpdateParameters params; - - auto getOrientationBasis = [&]() - { - auto orientationBasis = (float32_t3x3(1.f)); - - switch (mCurrentGizmoMode) - { - case ImGuizmo::LOCAL: orientationBasis = (float32_t3x3(getCastedMatrix(secondCameraGimbalModel))); break; - case ImGuizmo::WORLD: nblManipulationMode = ICamera::World; break; - default: assert(false); break; - } - - return orientationBasis; - }; - - params.imguizmoEvents = { { std::make_pair(imguizmoM16InOut.outDeltaTRS[1u], getOrientationBasis()) } }; + params.imguizmoEvents = { { imguizmoM16InOut.outDeltaTRS[1u] } }; secondcamera->process(virtualEvents.data(), vCount, params); } secondcamera->endInputProcessing(); - secondcamera->manipulate({ virtualEvents.data(), vCount }, nblManipulationMode); + // I start to think controller should be able to set sensitivity to scale magnitudes of generated events + // in order for camera to not keep any magnitude scalars like move or rotation speed scales + smart_refctd_ptr_static_cast(secondcamera)->setMoveSpeedScale(1); + smart_refctd_ptr_static_cast(secondcamera)->setRotationSpeedScale(1); + + // NOTE: generated events from ImGuizmo controller are always in world space! + secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); } } + for (uint32_t i = 0u; i < cameraz.size(); ++i) + { + auto& camera = cameraz[i]; + smart_refctd_ptr_static_cast(camera)->setMoveSpeedScale(moveSpeed[i]); + smart_refctd_ptr_static_cast(camera)->setRotationSpeedScale(rotateSpeed[i]); + } + // update scenes data // to Nabla + update camera & model matrices @@ -1235,7 +1280,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { ImGuiIO& io = ImGui::GetIO(); - if (ImGui::IsKeyPressed(ImGuiKey_LeftShift)) + if (ImGui::IsKeyPressed(ImGuiKey_Space)) enableActiveCameraMovement = !enableActiveCameraMovement; if (enableActiveCameraMovement) @@ -1293,7 +1338,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); - ImGui::Text("Press 'Left Shift' to Enable/Disable selected camera movement"); + ImGui::Text("Press 'Space' to Enable/Disable selected camera movement"); ImGui::End(); @@ -1314,9 +1359,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::TreeNodeEx(treeNodeLabel.c_str(), flags)) { - // WiP, does not affect camera yet - //ImGui::SliderFloat("Move speed", &moveSpeed[cameraIndex], 0.1f, 10.f); - //ImGui::SliderFloat("Rotate speed", &rotateSpeed[cameraIndex], 0.1f, 10.f); + ImGui::SliderFloat("Move speed factor", &moveSpeed[cameraIndex], 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("Rotate speed factor", &rotateSpeed[cameraIndex], 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); if (ImGui::TreeNodeEx("Data", ImGuiTreeNodeFlags_None)) { @@ -1370,6 +1414,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::RadioButton("RH", !isLH[lastProjectionIx])) isLH[lastProjectionIx] = false; + if(useWindow) + ImGui::Checkbox("Allow axes to flip##allowAxesToFlip", areAxesFlipped.data() + lastProjectionIx); + if (isPerspective[lastProjectionIx]) ImGui::SliderFloat("Fov", &fov[lastProjectionIx], 20.f, 150.f, "%.1f"); else @@ -1382,7 +1429,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - inline void TransformEditor(float* matrix) + inline void TransformEditor(float* matrix, IGimbalController::input_imguizmo_event_t* deltaTRS = nullptr) { static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; @@ -1410,32 +1457,45 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) mCurrentGizmoOperation = ImGuizmo::ROTATE; ImGui::SameLine(); if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) mCurrentGizmoOperation = ImGuizmo::SCALE; - float matrixTranslation[3], matrixRotation[3], matrixScale[3]; - ImGuizmo::DecomposeMatrixToComponents(matrix, matrixTranslation, matrixRotation, matrixScale); - const bool isCameraModelBound = lastManipulatedModelIx == 1u; + + float32_t3 matrixTranslation, matrixRotation, matrixScale; + IGimbalController::input_imguizmo_event_t decomposed, recomposed; + + if (deltaTRS) + *deltaTRS = IGimbalController::input_imguizmo_event_t(1); + + ImGuizmo::DecomposeMatrixToComponents(matrix, &matrixTranslation[0], &matrixRotation[0], &matrixScale[0]); + decomposed = *reinterpret_cast(matrix); { ImGuiInputTextFlags flags = 0; - if (isCameraModelBound) // TODO: cameras are WiP here + ImGui::InputFloat3("Tr", &matrixTranslation[0], "%.3f", flags); + + if (isCameraModelBound) // TODO: cameras are WiP here, imguizmo controller only works with translate manipulation + abs are banned currently { ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(1.0f, 0.0f, 0.0f, 0.5f)); flags |= ImGuiInputTextFlags_ReadOnly; } - ImGui::InputFloat3("Tr", matrixTranslation, "%.3f", flags); - ImGui::InputFloat3("Rt", matrixRotation, "%.3f", flags); - ImGui::InputFloat3("Sc", matrixScale, "%.3f", flags); + ImGui::InputFloat3("Rt", &matrixRotation[0], "%.3f", flags); + ImGui::InputFloat3("Sc", &matrixScale[0], "%.3f", flags); if(isCameraModelBound) ImGui::PopStyleColor(); } - ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix); + ImGuizmo::RecomposeMatrixFromComponents(&matrixTranslation[0], &matrixRotation[0], &matrixScale[0], matrix); + recomposed = *reinterpret_cast(matrix); + + // TODO AND NOTE: I only take care of translate part temporary! + if(deltaTRS) + deltaTRS->operator[](3) = recomposed[3] - decomposed[3]; if (mCurrentGizmoOperation != ImGuizmo::SCALE) { @@ -1446,22 +1506,19 @@ class UISampleApp final : public examples::SimpleWindowedApplication mCurrentGizmoMode = ImGuizmo::WORLD; } - if (not isCameraModelBound) // TODO: cameras are WiP here + ImGui::Checkbox(" ", &useSnap); + ImGui::SameLine(); + switch (mCurrentGizmoOperation) { - ImGui::Checkbox(" ", &useSnap); - ImGui::SameLine(); - switch (mCurrentGizmoOperation) - { - case ImGuizmo::TRANSLATE: - ImGui::InputFloat3("Snap", &snap[0]); - break; - case ImGuizmo::ROTATE: - ImGui::InputFloat("Angle Snap", &snap[0]); - break; - case ImGuizmo::SCALE: - ImGui::InputFloat("Scale Snap", &snap[0]); - break; - } + case ImGuizmo::TRANSLATE: + ImGui::InputFloat3("Snap", &snap[0]); + break; + case ImGuizmo::ROTATE: + ImGui::InputFloat("Angle Snap", &snap[0]); + break; + case ImGuizmo::SCALE: + ImGui::InputFloat("Scale Snap", &snap[0]); + break; } ImGui::End(); @@ -1562,11 +1619,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication using linear_projection_t = CLinearProjection; nbl::core::smart_refctd_ptr projections; - bool flipGizmoY = true; - std::array isPerspective = { true, true, true }, isLH = { true, true, true }; + const bool flipGizmoY = true; + std::array isPerspective = { true, true, true }, isLH = { true, true, true }, areAxesFlipped = { false, false, false }; std::array fov = { 60.f, 60.f, 60.f }, zNear = { 0.1f, 0.1f, 0.1f }, zFar = { 10000.f, 10000.f, 10000.f }, viewWidth = { 10.f, 10.f, 10.f }, aspectRatio = {}, invAspectRatio = {}; - std::array moveSpeed = { 1.f, 1.f }, rotateSpeed = { 1.f, 1.f }; + std::array moveSpeed = { 0.01, 0.01 }, rotateSpeed = { 0.003, 0.003 }; + bool isCameraModelBound = false; float camYAngle = 165.f / 180.f * 3.14159f; float camXAngle = 32.f / 180.f * 3.14159f; float camDistance = 8.f; diff --git a/common/include/camera/CFPSCamera.hpp b/common/include/camera/CFPSCamera.hpp index 188a8a47f..8b5868566 100644 --- a/common/include/camera/CFPSCamera.hpp +++ b/common/include/camera/CFPSCamera.hpp @@ -31,7 +31,7 @@ class CFPSCamera final : public ICamera virtual bool manipulate(std::span virtualEvents, base_t::ManipulationMode mode) override { - constexpr double MoveSpeedScale = 0.01, RotateSpeedScale = 0.003, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; + constexpr double MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; if (!virtualEvents.size()) return false; @@ -46,8 +46,8 @@ class CFPSCamera final : public ICamera const auto impulse = mode == base_t::Local ? m_gimbal.accumulate(virtualEvents) : m_gimbal.accumulate(virtualEvents, { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }); - const auto newPitch = std::clamp(gPitch + impulse.dVirtualRotation.x * RotateSpeedScale, MinVerticalAngle, MaxVerticalAngle), newYaw = gYaw + impulse.dVirtualRotation.y * RotateSpeedScale; - newOrientation = glm::quat(glm::vec3(newPitch, newYaw, 0.0f)); newPosition = m_gimbal.getPosition() + impulse.dVirtualTranslate * MoveSpeedScale; + const auto newPitch = std::clamp(gPitch + impulse.dVirtualRotation.x * m_rotationSpeedScale, MinVerticalAngle, MaxVerticalAngle), newYaw = gYaw + impulse.dVirtualRotation.y * m_rotationSpeedScale; + newOrientation = glm::quat(glm::vec3(newPitch, newYaw, 0.0f)); newPosition = m_gimbal.getPosition() + impulse.dVirtualTranslate * m_moveSpeedScale; bool manipulated = true; @@ -81,9 +81,25 @@ class CFPSCamera final : public ICamera return "FPS Camera"; } + // (***) + inline void setMoveSpeedScale(double scalar) + { + m_moveSpeedScale = scalar; + } + + // (***) + inline void setRotationSpeedScale(double scalar) + { + m_rotationSpeedScale = scalar; + } + private: typename base_t::CGimbal m_gimbal; + // (***) TODO: I need to think whether a camera should own this or controllers should be able + // to set sensitivity to scale magnitudes of generated events we put into manipulate method + double m_moveSpeedScale = 1, m_rotationSpeedScale = 1; + static inline constexpr auto AllowedLocalVirtualEvents = CVirtualGimbalEvent::Translate | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; static inline constexpr auto AllowedWorldVirtualEvents = AllowedLocalVirtualEvents; diff --git a/common/include/camera/ICamera.hpp b/common/include/camera/ICamera.hpp index 1f81903e5..fa3d62770 100644 --- a/common/include/camera/ICamera.hpp +++ b/common/include/camera/ICamera.hpp @@ -83,7 +83,7 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted // Manipulates camera with virtual events, returns true if *any* manipulation happens, it may fail partially or fully because each camera type has certain constraints which determine how it actually works // TODO: this really needs to be moved to more abstract interface, eg. IObjectTransform or something and ICamera should inherit it (its also an object!) - virtual bool manipulate(std::span virtualEvents, ManipulationMode mode) = 0; + virtual bool manipulate(std::span virtualEvents, ManipulationMode mode) = 0; // VirtualEventType bitmask for a camera view gimbal manipulation requests filtering virtual const uint32_t getAllowedVirtualEvents(ManipulationMode mode) = 0u; diff --git a/common/include/camera/IGimbalController.hpp b/common/include/camera/IGimbalController.hpp index b3f3b5917..b6e2ec00a 100644 --- a/common/include/camera/IGimbalController.hpp +++ b/common/include/camera/IGimbalController.hpp @@ -101,7 +101,7 @@ class IGimbalController : public IGimbalManipulateEncoder using input_mouse_event_t = ui::SMouseEvent; //! input of ImGuizmo gimbal controller process utility - ImGuizmo manipulate utility produces "delta (TRS) matrix" events - using input_imguizmo_event_t = std::pair; + using input_imguizmo_event_t = float32_t4x4; void beginInputProcessing(const std::chrono::microseconds nextPresentationTimeStamp) { @@ -131,7 +131,29 @@ class IGimbalController : public IGimbalManipulateEncoder std::span imguizmoEvents = {}; }; - // Processes SUpdateParameters events to generate virtual manipulation events + /** + * @brief Processes combined events from SUpdateParameters to generate virtual manipulation events. + * + * @note This function combines the processing of events from keyboards, mouse and ImGuizmo. + * It delegates the actual processing to the respective functions: + * - @ref processKeyboard for keyboard events + * - @ref processMouse for mouse events + * - @ref processImguizmo for ImGuizmo events + * The results are accumulated into the output array and the total count. + * + * @param "output" is a pointer to the array where all generated gimbal events will be stored. + * If nullptr, the function will only calculate the total count of potential + * output events without processing. + * + * @param "count" is a uint32_t reference to store the total count of generated gimbal events. + * + * @param "parameters" is an SUpdateParameters structure containing the individual event arrays + * for keyboard, mouse, and ImGuizmo inputs. + * + * @return void. If "count" > 0 and "output" is a valid pointer, use it to dereference your "output" + * containing "count" events. If "output" is nullptr, "count" tells you the total size of "output" + * you must guarantee to be valid. + */ void process(gimbal_event_t* output, uint32_t& count, const SUpdateParameters parameters = {}) { count = 0u; @@ -158,7 +180,25 @@ class IGimbalController : public IGimbalManipulateEncoder inline const imguizmo_to_virtual_events_t& getImguizmoVirtualEventMap() { return m_imguizmoVirtualEventMap; } private: - // Processes keyboard events to generate virtual manipulation events + /** + * @brief Processes keyboard events to generate virtual manipulation events. + * + * @note This function maps keyboard events into virtual gimbal manipulation events + * based on predefined mappings. It supports event types such as key press and key release + * to trigger corresponding actions. + * + * @param "output" is a pointer to the array where generated gimbal events will be stored. + * If nullptr, the function will only calculate the count of potential + * output events without processing. + * + * @param "count" is a uint32_t reference to store the count of generated gimbal events. + * + * @param "events" is a span of input_keyboard_event_t. Each such event contains a key code and action, + * such as key press or release. + * + * @return void. If "count" > 0 and "output" is a valid pointer, use it to dereference your "output" + * containing "count" events. If "output" is nullptr, "count" tells you the size of "output" you must guarantee to be valid. + */ void processKeyboard(gimbal_event_t* output, uint32_t& count, std::span events) { count = 0u; @@ -201,7 +241,25 @@ class IGimbalController : public IGimbalManipulateEncoder } } - // Processes mouse events to generate virtual manipulation events + /** + * @brief Processes mouse events to generate virtual manipulation events. + * + * @note This function processes mouse input, including clicks, scrolls, and movements, + * and maps them into virtual gimbal manipulation events. Mouse actions are processed + * using predefined mappings to determine corresponding gimbal manipulations. + * + * @param "output" is a pointer to the array where generated gimbal events will be stored. + * If nullptr, the function will only calculate the count of potential + * output events without processing. + * + * @param "count" is a uint32_t reference to store the count of generated gimbal events. + * + * @param "events" is a span of input_mouse_event_t. Each such event represents a mouse action, + * including clicks, scrolls, or movements. + * + * @return void. If "count" > 0 and "output" is a valid pointer, use it to dereference your "output" + * containing "count" events. If "output" is nullptr, "count" tells you the size of "output" you must guarantee to be valid. + */ void processMouse(gimbal_event_t* output, uint32_t& count, std::span events) { count = 0u; @@ -277,6 +335,26 @@ class IGimbalController : public IGimbalManipulateEncoder } } + /** + * @brief Processes input events from ImGuizmo and generates virtual gimbal events. + * + * @note This function is intended to process transformations provided by ImGuizmo and convert + * them into virtual gimbal events for the ICamera::World mode (ICamera::Local is invalid!). + * The function computes translation, rotation, and scale deltas from ImGuizmo's delta matrix, + * which are then mapped to corresponding virtual events using a predefined mapping. + * + * @param "output" is pointer to the array where generated gimbal events will be stored. + * If nullptr, the function will only calculate the count of potential + * output events without processing. + * + * @param "count" is uint32_t reference to store the count of generated gimbal events. + * + * @param "events" is a span of input_imguizmo_event_t. Each such event contains a delta + * transformation matrix that represents changes in world space. + * + * @return void. If "count" > 0 & "output" was valid pointer then use it to dereference your "output" containing "count" events. + * If "output" is nullptr then "count" tells you about size of "output" you must guarantee to be valid. + */ void processImguizmo(gimbal_event_t* output, uint32_t& count, std::span events) { count = 0u; @@ -294,51 +372,32 @@ class IGimbalController : public IGimbalManipulateEncoder for (const auto& ev : events) { - // TODO: debug assert "orientationBasis" is orthonormal - const auto& [deltaWorldTRS, orientationBasis] = ev; + const auto& deltaWorldTRS = ev; struct { float32_t3 dTranslation, dRotation, dScale; - } world, local; // its important to notice our imguizmo deltas are written in world base, so I will assume you generate events with respect for input local basis + } world; - // TODO: since I assume the delta matrix is from imguizmo I must follow its API (but this one I could actually steal & rewrite to Nabla to not call imguizmo stuff here <- its TEMP) + // TODO: since I assume the delta matrix is from imguizmo I must follow its API (but this one I could actually steal & rewrite to Nabla to not call imguizmo stuff here <- its TEMP!!!!!!!) + // - there are numerical issues with imguizmo decompose/recompose TRS (and the author also says it, we should have this util in Nabla and work with doubles not floats) + // - in rotate mode delta TRS matrix contains translate part (how? no idea, maybe imguizmo bug) and it glitches everything, I get translations e-07 or something ImGuizmo::DecomposeMatrixToComponents(&deltaWorldTRS[0][0], &world.dTranslation[0], &world.dRotation[0], &world.dScale[0]); - // FP precision threshold, lets filter small artifacts for this event generator to be more accurate - constexpr float threshold = 1e-8f; - - auto filterDelta = [](const float32_t3& in) -> float32_t3 - { - return - { - (std::abs(in[0]) > threshold) ? in[0] : 0.0f, - (std::abs(in[1]) > threshold) ? in[1] : 0.0f, - (std::abs(in[2]) > threshold) ? in[2] : 0.0f - }; - }; - - local.dTranslation = filterDelta(mul(orientationBasis, world.dTranslation)); - - // TODO - local.dRotation = { 0.f, 0.f, 0.f }; - // TODO - local.dScale = { 1.f, 1.f, 1.f }; - // Delta translation impulse - requestMagnitudeUpdateWithScalar(0.f, local.dTranslation[0], std::abs(local.dTranslation[0]), gimbal_event_t::MoveRight, gimbal_event_t::MoveLeft, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(0.f, local.dTranslation[1], std::abs(local.dTranslation[1]), gimbal_event_t::MoveUp, gimbal_event_t::MoveDown, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(0.f, local.dTranslation[2], std::abs(local.dTranslation[2]), gimbal_event_t::MoveForward, gimbal_event_t::MoveBackward, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, world.dTranslation[0], std::abs(world.dTranslation[0]), gimbal_event_t::MoveRight, gimbal_event_t::MoveLeft, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, world.dTranslation[1], std::abs(world.dTranslation[1]), gimbal_event_t::MoveUp, gimbal_event_t::MoveDown, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, world.dTranslation[2], std::abs(world.dTranslation[2]), gimbal_event_t::MoveForward, gimbal_event_t::MoveBackward, m_imguizmoVirtualEventMap); // Delta rotation impulse - requestMagnitudeUpdateWithScalar(0.f, local.dRotation[0], std::abs(local.dRotation[0]), gimbal_event_t::PanRight, gimbal_event_t::PanLeft, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(0.f, local.dRotation[1], std::abs(local.dRotation[1]), gimbal_event_t::TiltUp, gimbal_event_t::TiltDown, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(0.f, local.dRotation[2], std::abs(local.dRotation[2]), gimbal_event_t::RollRight, gimbal_event_t::RollLeft, m_imguizmoVirtualEventMap); + //requestMagnitudeUpdateWithScalar(0.f, world.dRotation[0], std::abs(world.dRotation[0]), gimbal_event_t::TiltUp , gimbal_event_t::TiltDown, m_imguizmoVirtualEventMap); + //requestMagnitudeUpdateWithScalar(0.f, world.dRotation[1], std::abs(world.dRotation[1]), gimbal_event_t::PanRight, gimbal_event_t::PanLeft, m_imguizmoVirtualEventMap); + //requestMagnitudeUpdateWithScalar(0.f, world.dRotation[2], std::abs(world.dRotation[2]), gimbal_event_t::RollRight, gimbal_event_t::RollLeft, m_imguizmoVirtualEventMap); // Delta scale impulse - requestMagnitudeUpdateWithScalar(1.f, local.dScale[0], std::abs(local.dScale[0]), gimbal_event_t::ScaleXInc, gimbal_event_t::ScaleXDec, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(1.f, local.dScale[1], std::abs(local.dScale[1]), gimbal_event_t::ScaleYInc, gimbal_event_t::ScaleYDec, m_imguizmoVirtualEventMap); - requestMagnitudeUpdateWithScalar(1.f, local.dScale[2], std::abs(local.dScale[2]), gimbal_event_t::ScaleZInc, gimbal_event_t::ScaleZDec, m_imguizmoVirtualEventMap); + //requestMagnitudeUpdateWithScalar(1.f, world.dScale[0], std::abs(world.dScale[0]), gimbal_event_t::ScaleXInc, gimbal_event_t::ScaleXDec, m_imguizmoVirtualEventMap); + //requestMagnitudeUpdateWithScalar(1.f, world.dScale[1], std::abs(world.dScale[1]), gimbal_event_t::ScaleYInc, gimbal_event_t::ScaleYDec, m_imguizmoVirtualEventMap); + //requestMagnitudeUpdateWithScalar(1.f, world.dScale[2], std::abs(world.dScale[2]), gimbal_event_t::ScaleZInc, gimbal_event_t::ScaleZDec, m_imguizmoVirtualEventMap); } postprocess(m_imguizmoVirtualEventMap, output, count); diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index 7fa94e517..357adefe9 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -38,7 +38,7 @@ class IProjection * * @param "vecToProjectionSpace" is a vector to transform from its space into projection space. * @param "output" is a vector which is "vecToProjectionSpace" transformed into projection space. - * @return The vector in projection space. + * @return void. "output" is the vector in projection space. */ virtual void project(const projection_vector_t& vecToProjectionSpace, projection_vector_t& output) const = 0; @@ -48,7 +48,7 @@ class IProjection * * @param "vecFromProjectionSpace" is a vector in the projection space to transform back to original space. * @param "output" is a vector which is "vecFromProjectionSpace" transformed back to its original space. - * @return The vector in the original space. + * @return true if inverse succeeded and then "output" is the vector in the original space. False otherwise. */ virtual bool unproject(const projection_vector_t& vecFromProjectionSpace, projection_vector_t& output) const = 0; From c615d58bebb71e5ddb27f48f8a09425a7ba0b33d Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 14 Dec 2024 16:03:03 +0100 Subject: [PATCH 60/84] allow to pick combo with object type when TRS editor has object from geometry creator bound (instead of mouse scroll), fix issues with capturing events when window has no focus (no queues & discard old ones) --- 61_UI/main.cpp | 57 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index d5dc40d66..10e7d1bc4 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -785,6 +785,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication }; m_surface->present(std::move(swapchainLock), presentInfo); } + firstFrame = false; } inline bool keepRunning() override @@ -821,25 +822,21 @@ class UISampleApp final : public examples::SimpleWindowedApplication std::vector mouse {}; std::vector keyboard {}; } capturedEvents; - - mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void { - for (const auto& e : events) + mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void { - capturedEvents.mouse.emplace_back(e); - - if (e.type == nbl::ui::SMouseEvent::EET_SCROLL) - gcIndex = std::clamp(int16_t(gcIndex) + int16_t(core::sign(e.scrollEvent.verticalScroll)), int64_t(0), int64_t(OT_COUNT - (uint8_t)1u)); - } - }, m_logger.get()); + if (m_window->hasInputFocus()) + for (const auto& e : events) + capturedEvents.mouse.emplace_back(e); + }, m_logger.get()); - keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void - { - for (const auto& e : events) + keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void { - capturedEvents.keyboard.emplace_back(e); - } - }, m_logger.get()); + if (m_window->hasInputFocus()) + for (const auto& e : events) + capturedEvents.keyboard.emplace_back(e); + }, m_logger.get()); + } const auto cursorPosition = m_window->getCursorControl()->getPosition(); @@ -1453,6 +1450,36 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Text("Identifier: \"%s\"", lastManipulatedModelIdentifier.c_str()); ImGui::Separator(); + + if (!isCameraModelBound) + { + static const char* gcObjectTypeNames[] = { + "Cube", + "Sphere", + "Cylinder", + "Rectangle", + "Disk", + "Arrow", + "Cone", + "Icosphere" + }; + + if (ImGui::BeginCombo("Object Type", gcObjectTypeNames[gcIndex])) + { + for (uint8_t i = 0; i < ObjectType::OT_COUNT; ++i) + { + bool isSelected = (static_cast(gcIndex) == static_cast(i)); + if (ImGui::Selectable(gcObjectTypeNames[i], isSelected)) + gcIndex = i; + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + + addMatrixTable("Model (TRS) Matrix", "ModelMatrixTable", 4, 4, matrix); if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) From 10fe3b297287b576b6689b959f46f57362410ab9 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 14 Dec 2024 17:03:41 +0100 Subject: [PATCH 61/84] forgot to remove old struct from ICamera interface, also use ImGuiSliderFlags_Logarithmic for some sliders --- 61_UI/main.cpp | 9 ++++----- common/include/camera/ICamera.hpp | 5 ----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 10e7d1bc4..acc42106e 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1386,7 +1386,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); } - } // Projections @@ -1415,12 +1414,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Checkbox("Allow axes to flip##allowAxesToFlip", areAxesFlipped.data() + lastProjectionIx); if (isPerspective[lastProjectionIx]) - ImGui::SliderFloat("Fov", &fov[lastProjectionIx], 20.f, 150.f, "%.1f"); + ImGui::SliderFloat("Fov", &fov[lastProjectionIx], 20.f, 150.f, "%.1f", ImGuiSliderFlags_Logarithmic); else - ImGui::SliderFloat("Ortho width", &viewWidth[lastProjectionIx], 1.f, 20.f, "%.1f"); + ImGui::SliderFloat("Ortho width", &viewWidth[lastProjectionIx], 1.f, 20.f, "%.1f", ImGuiSliderFlags_Logarithmic); - ImGui::SliderFloat("zNear", &zNear[lastProjectionIx], 0.1f, 100.f, "%.2f"); - ImGui::SliderFloat("zFar", &zFar[lastProjectionIx], 110.f, 10000.f, "%.1f"); + ImGui::SliderFloat("zNear", &zNear[lastProjectionIx], 0.1f, 100.f, "%.2f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("zFar", &zFar[lastProjectionIx], 110.f, 10000.f, "%.1f", ImGuiSliderFlags_Logarithmic); ImGui::End(); } diff --git a/common/include/camera/ICamera.hpp b/common/include/camera/ICamera.hpp index fa3d62770..33890fef1 100644 --- a/common/include/camera/ICamera.hpp +++ b/common/include/camera/ICamera.hpp @@ -35,11 +35,6 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted CGimbal(typename base_t::SCreationParameters&& parameters) : base_t(std::move(parameters)) { updateView(); } ~CGimbal() = default; - struct SView - { - matrix matrix = {}; - }; - inline void updateView() { const auto& gRight = base_t::getXAxis(), gUp = base_t::getYAxis(), gForward = base_t::getZAxis(); From 3ea910c6b56110eee49547328396c9edcc0fda7e Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Tue, 17 Dec 2024 20:51:08 +0100 Subject: [PATCH 62/84] create `CPlanarProjection.hpp` & `IPlanarProjection.hpp` classes, add 61_UI/cameras.json. Make a few changes to interface classes, load demo data from the json file. TODO for tomorrow: use loaded planar projections vector to interact with the application. --- 61_UI/CMakeLists.txt | 5 +- 61_UI/cameras.json | 105 +++++++ 61_UI/config.json.template | 28 -- 61_UI/include/common.hpp | 1 + 61_UI/main.cpp | 297 +++++++++++++++++++- 61_UI/pipeline.groovy | 50 ---- common/include/camera/CFPSCamera.hpp | 6 +- common/include/camera/CLinearProjection.hpp | 53 ++-- common/include/camera/CPlanarProjection.hpp | 42 +++ common/include/camera/IGimbal.hpp | 27 ++ common/include/camera/IGimbalController.hpp | 36 ++- common/include/camera/ILinearProjection.hpp | 43 +-- common/include/camera/IPlanarProjection.hpp | 116 ++++++++ common/include/camera/IProjection.hpp | 11 +- 14 files changed, 668 insertions(+), 152 deletions(-) create mode 100644 61_UI/cameras.json delete mode 100644 61_UI/config.json.template delete mode 100644 61_UI/pipeline.groovy create mode 100644 common/include/camera/CPlanarProjection.hpp create mode 100644 common/include/camera/IPlanarProjection.hpp diff --git a/61_UI/CMakeLists.txt b/61_UI/CMakeLists.txt index a34e46ce6..1930cb17f 100644 --- a/61_UI/CMakeLists.txt +++ b/61_UI/CMakeLists.txt @@ -15,4 +15,7 @@ if(NBL_BUILD_IMGUI) nbl_create_executable_project("${NBL_EXTRA_SOURCES}" "" "${NBL_INCLUDE_SERACH_DIRECTORIES}" "${NBL_LIBRARIES}" "${NBL_EXECUTABLE_PROJECT_CREATION_PCH_TARGET}") LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} geometryCreatorSpirvBRD) -endif() \ No newline at end of file +endif() + +add_dependencies(${EXECUTABLE_NAME} argparse) +target_include_directories(${EXECUTABLE_NAME} PUBLIC $) \ No newline at end of file diff --git a/61_UI/cameras.json b/61_UI/cameras.json new file mode 100644 index 000000000..bd4d7aaa9 --- /dev/null +++ b/61_UI/cameras.json @@ -0,0 +1,105 @@ +{ + "cameras": [ + { + "type": "FPS", + "position": [-2.238, 1.438, -1.558], + "orientation": [0.253, 0.368, -0.105, 0.888] + }, + { + "type": "FPS", + "position": [-2.017, 0.386, 0.684], + "orientation": [0.047, 0.830, -0.072, 0.55] + } + ], + "projections": [ + { + "type": "perspective", + "fov": 60.0, + "zNear": 0.1, + "zFar": 100.0, + "leftHanded": true + }, + { + "type": "orthographic", + "orthoWidth": 10.0, + "zNear": 0.1, + "zFar": 100.0, + "leftHanded": true + } + ], + "viewports": [ + { + "camera": 0, + "planarControllerSet": [ + { + "projection": 0, + "controllers": { + "keyboard": 0, + "mouse": 0 + } + }, + { + "projection": 1, + "controllers": { + "keyboard": 1, + "mouse": 0 + } + } + ] + }, + { + "camera": 1, + "planarControllerSet": [ + { + "projection": 0, + "controllers": { + "keyboard": 0, + "mouse": 0 + } + }, + { + "projection": 1, + "controllers": { + "keyboard": 1, + "mouse": 0 + } + } + ] + } + ], + "controllers": { + "keyboard": [ + { + "mappings": { + "W": "MoveForward", + "S": "MoveBackward", + "A": "MoveLeft", + "D": "MoveRight", + "I": "TiltDown", + "K": "TiltUp", + "J": "PanLeft", + "L": "PanRight" + } + }, + { + "mappings": { + "W": "MoveUp", + "S": "MoveDown", + "A": "MoveLeft", + "D": "MoveRight" + } + } + ], + "mouse": [ + { + "mappings": { + "RELATIVE_POSITIVE_MOVEMENT_X": "PanRight", + "RELATIVE_NEGATIVE_MOVEMENT_X": "PanLeft", + "RELATIVE_POSITIVE_MOVEMENT_Y": "TiltUp", + "RELATIVE_NEGATIVE_MOVEMENT_Y": "TiltDown" + } + } + ] + } + } + \ No newline at end of file diff --git a/61_UI/config.json.template b/61_UI/config.json.template deleted file mode 100644 index f961745c1..000000000 --- a/61_UI/config.json.template +++ /dev/null @@ -1,28 +0,0 @@ -{ - "enableParallelBuild": true, - "threadsPerBuildProcess" : 2, - "isExecuted": false, - "scriptPath": "", - "cmake": { - "configurations": [ "Release", "Debug", "RelWithDebInfo" ], - "buildModes": [], - "requiredOptions": [] - }, - "profiles": [ - { - "backend": "vulkan", - "platform": "windows", - "buildModes": [], - "runConfiguration": "Release", - "gpuArchitectures": [] - } - ], - "dependencies": [], - "data": [ - { - "dependencies": [], - "command": [""], - "outputs": [] - } - ] -} \ No newline at end of file diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index 0c33a5813..4033afc14 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -12,6 +12,7 @@ #include "camera/CCubeProjection.hpp" #include "camera/CLinearProjection.hpp" +#include "camera/CPlanarProjection.hpp" // the example's headers #include "nbl/ui/ICursorControl.h" diff --git a/61_UI/main.cpp b/61_UI/main.cpp index acc42106e..57bc61b78 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -2,11 +2,21 @@ // This file is part of the "Nabla Engine". // For conditions of distribution and use, see copyright notice in nabla.h +#include "nlohmann/json.hpp" +#include "argparse/argparse.hpp" +using json = nlohmann::json; + #include "common.hpp" #include "keysmapping.hpp" #include "camera/CCubeProjection.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING +//! TODO: this could be engine class actually, temporary keepin it in the example though +class CPlanarProjectionCameraController +{ + +}; + constexpr IGPUImage::SSubresourceRange TripleBufferUsedSubresourceRange = { .aspectMask = IGPUImage::EAF_COLOR_BIT, @@ -239,6 +249,22 @@ class UISampleApp final : public examples::SimpleWindowedApplication inline bool onAppInitialized(smart_refctd_ptr&& system) override { + argparse::ArgumentParser program("Virtual camera event system demo"); + + program.add_argument("--file") + .required() + .help("Path to json file with camera inputs"); + + try + { + program.parse_args({ argv.data(), argv.data() + argv.size() }); + } + catch (const std::exception& err) + { + std::cerr << err.what() << std::endl << program; + return false; + } + // Create imput system m_inputSystem = make_smart_refctd_ptr(logger_opt_smart_ptr(smart_refctd_ptr(m_logger))); @@ -485,9 +511,261 @@ class UISampleApp final : public examples::SimpleWindowedApplication oracle.reportBeginFrameRecord(); - /* - TESTS, TODO: remove all once finished work & integrate with the example properly - */ + // json file with camera inputs + // @Yas: TODO: THIS NEEDS BETTER VALIDATION AND LOGS ON FAILURE (+ status logs would be nice)!!! + { + const auto cameraJsonFile = program.get("--file"); + + std::ifstream file(cameraJsonFile.c_str()); + if (!file.is_open()) + { + std::cerr << "Error: Cannot open input \"" << cameraJsonFile.c_str() << "\" json file."; + return false; + } + + json j; + file >> j; + + std::vector> cameras; + for (const auto& jCamera : j["cameras"]) + { + if (jCamera.contains("type")) + { + if (jCamera["type"] == "FPS") + { + if (!jCamera.contains("position")) + { + std::cerr << "Expected \"position\" keyword for camera definition!"; + return false; + } + + if (!jCamera.contains("orientation")) + { + std::cerr << "Expected \"orientation\" keyword for camera definition!"; + return false; + } + + auto position = [&]() + { + auto jret = jCamera["position"].get>(); + return float32_t3(jret[0], jret[1], jret[2]); + }(); + + auto orientation = [&]() + { + auto jret = jCamera["orientation"].get>(); + return glm::quat(jret[0], jret[1], jret[2], jret[3]); + }(); + + cameras.emplace_back() = make_smart_refctd_ptr(position, orientation); + } + else + { + std::cerr << "Unsupported camera type!"; + return false; + } + } + else + { + std::cerr << "Expected \"type\" keyword for camera definition!"; + return false; + } + } + + std::vector projections; + for (const auto& jProjection : j["projections"]) + { + if (jProjection.contains("type")) + { + float zNear, zFar; + bool leftHanded; + + if (!jProjection.contains("zNear")) + { + "Expected \"zNear\" keyword for planar projection definition!"; + return false; + } + + if (!jProjection.contains("zFar")) + { + "Expected \"zFar\" keyword for planar projection definition!"; + return false; + } + + if (!jProjection.contains("leftHanded")) + { + "Expected \"leftHanded\" keyword for planar projection definition!"; + return false; + } + + zNear = jProjection["zNear"].get(); + zFar = jProjection["zFar"].get(); + leftHanded = jProjection["leftHanded"].get(); + + if (jProjection["type"] == "perspective") + { + if (!jProjection.contains("fov")) + { + "Expected \"fov\" keyword for planar perspective projection definition!"; + return false; + } + + float fov = jProjection["fov"].get(); + projections.emplace_back(IPlanarProjection::CProjection::create(leftHanded, zNear, zFar, fov)); + + } + else if (jProjection["type"] == "orthographic") + { + if (!jProjection.contains("orthoWidth")) + { + "Expected \"orthoWidth\" keyword for planar orthographic projection definition!"; + return false; + } + + float orthoWidth = jProjection["orthoWidth"].get(); + projections.emplace_back(IPlanarProjection::CProjection::create(leftHanded, zNear, zFar, orthoWidth)); + } + else + { + std::cerr << "Unsupported projection!"; + return false; + } + } + } + + struct + { + std::vector keyboard; + std::vector mouse; + } controllers; + + if (j.contains("controllers")) + { + const auto& jControllers = j["controllers"]; + + if (jControllers.contains("keyboard")) + { + for (const auto& jKeyboard : jControllers["keyboard"]) + { + if (jKeyboard.contains("mappings")) + { + auto& controller = controllers.keyboard.emplace_back(); + for (const auto& [key, value] : jKeyboard["mappings"].items()) + { + const auto nativeCode = stringToKeyCode(key.c_str()); + + if (nativeCode == EKC_NONE) + { + std::cerr << "Invalid native key \"" << key.c_str() << "\" code mapping for keyboard controller!" << std::endl; + return false; + } + + controller[nativeCode] = CVirtualGimbalEvent::stringToVirtualEvent(value.get()); + } + } + else + { + std::cerr << "Expected \"mappings\" keyword for keyboard controller definition!" << std::endl; + return false; + } + } + } + else + { + std::cerr << "Expected \"keyboard\" keyword in controllers definition!" << std::endl; + return false; + } + + if (jControllers.contains("mouse")) + { + for (const auto& jMouse : jControllers["mouse"]) + { + if (jMouse.contains("mappings")) + { + auto& controller = controllers.mouse.emplace_back(); + for (const auto& [key, value] : jMouse["mappings"].items()) + { + const auto nativeCode = stringToMouseCode(key.c_str()); + + if (nativeCode == EMC_NONE) + { + std::cerr << "Invalid native key \"" << key.c_str() << "\" code mapping for mouse controller!" << std::endl; + return false; + } + + controller[nativeCode] = CVirtualGimbalEvent::stringToVirtualEvent(value.get()); + } + } + else + { + std::cerr << "Expected \"mappings\" keyword for mouse controller definition!" << std::endl; + return false; + } + } + } + else + { + std::cerr << "Expected \"mouse\" keyword in controllers definition!" << std::endl; + return false; + } + } + else + { + std::cerr << "Expected \"controllers\" keyword in JSON!" << std::endl; + return false; + } + + + // viewpoets here + if (j.contains("viewports")) + { + for (const auto& jViewport : j["viewports"]) + { + if (!jViewport.contains("camera")) + { + std::cerr << "Expected \"camera\" keyword in viewport definition!" << std::endl; + return false; + } + + const auto cameraIx = jViewport["camera"].get(); + auto& planars = m_planarProjections.emplace_back() = planar_projection_t::create(smart_refctd_ptr(cameras[cameraIx])); + + if (!jViewport.contains("planarControllerSet")) + { + std::cerr << "Expected \"planarControllerSet\" keyword in viewport definition!" << std::endl; + return false; + } + + for (const auto& jPlanarController : jViewport["planarControllerSet"]) + { + if (!jPlanarController.contains("projection")) + { + std::cerr << "Expected \"projection\" keyword in planarControllerSet!" << std::endl; + return false; + } + + if (!jPlanarController.contains("controllers")) + { + std::cerr << "Expected \"controllers\" keyword in planarControllerSet!" << std::endl; + return false; + } + + auto projectionIx = jPlanarController["projection"].get(); + auto keyboardControllerIx = jPlanarController["controllers"]["keyboard"].get(); + auto mouseControllerIx = jPlanarController["controllers"]["mouse"].get(); + + auto& projection = planars->getPlanarProjections().emplace_back(projections[projectionIx]); + projection.updateKeyboardMapping([&](auto& map) { map = controllers.keyboard[keyboardControllerIx]; }); + projection.updateMouseMapping([&](auto& map) { map = controllers.mouse[mouseControllerIx]; }); + } + } + } + else + { + std::cerr << "Expected \"viewports\" keyword in JSON!" << std::endl; + return false; + } + } const auto iAspectRatio = float(m_window->getWidth()) / float(m_window->getHeight()); const auto iInvAspectRatio = float(m_window->getHeight()) / float(m_window->getWidth()); @@ -880,7 +1158,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto projectionMatrices = projections->getLinearProjections(); { - auto mutableRange = smart_refctd_ptr_static_cast(projections)->getLinearProjections(); + + /* + TODO: update it + + auto mutableRange = smart_refctd_ptr_static_cast(projections)->getLinearProjections(); for (uint32_t i = 0u; i < mutableRange.size(); ++i) { auto projection = mutableRange.begin() + i; @@ -902,6 +1184,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication projection->setProjectionMatrix(buildProjectionMatrixOrthoRH(viewWidth[i], viewHeight, zNear[i], zFar[i])); } } + */ + + } /* @@ -1662,6 +1947,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication std::string lastManipulatedModelIdentifier = "Geometry Creator Object"; bool firstFrame = true; + + using planar_projections_range_t = std::vector; + using planar_projection_t = CPlanarProjection; + std::vector> m_planarProjections; }; NBL_MAIN_FUNC(UISampleApp) \ No newline at end of file diff --git a/61_UI/pipeline.groovy b/61_UI/pipeline.groovy deleted file mode 100644 index 7b7c9702a..000000000 --- a/61_UI/pipeline.groovy +++ /dev/null @@ -1,50 +0,0 @@ -import org.DevshGraphicsProgramming.Agent -import org.DevshGraphicsProgramming.BuilderInfo -import org.DevshGraphicsProgramming.IBuilder - -class CUIBuilder extends IBuilder -{ - public CUIBuilder(Agent _agent, _info) - { - super(_agent, _info) - } - - @Override - public boolean prepare(Map axisMapping) - { - return true - } - - @Override - public boolean build(Map axisMapping) - { - IBuilder.CONFIGURATION config = axisMapping.get("CONFIGURATION") - IBuilder.BUILD_TYPE buildType = axisMapping.get("BUILD_TYPE") - - def nameOfBuildDirectory = getNameOfBuildDirectory(buildType) - def nameOfConfig = getNameOfConfig(config) - - agent.execute("cmake --build ${info.rootProjectPath}/${nameOfBuildDirectory}/${info.targetProjectPathRelativeToRoot} --target ${info.targetBaseName} --config ${nameOfConfig} -j12 -v") - - return true - } - - @Override - public boolean test(Map axisMapping) - { - return true - } - - @Override - public boolean install(Map axisMapping) - { - return true - } -} - -def create(Agent _agent, _info) -{ - return new CUIBuilder(_agent, _info) -} - -return this \ No newline at end of file diff --git a/common/include/camera/CFPSCamera.hpp b/common/include/camera/CFPSCamera.hpp index 8b5868566..a6d169f09 100644 --- a/common/include/camera/CFPSCamera.hpp +++ b/common/include/camera/CFPSCamera.hpp @@ -20,9 +20,9 @@ class CFPSCamera final : public ICamera : base_t(), m_gimbal({ .position = position, .orientation = orientation }) {} ~CFPSCamera() = default; - const base_t::keyboard_to_virtual_events_t& getKeyboardMappingPreset() const override { return m_keyboard_to_virtual_events_preset; } - const base_t::mouse_to_virtual_events_t& getMouseMappingPreset() const override { return m_mouse_to_virtual_events_preset; } - const base_t::imguizmo_to_virtual_events_t& getImguizmoMappingPreset() const override { return m_imguizmo_to_virtual_events_preset; } + const base_t::keyboard_to_virtual_events_t getKeyboardMappingPreset() const override { return m_keyboard_to_virtual_events_preset; } + const base_t::mouse_to_virtual_events_t getMouseMappingPreset() const override { return m_mouse_to_virtual_events_preset; } + const base_t::imguizmo_to_virtual_events_t getImguizmoMappingPreset() const override { return m_imguizmo_to_virtual_events_preset; } const typename base_t::CGimbal& getGimbal() override { diff --git a/common/include/camera/CLinearProjection.hpp b/common/include/camera/CLinearProjection.hpp index 77725125b..791e9cb1b 100644 --- a/common/include/camera/CLinearProjection.hpp +++ b/common/include/camera/CLinearProjection.hpp @@ -6,40 +6,39 @@ namespace nbl::hlsl { + template ProjectionsRange> + class CLinearProjection : public ILinearProjection + { + public: + using ILinearProjection::ILinearProjection; -template ProjectionsRange> -class CLinearProjection : public ILinearProjection -{ -public: - using ILinearProjection::ILinearProjection; - - CLinearProjection() = default; + CLinearProjection() = default; - inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) - { - if (!camera) - return nullptr; + inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) + { + if (!camera) + return nullptr; - return core::smart_refctd_ptr(new CLinearProjection(core::smart_refctd_ptr(camera)), core::dont_grab); - } + return core::smart_refctd_ptr(new CLinearProjection(core::smart_refctd_ptr(camera)), core::dont_grab); + } - virtual std::span getLinearProjections() const override - { - return std::span(m_projections.data(), m_projections.size()); - } + virtual std::span getLinearProjections() const override + { + return std::span(m_projections.data(), m_projections.size()); + } - inline std::span getLinearProjections() - { - return std::span(m_projections.data(), m_projections.size()); - } + inline std::span getLinearProjections() + { + return std::span(m_projections.data(), m_projections.size()); + } -private: - CLinearProjection(core::smart_refctd_ptr&& camera) - : ILinearProjection(core::smart_refctd_ptr(camera)) {} - virtual ~CLinearProjection() = default; + private: + CLinearProjection(core::smart_refctd_ptr&& camera) + : ILinearProjection(core::smart_refctd_ptr(camera)) {} + virtual ~CLinearProjection() = default; - ProjectionsRange m_projections; -}; + ProjectionsRange m_projections; + }; } // nbl::hlsl namespace diff --git a/common/include/camera/CPlanarProjection.hpp b/common/include/camera/CPlanarProjection.hpp new file mode 100644 index 000000000..f726ea959 --- /dev/null +++ b/common/include/camera/CPlanarProjection.hpp @@ -0,0 +1,42 @@ +#ifndef _NBL_C_PLANAR_PROJECTION_HPP_ +#define _NBL_C_PLANAR_PROJECTION_HPP_ + +#include "IPlanarProjection.hpp" +#include "IRange.hpp" + +namespace nbl::hlsl +{ + template ProjectionsRange> + class CPlanarProjection : public IPlanarProjection + { + public: + virtual ~CPlanarProjection() = default; + + inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) + { + if (!camera) + return nullptr; + + return core::smart_refctd_ptr(new CPlanarProjection(core::smart_refctd_ptr(camera)), core::dont_grab); + } + + virtual std::span getLinearProjections() const override + { + return { reinterpret_cast(m_projections.data()), m_projections.size() }; + } + + inline ProjectionsRange& getPlanarProjections() + { + return m_projections; + } + + private: + CPlanarProjection(core::smart_refctd_ptr&& camera) + : IPlanarProjection(core::smart_refctd_ptr(camera)) {} + + ProjectionsRange m_projections; + }; + +} // nbl::hlsl namespace + +#endif // _NBL_C_PLANAR_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index 3fe98d3eb..1a94affc5 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -79,6 +79,33 @@ namespace nbl::hlsl } } + static constexpr VirtualEventType stringToVirtualEvent(std::string_view event) + { + if (event == "MoveForward") return MoveForward; + if (event == "MoveBackward") return MoveBackward; + if (event == "MoveLeft") return MoveLeft; + if (event == "MoveRight") return MoveRight; + if (event == "MoveUp") return MoveUp; + if (event == "MoveDown") return MoveDown; + if (event == "TiltUp") return TiltUp; + if (event == "TiltDown") return TiltDown; + if (event == "PanLeft") return PanLeft; + if (event == "PanRight") return PanRight; + if (event == "RollLeft") return RollLeft; + if (event == "RollRight") return RollRight; + if (event == "ScaleXInc") return ScaleXInc; + if (event == "ScaleXDec") return ScaleXDec; + if (event == "ScaleYInc") return ScaleYInc; + if (event == "ScaleYDec") return ScaleYDec; + if (event == "ScaleZInc") return ScaleZInc; + if (event == "ScaleZDec") return ScaleZDec; + if (event == "Translate") return Translate; + if (event == "Rotate") return Rotate; + if (event == "Scale") return Scale; + if (event == "None") return None; + return None; + } + static inline constexpr auto VirtualEventsTypeTable = []() { std::array output; diff --git a/common/include/camera/IGimbalController.hpp b/common/include/camera/IGimbalController.hpp index b6e2ec00a..c7a0c507a 100644 --- a/common/include/camera/IGimbalController.hpp +++ b/common/include/camera/IGimbalController.hpp @@ -77,13 +77,26 @@ struct IGimbalManipulateEncoder using imguizmo_to_virtual_events_t = std::unordered_map; //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map - virtual const keyboard_to_virtual_events_t& getKeyboardMappingPreset() const = 0u; + virtual const keyboard_to_virtual_events_t getKeyboardMappingPreset() const { return {}; } //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map - virtual const mouse_to_virtual_events_t& getMouseMappingPreset() const = 0u; + virtual const mouse_to_virtual_events_t getMouseMappingPreset() const { return {}; } //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map - virtual const imguizmo_to_virtual_events_t& getImguizmoMappingPreset() const = 0u; + virtual const imguizmo_to_virtual_events_t getImguizmoMappingPreset() const { return {}; } + + virtual const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() const = 0; + virtual const mouse_to_virtual_events_t& getMouseVirtualEventMap() const = 0; + virtual const imguizmo_to_virtual_events_t& getImguizmoVirtualEventMap() const = 0; + + // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller keyboard_to_virtual_events_t table + virtual void updateKeyboardMapping(const std::function& mapKeys) = 0; + + // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller mouse_to_virtual_events_t table + virtual void updateMouseMapping(const std::function& mapKeys) = 0; + + // Binds imguizmo key codes to virtual events, the mapKeys lambda will be executed with controller imguizmo_to_virtual_events_t table + virtual void updateImguizmoMapping(const std::function& mapKeys) = 0; }; class IGimbalController : public IGimbalManipulateEncoder @@ -115,14 +128,9 @@ class IGimbalController : public IGimbalManipulateEncoder m_lastVirtualUpTimeStamp = m_nextPresentationTimeStamp; } - // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller keyboard_to_virtual_events_t table - void updateKeyboardMapping(const std::function& mapKeys) { mapKeys(m_keyboardVirtualEventMap); } - - // Binds mouse key codes to virtual events, the mapKeys lambda will be executed with controller mouse_to_virtual_events_t table - void updateMouseMapping(const std::function& mapKeys) { mapKeys(m_mouseVirtualEventMap); } - - // Binds imguizmo key codes to virtual events, the mapKeys lambda will be executed with controller imguizmo_to_virtual_events_t table - void updateImguizmoMapping(const std::function& mapKeys) { mapKeys(m_imguizmoVirtualEventMap); } + virtual void updateKeyboardMapping(const std::function& mapKeys) override { mapKeys(m_keyboardVirtualEventMap); } + virtual void updateMouseMapping(const std::function& mapKeys) override { mapKeys(m_mouseVirtualEventMap); } + virtual void updateImguizmoMapping(const std::function& mapKeys) override { mapKeys(m_imguizmoVirtualEventMap); } struct SUpdateParameters { @@ -175,9 +183,9 @@ class IGimbalController : public IGimbalManipulateEncoder count = vKeyboardEventsCount + vMouseEventsCount + vImguizmoEventsCount; } - inline const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() { return m_keyboardVirtualEventMap; } - inline const mouse_to_virtual_events_t& getMouseVirtualEventMap() { return m_mouseVirtualEventMap; } - inline const imguizmo_to_virtual_events_t& getImguizmoVirtualEventMap() { return m_imguizmoVirtualEventMap; } + virtual const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() const override { return m_keyboardVirtualEventMap; } + virtual const mouse_to_virtual_events_t& getMouseVirtualEventMap() const override { return m_mouseVirtualEventMap; } + virtual const imguizmo_to_virtual_events_t& getImguizmoVirtualEventMap() const override { return m_imguizmoVirtualEventMap; } private: /** diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp index 6222a58b4..fc0115610 100644 --- a/common/include/camera/ILinearProjection.hpp +++ b/common/include/camera/ILinearProjection.hpp @@ -38,27 +38,6 @@ class ILinearProjection : virtual public core::IReferenceCounted CProjection() : CProjection(projection_matrix_t(1)) {} CProjection(const projection_matrix_t& matrix) { setProjectionMatrix(matrix); } - inline void setProjectionMatrix(const projection_matrix_t& matrix) - { - m_projectionMatrix = matrix; - const auto det = hlsl::determinant(m_projectionMatrix); - - // we will allow you to lose a dimension since such a projection itself *may* - // be valid, however then you cannot un-project because the inverse doesn't exist! - m_isProjectionSingular = not det; - - if (m_isProjectionSingular) - { - m_isProjectionLeftHanded = std::nullopt; - m_invProjectionMatrix = std::nullopt; - } - else - { - m_isProjectionLeftHanded = det < 0.0; - m_invProjectionMatrix = inverse(m_projectionMatrix); - } - } - //! Returns P (Projection matrix) inline const projection_matrix_t& getProjectionMatrix() const { return m_projectionMatrix; } @@ -84,6 +63,28 @@ class ILinearProjection : virtual public core::IReferenceCounted return true; } + protected: + inline void setProjectionMatrix(const projection_matrix_t& matrix) + { + m_projectionMatrix = matrix; + const auto det = hlsl::determinant(m_projectionMatrix); + + // we will allow you to lose a dimension since such a projection itself *may* + // be valid, however then you cannot un-project because the inverse doesn't exist! + m_isProjectionSingular = not det; + + if (m_isProjectionSingular) + { + m_isProjectionLeftHanded = std::nullopt; + m_invProjectionMatrix = std::nullopt; + } + else + { + m_isProjectionLeftHanded = det < 0.0; + m_invProjectionMatrix = inverse(m_projectionMatrix); + } + } + private: projection_matrix_t m_projectionMatrix; inv_projection_matrix_t m_invProjectionMatrix; diff --git a/common/include/camera/IPlanarProjection.hpp b/common/include/camera/IPlanarProjection.hpp new file mode 100644 index 000000000..4ca9a0475 --- /dev/null +++ b/common/include/camera/IPlanarProjection.hpp @@ -0,0 +1,116 @@ +#ifndef _NBL_I_PLANAR_PROJECTION_HPP_ +#define _NBL_I_PLANAR_PROJECTION_HPP_ + +#include "ILinearProjection.hpp" + +namespace nbl::hlsl +{ + +class IPlanarProjection : public ILinearProjection +{ +public: + struct CProjection : public ILinearProjection::CProjection, public IGimbalManipulateEncoder + { + using base_t = ILinearProjection::CProjection; + + enum ProjectionType : uint8_t + { + Perspective, + Orthographic, + + Count + }; + + template + static CProjection create(Args&&... args) + requires (T != Count) + { + CProjection output; + + if constexpr (T == Perspective) output.setPerspective(std::forward(args)...); + else if (T == Orthographic) output.setOrthographic(std::forward(args)...); + + return output; + } + + CProjection(const CProjection& other) = default; + CProjection(CProjection&& other) noexcept = default; + + struct ProjectionParameters + { + ProjectionType m_type; + + union PlanarParameters + { + struct + { + float fov; + } perspective; + + struct + { + float orthoWidth; + } orthographic; + + PlanarParameters() {} + ~PlanarParameters() {} + } m_planar; + + float m_zNear; + float m_zFar; + }; + + inline void setPerspective(bool leftHanded = true, float zNear = 0.1f, float zFar = 100.f, float fov = 60.f, float aspectRatio = 16.f / 9.f) + { + m_parameters.m_type = Perspective; + m_parameters.m_planar.perspective.fov = fov; + m_parameters.m_zNear = zNear; + m_parameters.m_zFar = zFar; + + if (leftHanded) + base_t::setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio, zNear, zFar)); + else + base_t::setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio, zNear, zFar)); + } + + inline void setOrthographic(bool leftHanded = true, float zNear = 0.1f, float zFar = 100.f, float orthoWidth = 10.f, float aspectRatio = 16.f / 9.f) + { + m_parameters.m_type = Orthographic; + m_parameters.m_planar.orthographic.orthoWidth = orthoWidth; + m_parameters.m_zNear = zNear; + m_parameters.m_zFar = zFar; + + const auto viewHeight = orthoWidth * core::reciprocal(aspectRatio); + + if (leftHanded) + base_t::setProjectionMatrix(buildProjectionMatrixOrthoLH(orthoWidth, viewHeight, zNear, zFar)); + else + base_t::setProjectionMatrix(buildProjectionMatrixOrthoRH(orthoWidth, viewHeight, zNear, zFar)); + } + + virtual void updateKeyboardMapping(const std::function& mapKeys) override { mapKeys(m_keyboardVirtualEventMap); } + virtual void updateMouseMapping(const std::function& mapKeys) override { mapKeys(m_mouseVirtualEventMap); } + virtual void updateImguizmoMapping(const std::function& mapKeys) override { mapKeys(m_imguizmoVirtualEventMap); } + + virtual const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() const override { return m_keyboardVirtualEventMap; } + virtual const mouse_to_virtual_events_t& getMouseVirtualEventMap() const override { return m_mouseVirtualEventMap; } + virtual const imguizmo_to_virtual_events_t& getImguizmoVirtualEventMap() const override { return m_imguizmoVirtualEventMap; } + inline const ProjectionParameters& getParameters() const { return m_parameters; } + private: + CProjection() = default; + ProjectionParameters m_parameters; + + keyboard_to_virtual_events_t m_keyboardVirtualEventMap; + mouse_to_virtual_events_t m_mouseVirtualEventMap; + imguizmo_to_virtual_events_t m_imguizmoVirtualEventMap; + }; + +protected: + IPlanarProjection(core::smart_refctd_ptr&& camera) + : ILinearProjection(core::smart_refctd_ptr(camera)) {} + virtual ~IPlanarProjection() = default; +}; + +} // nbl::hlsl namespace + +#endif // _NBL_I_PLANAR_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp index 357adefe9..cb6facdcc 100644 --- a/common/include/camera/IProjection.hpp +++ b/common/include/camera/IProjection.hpp @@ -15,13 +15,16 @@ class IProjection enum class ProjectionType { - //! Perspective, Orthographic, Oblique, Axonometric, Shear projections or any custom linear transformation + //! Any raw linear transformation, for example it may represent Perspective, Orthographic, Oblique, Axonometric, Shear projections Linear, - //! Represents pre-transform *concatenated* with linear view-port transform, projects onto a quad - Perspective, + //! Specialized linear projection for planar projections with parameters + Planar, - //! Represents a Perspective projection onto cube consisting of 6 quad cube faces + //! Extension of planar projection represented by pre-transform & planar transform combined projecting onto R3 cave quad + CaveQuad, + + //! Specialized CaveQuad projection, represents planar projections onto cube with 6 quad cube faces Cube, Spherical, From e34cb66487eea0e847a7c6a607978c14a806a752 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Wed, 18 Dec 2024 16:39:26 +0100 Subject: [PATCH 63/84] planar projections are bound to controllers, now I need to fix manipulation bugs after demo & API updates --- 61_UI/include/keysmapping.hpp | 28 +- 61_UI/main.cpp | 867 ++++++++++++++------------- common/include/camera/CFPSCamera.hpp | 5 +- 3 files changed, 459 insertions(+), 441 deletions(-) diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp index 474827b77..83ec22c32 100644 --- a/61_UI/include/keysmapping.hpp +++ b/61_UI/include/keysmapping.hpp @@ -2,9 +2,8 @@ #define __NBL_KEYSMAPPING_H_INCLUDED__ #include "common.hpp" -#include "camera/ICamera.hpp" -void handleAddMapping(const char* tableID, ICamera* camera, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) +void handleAddMapping(const char* tableID, IGimbalManipulateEncoder* encoder, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) { ImGui::BeginTable(tableID, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.33f); @@ -66,20 +65,19 @@ void handleAddMapping(const char* tableID, ICamera* camera, IGimbalManipulateEnc if (ImGui::Button("Confirm Add", ImVec2(100, 30))) { if (activeController == IGimbalManipulateEncoder::Keyboard) - camera->updateKeyboardMapping([&](auto& keys) { keys[newKey] = selectedEventType; }); + encoder->updateKeyboardMapping([&](auto& keys) { keys[newKey] = selectedEventType; }); else - camera->updateMouseMapping([&](auto& mouse) { mouse[newMouseCode] = selectedEventType; }); + encoder->updateMouseMapping([&](auto& mouse) { mouse[newMouseCode] = selectedEventType; }); addMode = false; } ImGui::EndTable(); } -void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow = false) +void displayKeyMappingsAndVirtualStatesInline(IGimbalManipulateEncoder* encoder, bool spawnWindow = false) { - if (!camera) return; + if (!encoder) return; - // Per-camera state for UI rendering struct MappingState { bool addMode = false; @@ -89,11 +87,11 @@ void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow IGimbalManipulateEncoder::EncoderType activeController = IGimbalManipulateEncoder::Keyboard; }; - static std::unordered_map cameraStates; - auto& state = cameraStates[camera]; + static std::unordered_map cameraStates; + auto& state = cameraStates[encoder]; - const auto& keyboardMappings = camera->getKeyboardVirtualEventMap(); - const auto& mouseMappings = camera->getMouseVirtualEventMap(); + const auto& keyboardMappings = encoder->getKeyboardVirtualEventMap(); + const auto& mouseMappings = encoder->getMouseVirtualEventMap(); if (spawnWindow) { @@ -145,7 +143,7 @@ void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow ImGui::TableSetColumnIndex(4); if (ImGui::Button(("Delete##deleteKey" + std::to_string(static_cast(keyboardCode))).c_str())) { - camera->updateKeyboardMapping([keyboardCode](auto& keys) { keys.erase(keyboardCode); }); + encoder->updateKeyboardMapping([keyboardCode](auto& keys) { keys.erase(keyboardCode); }); break; } } @@ -154,7 +152,7 @@ void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow if (state.addMode) { ImGui::Separator(); - handleAddMapping("AddKeyboardMappingTable", camera, state.activeController, state.selectedEventType, state.newKey, state.newMouseCode, state.addMode); + handleAddMapping("AddKeyboardMappingTable", encoder, state.activeController, state.selectedEventType, state.newKey, state.newMouseCode, state.addMode); } ImGui::EndTabItem(); @@ -202,7 +200,7 @@ void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow ImGui::TableSetColumnIndex(4); if (ImGui::Button(("Delete##deleteMouse" + std::to_string(static_cast(mouseCode))).c_str())) { - camera->updateMouseMapping([mouseCode](auto& mouse) { mouse.erase(mouseCode); }); + encoder->updateMouseMapping([mouseCode](auto& mouse) { mouse.erase(mouseCode); }); break; } } @@ -211,7 +209,7 @@ void displayKeyMappingsAndVirtualStatesInline(ICamera* camera, bool spawnWindow if (state.addMode) { ImGui::Separator(); - handleAddMapping("AddMouseMappingTable", camera, state.activeController, state.selectedEventType, state.newKey, state.newMouseCode, state.addMode); + handleAddMapping("AddMouseMappingTable", encoder, state.activeController, state.selectedEventType, state.newKey, state.newMouseCode, state.addMode); } ImGui::EndTabItem(); } diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 57bc61b78..109f24bdd 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -11,12 +11,6 @@ using json = nlohmann::json; #include "camera/CCubeProjection.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING -//! TODO: this could be engine class actually, temporary keepin it in the example though -class CPlanarProjectionCameraController -{ - -}; - constexpr IGPUImage::SSubresourceRange TripleBufferUsedSubresourceRange = { .aspectMask = IGPUImage::EAF_COLOR_BIT, @@ -197,12 +191,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication constexpr static inline clock_t::duration DisplayImageDuration = std::chrono::milliseconds(900); - enum CameraRenderImguiTextureIx - { - OfflineSceneFirstCameraTextureIx = 1u, - OfflineSceneSecondCameraTextureIx = 2u - }; - public: using base_t::base_t; @@ -450,7 +438,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication m_ui.manager->registerListener([this]() -> void { imguiListen(); }); } - // Geometry Creator Render Scenes + // Geometry Creator Render Scene FBOs { resources = ResourcesBundle::create(m_device.get(), m_logger.get(), getGraphicsQueue(), m_assetManager->getGeometryCreator()); @@ -460,45 +448,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - // TOOD: we should be able to load position & orientation from json file, support multiple cameraz - const float32_t3 iPosition[CamerazCount] = { float32_t3{ -2.238f, 1.438f, -1.558f }, float32_t3{ -2.017f, 0.386f, 0.684f } }; - // order important for glm::quat, the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) but memory layout is x,y,z,w - const glm::quat iOrientation[CamerazCount] = { glm::quat(0.888f, 0.253f, 0.368f, -0.105f), glm::quat(0.55f, 0.047f, 0.830f, -0.072f) }; - - for (uint32_t i = 0u; i < cameraz.size(); ++i) - { - auto& camera = cameraz[i]; - - // lets use key map presets to update the controller - - camera = make_smart_refctd_ptr(iPosition[i], iOrientation[i]); - - // init keyboard map - camera->updateKeyboardMapping([&](auto& keys) - { - keys = camera->getKeyboardMappingPreset(); - }); - - // init mouse map - camera->updateMouseMapping([&](auto& keys) - { - keys = camera->getMouseMappingPreset(); - }); - - // init imguizmo map - camera->updateImguizmoMapping([&](auto& keys) - { - keys = camera->getImguizmoMappingPreset(); - }); - } - - // projections - projections = linear_projection_t::create(smart_refctd_ptr(cameraz.front())); - const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); - for (uint32_t i = 0u; i < scenez.size(); ++i) + + for (uint32_t i = 0u; i < sceneControlBinding.size(); ++i) { - auto& scene = scenez[i]; + auto& scene = sceneControlBinding[i].scene; scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources), dpyInfo.resX, dpyInfo.resY); if (!scene) @@ -715,8 +669,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - - // viewpoets here if (j.contains("viewports")) { for (const auto& jViewport : j["viewports"]) @@ -765,15 +717,19 @@ class UISampleApp final : public examples::SimpleWindowedApplication std::cerr << "Expected \"viewports\" keyword in JSON!" << std::endl; return false; } - } - const auto iAspectRatio = float(m_window->getWidth()) / float(m_window->getHeight()); - const auto iInvAspectRatio = float(m_window->getHeight()) / float(m_window->getWidth()); + if (m_planarProjections.size() < sceneControlBinding.size()) + { + // TODO, temporary assuming it + std::cerr << "Expected at least " << std::to_string(sceneControlBinding.size()) << " planars!" << std::endl; + return false; + } - for (uint32_t i = 0u; i < ProjectionsCount; ++i) - { - aspectRatio[i] = iAspectRatio; - invAspectRatio[i] = iInvAspectRatio; + for (uint32_t i = 0u; i < sceneControlBinding.size(); ++i) + { + auto& planar = sceneControlBinding[i].planar; + planar = smart_refctd_ptr(m_planarProjections[i]); + } } if (base_t::argv.size() >= 3 && argv[1] == "-timeout_seconds") @@ -790,12 +746,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].desc = core::smart_refctd_ptr(m_ui.manager->getFontAtlasView()); + writes[nbl::ext::imgui::UI::FontAtlasTexId].info = descriptorInfo.data() + nbl::ext::imgui::UI::FontAtlasTexId; - descriptorInfo[OfflineSceneFirstCameraTextureIx].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - descriptorInfo[OfflineSceneFirstCameraTextureIx].desc = scenez[0]->getColorAttachment(); + for (uint32_t i = 0; i < sceneControlBinding.size(); ++i) + { + const auto textureIx = i + 1u; - descriptorInfo[OfflineSceneSecondCameraTextureIx].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - descriptorInfo[OfflineSceneSecondCameraTextureIx].desc = scenez[1]->getColorAttachment(); + descriptorInfo[textureIx].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + descriptorInfo[textureIx].desc = sceneControlBinding[i].scene->getColorAttachment(); + + writes[textureIx].info = descriptorInfo.data() + textureIx; + writes[textureIx].info = descriptorInfo.data() + textureIx; + } for (uint32_t i = 0; i < descriptorInfo.size(); ++i) { @@ -804,9 +766,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication writes[i].arrayElement = i; writes[i].count = 1u; } - writes[nbl::ext::imgui::UI::FontAtlasTexId].info = descriptorInfo.data() + nbl::ext::imgui::UI::FontAtlasTexId; - writes[OfflineSceneFirstCameraTextureIx].info = descriptorInfo.data() + OfflineSceneFirstCameraTextureIx; - writes[OfflineSceneSecondCameraTextureIx].info = descriptorInfo.data() + OfflineSceneSecondCameraTextureIx; return m_device->updateDescriptorSets(writes, {}); } @@ -869,10 +828,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication }; if (useWindow) - for (auto scene : scenez) - renderOfflineScene(scene); + for (auto binding : sceneControlBinding) + renderOfflineScene(binding.scene); else - renderOfflineScene(scenez.front().get()); // just to not render to all at once + renderOfflineScene(sceneControlBinding.front().scene.get()); // just to not render to all at once const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; const IGPUCommandBuffer::SRenderpassBeginInfo info = { @@ -996,35 +955,21 @@ class UISampleApp final : public examples::SimpleWindowedApplication // (**) -> wait on offline framebuffers in window mode { - const nbl::video::ISemaphore::SWaitInfo waitInfos[] = - { - // wait for first camera scene view fb - { - .semaphore = scenez[0]->semaphore.progress.get(), - .value = scenez[0]->semaphore.finishedValue - }, - // and second one too - { - .semaphore = scenez[1]->semaphore.progress.get(), - .value = scenez[1]->semaphore.finishedValue - }, - }; - if (useWindow) { m_device->blockForSemaphores(std::to_array ( { - // wait for first camera scene view fb + // wait for first planar scene view fb { - .semaphore = scenez[0]->semaphore.progress.get(), - .value = scenez[0]->semaphore.finishedValue + .semaphore = sceneControlBinding[0].scene->semaphore.progress.get(), + .value = sceneControlBinding[0].scene->semaphore.finishedValue }, // and second one too { - .semaphore = scenez[1]->semaphore.progress.get(), - .value = scenez[1]->semaphore.finishedValue - }, + .semaphore = sceneControlBinding[1].scene->semaphore.progress.get(), + .value = sceneControlBinding[1].scene->semaphore.finishedValue + } } )); } @@ -1033,10 +978,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication m_device->blockForSemaphores(std::to_array ( { - // wait for first only, we use it temporary for FS render mode + // wait for picked planar only { - .semaphore = scenez.front()->semaphore.progress.get(), - .value = scenez.front()->semaphore.finishedValue + .semaphore = sceneControlBinding[activePlanarIx].scene->semaphore.progress.get(), + .value = sceneControlBinding[activePlanarIx].scene->semaphore.finishedValue } } )); @@ -1128,7 +1073,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (enableActiveCameraMovement) { - auto& camera = cameraz[activeCameraIndex]; + auto* camera = m_planarProjections[activePlanarIx]->getCamera(); static std::vector virtualEvents(0x45); uint32_t vCount; @@ -1156,7 +1101,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::BeginFrame(); - auto projectionMatrices = projections->getLinearProjections(); + //auto projectionMatrices = projections->getLinearProjections(); { /* @@ -1231,22 +1176,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication * */ - static struct - { - float32_t4x4 view[CamerazCount], projection[ProjectionsCount], inModel[2u], outModel[2u], outDeltaTRS[2u]; - } imguizmoM16InOut; - - const auto& firstcamera = cameraz.front(); - const auto& secondcamera = cameraz.back(); - - ImGuizmo::SetID(0u); - - imguizmoM16InOut.view[0u] = getCastedMatrix(transpose(getMatrix3x4As4x4(firstcamera->getGimbal().getViewMatrix()))); - imguizmoM16InOut.view[1u] = getCastedMatrix(transpose(getMatrix3x4As4x4(secondcamera->getGimbal().getViewMatrix()))); - - for(uint32_t i = 0u; i < ProjectionsCount; ++i) - imguizmoM16InOut.projection[i] = getCastedMatrix(transpose(projectionMatrices[i].getProjectionMatrix())); - // TODO: need to inspect where I'm wrong, workaround auto gimbalToImguizmoTRS = [&](const float32_t3x4& nblGimbalTrs) -> float32_t4x4 { @@ -1260,26 +1189,30 @@ class UISampleApp final : public examples::SimpleWindowedApplication return trs; }; + + + ImGuizmo::SetID(0u); + + /* + + imguizmoM16InOut.view[0u] = getCastedMatrix(transpose(getMatrix3x4As4x4(firstcamera->getGimbal().getViewMatrix()))); + imguizmoM16InOut.view[1u] = getCastedMatrix(transpose(getMatrix3x4As4x4(secondcamera->getGimbal().getViewMatrix()))); + + for (uint32_t i = 0u; i < ProjectionsCount; ++i) + imguizmoM16InOut.projection[i] = getCastedMatrix(transpose(projectionMatrices[i].getProjectionMatrix())); + const auto secondCameraGimbalModel = secondcamera->getGimbal()(); // we will transform a scene object's model imguizmoM16InOut.inModel[0] = transpose(getMatrix3x4As4x4(m_model)); // and second camera's model too imguizmoM16InOut.inModel[1] = gimbalToImguizmoTRS(getCastedMatrix(secondCameraGimbalModel)); - { - if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates - for(uint32_t i = 0u; i < ProjectionsCount; ++i) - imguizmoM16InOut.projection[i][1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ - - ImGuizmo::AllowAxisFlip(false); - if(enableActiveCameraMovement) - ImGuizmo::Enable(false); - else - ImGuizmo::Enable(true); + */ - aspectRatio[0] = io.DisplaySize.x / io.DisplaySize.y; - invAspectRatio[0] = io.DisplaySize.y / io.DisplaySize.x; + { + ImGuizmo::AllowAxisFlip(false); + ImGuizmo::Enable(false); SImResourceInfo info; info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; @@ -1287,158 +1220,259 @@ class UISampleApp final : public examples::SimpleWindowedApplication // render camera views onto GUIs if (useWindow) { - // ImGuizmo manipulations on the last used model matrix in window mode - IGimbalController::input_imguizmo_event_t deltaTRS; - TransformEditor(&imguizmoM16InOut.inModel[lastManipulatedModelIx][0][0], &deltaTRS); // TODO! DELTA TRS IS ONLY TRANSLATE TEMPORARY + // the only reason for those is to remind we must go with transpose & 4x4 matrices + struct ImGuizmoPlanarM16InOut + { + float32_t4x4 view, projection; + }; - if (isCameraModelBound) + struct ImGuizmoModelM16InOut { - { - static std::vector virtualEvents(0x45); + float32_t4x4 inTRS, outTRS, outDeltaTRS; + }; + + //////////////////////////////////////////////////////////////////////////// + // ABS TRS control with editor, only single object can be manipulated at time + { + ImGuizmoModelM16InOut imguizmoModel; + + if (boundCameraToManipulate) + imguizmoModel.inTRS = gimbalToImguizmoTRS(getCastedMatrix(boundCameraToManipulate->getGimbal()())); + else + imguizmoModel.inTRS = transpose(getMatrix3x4As4x4(m_model)); - uint32_t vCount; + imguizmoModel.outTRS = imguizmoModel.inTRS; - secondcamera->beginInputProcessing(m_nextPresentationTimestamp); + // TODO! DELTA TRS IS ONLY TRANSLATE TEMPORARY + TransformEditor(&imguizmoModel.outTRS[0][0], &imguizmoModel.outDeltaTRS); + + // generate virtual events given delta TRS matrix + if (boundCameraToManipulate) + { { - secondcamera->process(nullptr, vCount); + static std::vector virtualEvents(0x45); - if (virtualEvents.size() < vCount) - virtualEvents.resize(vCount); + uint32_t vCount; - IGimbalController::SUpdateParameters params; - params.imguizmoEvents = { { deltaTRS } }; - secondcamera->process(virtualEvents.data(), vCount, params); - } - secondcamera->endInputProcessing(); + boundCameraToManipulate->beginInputProcessing(m_nextPresentationTimestamp); + { + boundCameraToManipulate->process(nullptr, vCount); - // I start to think controller should be able to set sensitivity to scale magnitudes of generated events - // in order for camera to not keep any magnitude scalars like move or rotation speed scales - smart_refctd_ptr_static_cast(secondcamera)->setMoveSpeedScale(1); - smart_refctd_ptr_static_cast(secondcamera)->setRotationSpeedScale(1); + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); - // NOTE: generated events from ImGuizmo controller are always in world space! - if(vCount) - secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); - } - } + IGimbalController::SUpdateParameters params; + params.imguizmoEvents = { { imguizmoModel.outDeltaTRS } }; + boundCameraToManipulate->process(virtualEvents.data(), vCount, params); + } + boundCameraToManipulate->endInputProcessing(); - uint32_t gizmoIx = {}; - bool manipulatedFromAnyWindow = false; + // I start to think controller should be able to set sensitivity to scale magnitudes of generated events + // in order for camera to not keep any magnitude scalars like move or rotation speed scales - // we have 2 GUI windows we render into with FBOs - for (uint32_t windowIndex = 0; windowIndex < 2u; ++windowIndex) - { - const auto& cameraIx = windowIndex; // tmp bound & hardcoded, we will extend it later - const auto projectionIx = cameraIx + 1u; // offset because first projection belongs to full screen (**) - info.textureID = projectionIx; + auto* fps = dynamic_cast(boundCameraToManipulate.get()); - if(isPerspective[projectionIx]) - ImGuizmo::SetOrthographic(false); - else - ImGuizmo::SetOrthographic(true); + if (fps) + { + smart_refctd_ptr_static_cast(boundCameraToManipulate)->setMoveSpeedScale(1); + smart_refctd_ptr_static_cast(boundCameraToManipulate)->setRotationSpeedScale(1); + } - if (areAxesFlipped[projectionIx]) - ImGuizmo::AllowAxisFlip(true); + // NOTE: generated events from ImGuizmo controller are always in world space! + if (vCount) + boundCameraToManipulate->manipulate({ virtualEvents.data(), vCount }, ICamera::World); + } + } else - ImGuizmo::AllowAxisFlip(false); + { + // for scene demo model full affine transformation without limits is assumed + m_model = float32_t3x4(transpose(imguizmoModel.outTRS)); + } + } + //////////////////////////////////////////////////////////////////////////// + for (uint32_t windowIx = 0; windowIx < sceneControlBinding.size(); ++windowIx) + { + // setup imgui window ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); - ImGui::SetNextWindowPos(ImVec2(400, 20 + cameraIx * 420), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImVec2(400, 20 + windowIx * 420), ImGuiCond_Appearing); ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); - std::string ident = "Camera \"" + std::to_string(cameraIx) + "\" View"; + const std::string ident = "Render Window \"" + std::to_string(windowIx) + "\""; ImGui::Begin(ident.data(), 0, ImGuiWindowFlags_NoMove); - ImGuizmo::SetDrawlist(); + const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetCursorScreenPos(), cursorPos = ImGui::GetWindowPos(); + + // setup bound entities for the window like camera & projections + auto& binding = sceneControlBinding[windowIx]; + binding.aspectRatio = contentRegionSize.x / contentRegionSize.y; + auto* planarViewCameraBound = binding.planar->getCamera(); + assert(planarViewCameraBound); + + auto& projection = binding.planar->getPlanarProjections()[binding.boundProjectionIx]; - ImVec2 contentRegionSize, windowPos, cursorPos; - contentRegionSize = ImGui::GetContentRegionAvail(); - cursorPos = ImGui::GetCursorScreenPos(); - windowPos = ImGui::GetWindowPos(); + // first 0th texture is for UI texture atlas, then there are our window textures + auto fboImguiTextureID = windowIx + 1u; + info.textureID = fboImguiTextureID; - // (**) - aspectRatio[projectionIx] = contentRegionSize.x / contentRegionSize.y; - invAspectRatio[projectionIx] = contentRegionSize.y / contentRegionSize.x; + if(binding.allowGizmoAxesToFlip) + ImGuizmo::AllowAxisFlip(true); + else + ImGuizmo::AllowAxisFlip(false); + + if(projection.getParameters().m_type == IPlanarProjection::CProjection::Orthographic) + ImGuizmo::SetOrthographic(true); + else + ImGuizmo::SetOrthographic(false); + + // set imguizmo draw lists + ImGuizmo::SetDrawlist(); ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + // I will assume we need to focus a window to start manipulating objects from it if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) - lastProjectionIx = projectionIx; + activePlanarIx = windowIx; - // but only 2 objects with matrices we will try to manipulate & we can do this from both windows! - // however note it only makes sense to obsly assume we cannot manipulate 2 objects at the same time - for (uint32_t modelIx = 0; modelIx < 2; modelIx++) - { - const bool isCameraGizmoBound = gizmoIx == 1; - const bool discard = isCameraGizmoBound && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; + if (windowIx == activePlanarIx && not enableActiveCameraMovement) + ImGuizmo::Enable(true); - // future logic will need to filter gizmos which represents gimbal of camera which view matrix we use to render scene with - if (gizmoIx == 3) - continue; + ImGuizmoPlanarM16InOut imguizmoPlanar; + imguizmoPlanar.view = getCastedMatrix(transpose(getMatrix3x4As4x4(planarViewCameraBound->getGimbal().getViewMatrix()))); + imguizmoPlanar.projection = getCastedMatrix(transpose(projection.getProjectionMatrix())); - // and because of imguizmo API usage to achieve it we must work on copies & filter the output (unless we try gizmo enable/disable) - // -> in general we need to be careful to not edit the same model twice - - auto model = imguizmoM16InOut.inModel[modelIx]; - float32_t4x4 deltaOutputTRS; + if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates + imguizmoPlanar.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ - // note we also need to take care of unique gizmo IDs, we have in total 4 gizmos even though we only want to manipulate 2 objects in total - ImGuizmo::PushID(gizmoIx); + for (uint32_t modelIx = 0; modelIx < 1u + m_planarProjections.size(); modelIx++) + { + const bool isCameraGimbalTarget = modelIx; // I assume scene demo model is 0th ix then cameras + ICamera* const targetGimbalManipulationCamera = isCameraGimbalTarget ? m_planarProjections[modelIx - 1u]->getCamera() : nullptr; + const bool discard = isCameraGimbalTarget && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; // discard WiP stuff - ImGuiIO& io = ImGui::GetIO(); + // if we try to manipulate a camera which appears to be the same camera we see scene from then obvsly it doesn't make sense to manipulate its gizmo so we skip it + if (targetGimbalManipulationCamera == planarViewCameraBound) + continue; - const bool success = ImGuizmo::Manipulate(&imguizmoM16InOut.view[cameraIx][0][0], &imguizmoM16InOut.projection[projectionIx][0][0], mCurrentGizmoOperation, mCurrentGizmoMode, &model[0][0], &deltaOutputTRS[0][0], useSnap ? &snap[0] : nullptr); + ImGuizmoModelM16InOut imguizmoModel; - // we manipulated a gizmo from a X-th window, now we update output matrices and assume no more gizmos can be manipulated at the same frame - if (!manipulatedFromAnyWindow) + if (isCameraGimbalTarget) { - // TMP WIP, our imguizmo controller doesnt support rotation & scale yet and its because - // - there are numerical issues with imguizmo decompose/recompose TRS (and the author also says it) - // - in rotate mode delta TRS matrix contains translate part (how? no idea, maybe imguizmo bug) and it glitches everything - - if (!discard) - { - imguizmoM16InOut.outModel[modelIx] = model; - imguizmoM16InOut.outDeltaTRS[modelIx] = deltaOutputTRS; - } + assert(targetGimbalManipulationCamera); + imguizmoModel.inTRS = gimbalToImguizmoTRS(getCastedMatrix(targetGimbalManipulationCamera->getGimbal()())); } + else + imguizmoModel.inTRS = transpose(getMatrix3x4As4x4(m_model)); - if (success) - manipulatedFromAnyWindow = true; - - if (ImGuizmo::IsUsing()) + imguizmoModel.outTRS = imguizmoModel.inTRS; + + ImGuizmo::PushID(modelIx); { - lastManipulatedModelIx = modelIx; - lastManipulatedGizmoIx = gizmoIx; - isCameraModelBound = lastManipulatedModelIx == 1u; + const bool success = ImGuizmo::Manipulate(&imguizmoPlanar.view[0][0], &imguizmoPlanar.projection[0][0], mCurrentGizmoOperation, mCurrentGizmoMode, &imguizmoModel.outTRS[0][0], &imguizmoModel.outDeltaTRS[0][0], useSnap ? &snap[0] : nullptr); - lastManipulatedModelIdentifier = isCameraModelBound ? "Camera FPS" : "Geometry Creator Object"; - } + if (success) + { + // TMP WIP, our imguizmo controller doesnt support rotation & scale yet and its because + // - there are numerical issues with imguizmo decompose/recompose TRS (and the author also says it) + // - in rotate mode delta TRS matrix contains translate part (how? no idea, maybe imguizmo bug) and it glitches everything - if (ImGuizmo::IsOver()) - { - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); + if (!discard) + { + if (targetGimbalManipulationCamera) + { + boundCameraToManipulate = smart_refctd_ptr(targetGimbalManipulationCamera); + + /* + testing imguizmo controller for target camera, we use delta world imguizmo TRS matrix to generate virtual events + */ + + { + static std::vector virtualEvents(0x45); + + if (ImGuizmo::IsUsingAny()) + { + uint32_t vCount; + + targetGimbalManipulationCamera->beginInputProcessing(m_nextPresentationTimestamp); + { + targetGimbalManipulationCamera->process(nullptr, vCount); + + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); + + IGimbalController::SUpdateParameters params; + params.imguizmoEvents = { { imguizmoModel.outTRS } }; + targetGimbalManipulationCamera->process(virtualEvents.data(), vCount, params); + } + targetGimbalManipulationCamera->endInputProcessing(); + + auto* fps = dynamic_cast(targetGimbalManipulationCamera); + + float pMoveSpeed = 1.f, pRotationSpeed = 1.f; + + if (fps) + { + pMoveSpeed = fps->getMoveSpeedScale(); + pRotationSpeed = fps->getRotationSpeedScale(); + + // I start to think controller should be able to set sensitivity to scale magnitudes of generated events + // in order for camera to not keep any magnitude scalars like move or rotation speed scales + + fps->setMoveSpeedScale(1); + fps->setRotationSpeedScale(1); + } + + // NOTE: generated events from ImGuizmo controller are always in world space! + targetGimbalManipulationCamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); + + if (fps) + { + fps->setMoveSpeedScale(pMoveSpeed); + fps->setRotationSpeedScale(pRotationSpeed); + } + } + } + } + else + { + // again, for scene demo model full affine transformation without limits is assumed + m_model = float32_t3x4(transpose(imguizmoModel.outTRS)); + boundCameraToManipulate = nullptr; + } + } + } + + if (ImGuizmo::IsOver()) + { + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); - ImVec2 mousePos = io.MousePos; - ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); + ImGuiIO& io = ImGui::GetIO(); + ImVec2 mousePos = io.MousePos; + ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); - ImGui::Begin("InfoOverlay", nullptr, - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings); + ImGui::Begin("InfoOverlay", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings); - ImGui::Text("Identifier: %s", lastManipulatedModelIdentifier.c_str()); - ImGui::Text("Object Ix: %u", modelIx); + std::string ident; - ImGui::End(); + if (targetGimbalManipulationCamera) + ident = targetGimbalManipulationCamera->getIdentifier(); + else + ident = "Geometry Creator Object"; - ImGui::PopStyleVar(); - ImGui::PopStyleColor(2); - } + ImGui::Text("Identifier: %s", ident.c_str()); + ImGui::Text("Object Ix: %u", modelIx); + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + } ImGuizmo::PopID(); - ++gizmoIx; } ImGui::End(); @@ -1448,18 +1482,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication // render selected camera view onto full screen else { - info.textureID = OfflineSceneFirstCameraTextureIx;; - lastProjectionIx = 0; + info.textureID = 1u + activePlanarIx; ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(io.DisplaySize); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); - - ImVec2 contentRegionSize, windowPos, cursorPos; - contentRegionSize = ImGui::GetContentRegionAvail(); - cursorPos = ImGui::GetCursorScreenPos(); - windowPos = ImGui::GetWindowPos(); + const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetCursorScreenPos(), cursorPos = ImGui::GetWindowPos(); ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); @@ -1469,96 +1498,133 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - /* - testing imguizmo controller for second camera, we use delta world imguizmo TRS matrix to generate virtual events - */ - + // to Nabla + update camera & model matrices { - static std::vector virtualEvents(0x45); - - if (ImGuizmo::IsUsingAny()) + const auto& references = resources->objects; + const auto type = static_cast(gcIndex); + const auto& [gpu, meta] = references[type]; + + for (uint32_t i = 0u; i < sceneControlBinding.size(); ++i) { - uint32_t vCount; + auto& binding = sceneControlBinding[i]; + auto& hook = binding.scene->object; - secondcamera->beginInputProcessing(m_nextPresentationTimestamp); + auto* boundPlanarCamera = binding.planar->getCamera(); + + hook.meta.type = type; + hook.meta.name = meta.name; { - secondcamera->process(nullptr, vCount); + float32_t3x4 viewMatrix, modelViewMatrix, normalMatrix; + float32_t4x4 modelViewProjectionMatrix; - if (virtualEvents.size() < vCount) - virtualEvents.resize(vCount); + viewMatrix = getCastedMatrix(boundPlanarCamera->getGimbal().getViewMatrix()); + modelViewMatrix = concatenateBFollowedByA(viewMatrix, m_model); - IGimbalController::SUpdateParameters params; - params.imguizmoEvents = { { imguizmoM16InOut.outDeltaTRS[1u] } }; - secondcamera->process(virtualEvents.data(), vCount, params); - } - secondcamera->endInputProcessing(); + auto& projection = binding.planar->getPlanarProjections()[binding.boundProjectionIx]; - // I start to think controller should be able to set sensitivity to scale magnitudes of generated events - // in order for camera to not keep any magnitude scalars like move or rotation speed scales - smart_refctd_ptr_static_cast(secondcamera)->setMoveSpeedScale(1); - smart_refctd_ptr_static_cast(secondcamera)->setRotationSpeedScale(1); + auto concatMatrix = mul(getCastedMatrix(projection.getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); + modelViewProjectionMatrix = mul(concatMatrix, getMatrix3x4As4x4(m_model)); - // NOTE: generated events from ImGuizmo controller are always in world space! - secondcamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); + memcpy(hook.viewParameters.MVP, &modelViewProjectionMatrix[0][0], sizeof(hook.viewParameters.MVP)); + memcpy(hook.viewParameters.MV, &modelViewMatrix[0][0], sizeof(hook.viewParameters.MV)); + memcpy(hook.viewParameters.NormalMat, &normalMatrix[0][0], sizeof(hook.viewParameters.NormalMat)); + } } } - for (uint32_t i = 0u; i < cameraz.size(); ++i) + // Planars { - auto& camera = cameraz[i]; - smart_refctd_ptr_static_cast(camera)->setMoveSpeedScale(moveSpeed[i]); - smart_refctd_ptr_static_cast(camera)->setRotationSpeedScale(rotateSpeed[i]); - } + ImGui::Begin("Projection"); - // update scenes data - // to Nabla + update camera & model matrices - - // TODO: make it more nicely once view manipulate supported - //const_cast(firstcamera->getGimbal().getViewMatrix()) = float64_t3x4(getCastedMatrix(transpose(imguizmoM16InOut.view[0u]))); // a hack for "view manipulate", correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) - { - m_model = float32_t3x4(transpose(imguizmoM16InOut.outModel[0])); + ImGui::Checkbox("Window mode##useWindow", &useWindow); - auto firstCameraView = getCastedMatrix(firstcamera->getGimbal().getViewMatrix()); - auto secondCameraView = getCastedMatrix(secondcamera->getGimbal().getViewMatrix()); + ImGui::Separator(); - const float32_t3x4* views[] = + const auto activePlanarIxString = std::to_string(activePlanarIx); + ImGui::Text("Active Planar Ix: %s", activePlanarIxString.c_str()); + + auto& active = sceneControlBinding[activePlanarIx]; + const auto& params = active.planar->getPlanarProjections()[active.boundProjectionIx].getParameters(); + auto selectedProjectionType = params.m_type; { - &firstCameraView, - &secondCameraView - }; + const char* labels[] = { "Perspective", "Orthographic" }; + int type = static_cast(params.m_type); - const auto& references = resources->objects; - const auto type = static_cast(gcIndex); - const auto& [gpu, meta] = references[type]; + if (ImGui::Combo("Projection Type", &type, labels, IM_ARRAYSIZE(labels))) + selectedProjectionType = static_cast(type); + } - for (uint32_t i = 0u; i < cameraz.size(); ++i) + auto getPresetName = [&](auto ix) -> std::string { - auto& scene = scenez[i]; - auto& hook = scene->object; + switch (selectedProjectionType) + { + case IPlanarProjection::CProjection::Perspective: return "Perspective Projection Preset " + std::to_string(ix); + case IPlanarProjection::CProjection::Orthographic: return "Orthographic Projection Preset " + std::to_string(ix); + default: return "Unknown Projection Preset " + std::to_string(ix); + } + }; - hook.meta.type = type; - hook.meta.name = meta.name; + if (ImGui::BeginCombo("Projection Preset", getPresetName(active.boundProjectionIx).c_str())) + { + auto& projections = active.planar->getPlanarProjections(); + + for (uint32_t i = 0; i < projections.size(); ++i) { - float32_t3x4 modelView, normal; - float32_t4x4 modelViewProjection; + const auto& projection = projections[i]; + const auto& params = projection.getParameters(); - const auto& viewMatrix = *views[useWindow ? i : activeCameraIndex]; - modelView = concatenateBFollowedByA(viewMatrix, m_model); + if (params.m_type != selectedProjectionType) + continue; - // TODO - //modelView.getSub3x3InverseTranspose(normal); + bool isSelected = (i == active.boundProjectionIx); - auto concatMatrix = mul(getCastedMatrix(projectionMatrices[useWindow ? i + 1u : 0u].getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); - modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(m_model)); + if (ImGui::Selectable(getPresetName(i).c_str(), isSelected)) + active.boundProjectionIx = i; - memcpy(hook.viewParameters.MVP, &modelViewProjection[0][0], sizeof(hook.viewParameters.MVP)); - memcpy(hook.viewParameters.MV, &modelView[0][0], sizeof(hook.viewParameters.MV)); - memcpy(hook.viewParameters.NormalMat, &normal[0][0], sizeof(hook.viewParameters.NormalMat)); + if (isSelected) + ImGui::SetItemDefaultFocus(); } + ImGui::EndCombo(); + } + + auto* const boundCamera = active.planar->getCamera(); + auto& boundProjection = active.planar->getPlanarProjections()[active.boundProjectionIx]; + assert(not boundProjection.isProjectionSingular()); + + auto updateParameters = boundProjection.getParameters(); + bool isLH = boundProjection.isProjectionLeftHanded().value(); + + if (useWindow) + ImGui::Checkbox("Allow axes to flip##allowAxesToFlip", &active.allowGizmoAxesToFlip); + + if (ImGui::RadioButton("LH", boundProjection.isProjectionLeftHanded().value())) + isLH = true; + + ImGui::SameLine(); + + if (ImGui::RadioButton("RH", not isLH)) + isLH = false; + + ImGui::SliderFloat("zNear", &updateParameters.m_zNear, 0.1f, 100.f, "%.2f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("zFar", &updateParameters.m_zFar, 110.f, 10000.f, "%.1f", ImGuiSliderFlags_Logarithmic); + + switch (selectedProjectionType) + { + case IPlanarProjection::CProjection::Perspective: + { + ImGui::SliderFloat("Fov", &updateParameters.m_planar.perspective.fov, 20.f, 150.f, "%.1f", ImGuiSliderFlags_Logarithmic); + boundProjection.setPerspective(isLH, updateParameters.m_zNear, updateParameters.m_zFar, updateParameters.m_planar.perspective.fov, active.aspectRatio); + } break; + + case IPlanarProjection::CProjection::Orthographic: + { + ImGui::SliderFloat("Ortho width", &updateParameters.m_planar.orthographic.orthoWidth, 1.f, 20.f, "%.1f", ImGuiSliderFlags_Logarithmic); + boundProjection.setOrthographic(isLH, updateParameters.m_zNear, updateParameters.m_zFar, updateParameters.m_planar.orthographic.orthoWidth, active.aspectRatio); + } break; + + default: break; } - } - { { ImGuiIO& io = ImGui::GetIO(); @@ -1578,33 +1644,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication io.WantCaptureMouse = true; } - ImGui::Begin("Cameras", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysVerticalScrollbar); - ImGui::Checkbox("Window mode##useWindow", &useWindow); - ImGui::Text("Select Active Camera:"); - ImGui::Separator(); - - if (ImGui::BeginCombo("Active Camera", ("Camera " + std::to_string(activeCameraIndex)).c_str())) - { - for (uint32_t cameraIndex = 0; cameraIndex < CamerazCount; ++cameraIndex) - { - bool isSelected = (cameraIndex == activeCameraIndex); - std::string comboLabel = "Camera " + std::to_string(cameraIndex); - - if (ImGui::Selectable(comboLabel.c_str(), isSelected)) - activeCameraIndex = cameraIndex; - - if (isSelected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - - ImGui::Separator(); - if (enableActiveCameraMovement) - ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Active Camera Movement: Enabled"); + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Bound Camera Movement: Enabled"); else - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Active Camera Movement: Disabled"); + ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Bound Camera Movement: Disabled"); if (ImGui::IsItemHovered()) { @@ -1620,7 +1663,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); - ImGui::Text("Press 'Space' to Enable/Disable selected camera movement"); + ImGui::Text("Press 'Space' to Enable/Disable bound planar camera movement"); ImGui::End(); @@ -1630,87 +1673,59 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Separator(); - for (size_t cameraIndex = 0; cameraIndex < CamerazCount; ++cameraIndex) + const auto flags = ImGuiTreeNodeFlags_DefaultOpen; + if (ImGui::TreeNodeEx("Bound Camera", flags)) { - auto& camera = cameraz[cameraIndex]; - if (!camera) - continue; - - const auto flags = (activeCameraIndex == cameraIndex) ? ImGuiTreeNodeFlags_DefaultOpen : ImGuiTreeNodeFlags_None; - std::string treeNodeLabel = "Camera " + std::to_string(cameraIndex); - - if (ImGui::TreeNodeEx(treeNodeLabel.c_str(), flags)) { - ImGui::SliderFloat("Move speed factor", &moveSpeed[cameraIndex], 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); - ImGui::SliderFloat("Rotate speed factor", &rotateSpeed[cameraIndex], 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); + auto* fps = dynamic_cast(boundCamera); - if (ImGui::TreeNodeEx("Data", ImGuiTreeNodeFlags_None)) + if (fps) { - auto& gimbal = camera->getGimbal(); - const auto position = getCastedVector(gimbal.getPosition()); - const auto& orientation = gimbal.getOrientation(); - const auto viewMatrix = getCastedMatrix(gimbal.getViewMatrix()); - - ImGui::Text("Type: %s", camera->getIdentifier().data()); - ImGui::Separator(); - addMatrixTable("Position", ("PositionTable_" + std::to_string(cameraIndex)).c_str(), 1, 3, &position[0], false); - addMatrixTable("Orientation (Quaternion)", ("OrientationTable_" + std::to_string(cameraIndex)).c_str(), 1, 4, &orientation[0], false); - addMatrixTable("View Matrix", ("ViewMatrixTable_" + std::to_string(cameraIndex)).c_str(), 3, 4, &viewMatrix[0][0], false); - ImGui::TreePop(); - } + float moveSpeed = fps->getMoveSpeedScale(); + float rotationSpeed = fps->getRotationSpeedScale(); - if (ImGui::TreeNodeEx("Virtual Event Mappings", ImGuiTreeNodeFlags_DefaultOpen)) - { - displayKeyMappingsAndVirtualStatesInline(camera.get()); - ImGui::TreePop(); + ImGui::SliderFloat("Move speed factor", &moveSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("Rotate speed factor", &rotationSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); + + fps->setMoveSpeedScale(moveSpeed); + fps->setRotationSpeedScale(rotationSpeed); } + } + if (ImGui::TreeNodeEx("World Data", ImGuiTreeNodeFlags_None)) + { + auto& gimbal = boundCamera->getGimbal(); + const auto position = getCastedVector(gimbal.getPosition()); + const auto& orientation = gimbal.getOrientation(); + const auto viewMatrix = getCastedMatrix(gimbal.getViewMatrix()); + + ImGui::Text("Type: %s", boundCamera->getIdentifier().data()); + ImGui::Separator(); + addMatrixTable("Position", ("PositionTable_" + activePlanarIxString).c_str(), 1, 3, &position[0], false); + addMatrixTable("Orientation (Quaternion)", ("OrientationTable_" + activePlanarIxString).c_str(), 1, 4, &orientation[0], false); + addMatrixTable("View Matrix", ("ViewMatrixTable_" + activePlanarIxString).c_str(), 3, 4, &viewMatrix[0][0], false); ImGui::TreePop(); } - } - - ImGui::End(); - } - } - - // Projections - { - ImGui::Begin("Projection"); - - ImGui::Text("Ix: %s", std::to_string(lastProjectionIx).c_str()); - - if (ImGui::RadioButton("Perspective", isPerspective[lastProjectionIx])) - isPerspective[lastProjectionIx] = true; - - ImGui::SameLine(); - if (ImGui::RadioButton("Orthographic", !isPerspective[lastProjectionIx])) - isPerspective[lastProjectionIx] = false; - - if (ImGui::RadioButton("LH", isLH[lastProjectionIx])) - isLH[lastProjectionIx] = true; - - ImGui::SameLine(); - - if (ImGui::RadioButton("RH", !isLH[lastProjectionIx])) - isLH[lastProjectionIx] = false; - - if(useWindow) - ImGui::Checkbox("Allow axes to flip##allowAxesToFlip", areAxesFlipped.data() + lastProjectionIx); + if (ImGui::TreeNodeEx("Virtual Event Mappings", flags)) + { + auto* encoder = static_cast(&boundProjection); + displayKeyMappingsAndVirtualStatesInline(encoder); + ImGui::TreePop(); + } - if (isPerspective[lastProjectionIx]) - ImGui::SliderFloat("Fov", &fov[lastProjectionIx], 20.f, 150.f, "%.1f", ImGuiSliderFlags_Logarithmic); - else - ImGui::SliderFloat("Ortho width", &viewWidth[lastProjectionIx], 1.f, 20.f, "%.1f", ImGuiSliderFlags_Logarithmic); + ImGui::TreePop(); + } - ImGui::SliderFloat("zNear", &zNear[lastProjectionIx], 0.1f, 100.f, "%.2f", ImGuiSliderFlags_Logarithmic); - ImGui::SliderFloat("zFar", &zFar[lastProjectionIx], 110.f, 10000.f, "%.1f", ImGuiSliderFlags_Logarithmic); + boundCamera->updateKeyboardMapping([&](auto& map) { map = boundProjection.getKeyboardVirtualEventMap(); }); + boundCamera->updateMouseMapping([&](auto& map) { map = boundProjection.getMouseVirtualEventMap(); }); + } ImGui::End(); } } - inline void TransformEditor(float* matrix, IGimbalController::input_imguizmo_event_t* deltaTRS = nullptr) + inline void TransformEditor(float* m16TRSmatrix, IGimbalController::input_imguizmo_event_t* deltaTRS = nullptr) { static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; @@ -1728,14 +1743,16 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Text("Using gizmo"); ImGui::Separator(); - ImGui::Text("Object Ix: \"%s\"", std::to_string(lastManipulatedModelIx).c_str()); - ImGui::Separator(); + std::string indent; + if (boundCameraToManipulate) + indent = boundCameraToManipulate->getIdentifier(); + else + indent = "Geometry Creator Object"; - ImGui::Text("Identifier: \"%s\"", lastManipulatedModelIdentifier.c_str()); + ImGui::Text("Identifier: \"%s\"", indent.c_str()); ImGui::Separator(); - - if (!isCameraModelBound) + if (!boundCameraToManipulate) { static const char* gcObjectTypeNames[] = { "Cube", @@ -1763,8 +1780,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - - addMatrixTable("Model (TRS) Matrix", "ModelMatrixTable", 4, 4, matrix); + addMatrixTable("Model (TRS) Matrix", "ModelMatrixTable", 4, 4, m16TRSmatrix); if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE; @@ -1782,14 +1798,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (deltaTRS) *deltaTRS = IGimbalController::input_imguizmo_event_t(1); - ImGuizmo::DecomposeMatrixToComponents(matrix, &matrixTranslation[0], &matrixRotation[0], &matrixScale[0]); - decomposed = *reinterpret_cast(matrix); + ImGuizmo::DecomposeMatrixToComponents(m16TRSmatrix, &matrixTranslation[0], &matrixRotation[0], &matrixScale[0]); + decomposed = *reinterpret_cast(m16TRSmatrix); { ImGuiInputTextFlags flags = 0; ImGui::InputFloat3("Tr", &matrixTranslation[0], "%.3f", flags); - if (isCameraModelBound) // TODO: cameras are WiP here, imguizmo controller only works with translate manipulation + abs are banned currently + if (boundCameraToManipulate) // TODO: cameras are WiP here, imguizmo controller only works with translate manipulation + abs are banned currently { ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(1.0f, 0.0f, 0.0f, 0.5f)); flags |= ImGuiInputTextFlags_ReadOnly; @@ -1798,11 +1814,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::InputFloat3("Rt", &matrixRotation[0], "%.3f", flags); ImGui::InputFloat3("Sc", &matrixScale[0], "%.3f", flags); - if(isCameraModelBound) + if(boundCameraToManipulate) ImGui::PopStyleColor(); } - ImGuizmo::RecomposeMatrixFromComponents(&matrixTranslation[0], &matrixRotation[0], &matrixScale[0], matrix); - recomposed = *reinterpret_cast(matrix); + ImGuizmo::RecomposeMatrixFromComponents(&matrixTranslation[0], &matrixRotation[0], &matrixScale[0], m16TRSmatrix); + recomposed = *reinterpret_cast(m16TRSmatrix); // TODO AND NOTE: I only take care of translate part temporary! if(deltaTRS) @@ -1895,9 +1911,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication //! next presentation timestamp std::chrono::microseconds m_nextPresentationTimestamp = {}; - // UI font atlas; first camera fb, second camera fb - constexpr static inline auto TotalUISampleTexturesAmount = 3u; - core::smart_refctd_ptr m_descriptorSetPool; struct CRenderUI @@ -1912,30 +1925,40 @@ class UISampleApp final : public examples::SimpleWindowedApplication core::smart_refctd_ptr descriptorSet; }; - // one model object in the world, testing 2 cameraz for which view is rendered to separate frame buffers (so what they see) with new controller API including imguizmo + // one model object in the world, testing multiuple cameraz for which view is rendered to separate frame buffers (so what they see) with new controller API including imguizmo nbl::hlsl::float32_t3x4 m_model = nbl::hlsl::float32_t3x4(1.f); - static constexpr inline auto CamerazCount = 2u; - std::array, CamerazCount> scenez; - std::array, CamerazCount> cameraz; - uint32_t activeCameraIndex = 0; + nbl::core::smart_refctd_ptr boundCameraToManipulate; + + using planar_projections_range_t = std::vector; + using planar_projection_t = CPlanarProjection; + std::vector> m_planarProjections; + bool enableActiveCameraMovement = false; + + struct SceneControlBinding + { + nbl::core::smart_refctd_ptr scene; + nbl::core::smart_refctd_ptr planar; + uint32_t boundProjectionIx = 0u; + bool allowGizmoAxesToFlip = false; + float aspectRatio = 16.f / 9.f; + }; + + static constexpr inline auto MaxSceneFBOs = 2u; + std::array sceneControlBinding; + uint32_t activePlanarIx = 0u; + + // UI font atlas + viewport FBO color attachment textures + constexpr static inline auto TotalUISampleTexturesAmount = 1u + MaxSceneFBOs; + nbl::core::smart_refctd_ptr resources; CRenderUI m_ui; video::CDumbPresentationOracle oracle; - uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed - - static constexpr inline auto ProjectionsCount = 3u; // full screen, first & second GUI windows - using linear_projections_range_t = std::array; - using linear_projection_t = CLinearProjection; - nbl::core::smart_refctd_ptr projections; + uint16_t gcIndex = {}; const bool flipGizmoY = true; - std::array isPerspective = { true, true, true }, isLH = { true, true, true }, areAxesFlipped = { false, false, false }; - std::array fov = { 60.f, 60.f, 60.f }, zNear = { 0.1f, 0.1f, 0.1f }, zFar = { 10000.f, 10000.f, 10000.f }, viewWidth = { 10.f, 10.f, 10.f }, aspectRatio = {}, invAspectRatio = {}; - std::array moveSpeed = { 0.01, 0.01 }, rotateSpeed = { 0.003, 0.003 }; - bool isCameraModelBound = false; float camYAngle = 165.f / 180.f * 3.14159f; float camXAngle = 32.f / 180.f * 3.14159f; float camDistance = 8.f; @@ -1943,14 +1966,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::OPERATION mCurrentGizmoOperation = ImGuizmo::TRANSLATE; ImGuizmo::MODE mCurrentGizmoMode = ImGuizmo::LOCAL; float snap[3] = { 1.f, 1.f, 1.f }; - int lastManipulatedModelIx = 0, lastManipulatedGizmoIx = 0, lastProjectionIx = 0; - std::string lastManipulatedModelIdentifier = "Geometry Creator Object"; bool firstFrame = true; - - using planar_projections_range_t = std::vector; - using planar_projection_t = CPlanarProjection; - std::vector> m_planarProjections; }; NBL_MAIN_FUNC(UISampleApp) \ No newline at end of file diff --git a/common/include/camera/CFPSCamera.hpp b/common/include/camera/CFPSCamera.hpp index a6d169f09..6efc91f95 100644 --- a/common/include/camera/CFPSCamera.hpp +++ b/common/include/camera/CFPSCamera.hpp @@ -83,7 +83,7 @@ class CFPSCamera final : public ICamera // (***) inline void setMoveSpeedScale(double scalar) - { + { m_moveSpeedScale = scalar; } @@ -93,6 +93,9 @@ class CFPSCamera final : public ICamera m_rotationSpeedScale = scalar; } + inline double getMoveSpeedScale() const { return m_moveSpeedScale; } + inline double getRotationSpeedScale() const { return m_rotationSpeedScale; } + private: typename base_t::CGimbal m_gimbal; From 1332a872013c3e90528d8ddb3c1dbb24fd9ce328 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 19 Dec 2024 14:35:00 +0100 Subject: [PATCH 64/84] use `struct CPlanarProjectionWithMeta : public CPlanarProjection` in the demo with bound & last projection IXes, fix picking projection presets --- 61_UI/main.cpp | 87 ++++++++++++++++++--- common/include/camera/CPlanarProjection.hpp | 2 +- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 109f24bdd..1dc69874c 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -11,6 +11,24 @@ using json = nlohmann::json; #include "camera/CCubeProjection.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING +using planar_projections_range_t = std::vector; +struct CPlanarProjectionWithMeta : public CPlanarProjection +{ + using base_t = CPlanarProjection; + + inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) + { + if (!camera) return nullptr; + return core::smart_refctd_ptr(new CPlanarProjectionWithMeta(core::smart_refctd_ptr(camera)), core::dont_grab); + } + + std::optional boundProjectionIx = std::nullopt, lastBoundPerspectivePresetProjectionIx = std::nullopt, lastBoundOrthoPresetProjectionIx = std::nullopt; +private: + CPlanarProjectionWithMeta(core::smart_refctd_ptr&& camera) + : base_t::CPlanarProjection(core::smart_refctd_ptr(camera)) {} +}; +using planar_projection_t = CPlanarProjectionWithMeta; + constexpr IGPUImage::SSubresourceRange TripleBufferUsedSubresourceRange = { .aspectMask = IGPUImage::EAF_COLOR_BIT, @@ -681,6 +699,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto cameraIx = jViewport["camera"].get(); auto& planars = m_planarProjections.emplace_back() = planar_projection_t::create(smart_refctd_ptr(cameras[cameraIx])); + planars->boundProjectionIx = 0u; // I will assume I bind first by default if (!jViewport.contains("planarControllerSet")) { @@ -1305,9 +1324,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto& binding = sceneControlBinding[windowIx]; binding.aspectRatio = contentRegionSize.x / contentRegionSize.y; auto* planarViewCameraBound = binding.planar->getCamera(); - assert(planarViewCameraBound); - auto& projection = binding.planar->getPlanarProjections()[binding.boundProjectionIx]; + assert(planarViewCameraBound); + assert(binding.planar->boundProjectionIx.has_value()); + + auto& projection = binding.planar->getPlanarProjections()[binding.planar->boundProjectionIx.value()]; // first 0th texture is for UI texture atlas, then there are our window textures auto fboImguiTextureID = windowIx + 1u; @@ -1520,7 +1541,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication viewMatrix = getCastedMatrix(boundPlanarCamera->getGimbal().getViewMatrix()); modelViewMatrix = concatenateBFollowedByA(viewMatrix, m_model); - auto& projection = binding.planar->getPlanarProjections()[binding.boundProjectionIx]; + assert(binding.planar->boundProjectionIx.has_value()); + auto& projection = binding.planar->getPlanarProjections()[binding.planar->boundProjectionIx.value()]; auto concatMatrix = mul(getCastedMatrix(projection.getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); modelViewProjectionMatrix = mul(concatMatrix, getMatrix3x4As4x4(m_model)); @@ -1544,14 +1566,47 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Text("Active Planar Ix: %s", activePlanarIxString.c_str()); auto& active = sceneControlBinding[activePlanarIx]; - const auto& params = active.planar->getPlanarProjections()[active.boundProjectionIx].getParameters(); - auto selectedProjectionType = params.m_type; + assert(active.planar->boundProjectionIx.has_value()); + + auto requestPlanarCacheInit = [&](std::optional& optionalPresetIx, IPlanarProjection::CProjection::ProjectionType requestedType) -> void + { + if (not optionalPresetIx.has_value()) + { + const auto& projections = active.planar->getPlanarProjections(); + + for (uint32_t i = 0u; i < projections.size(); ++i) + { + const auto& params = projections[i].getParameters(); + if (params.m_type == requestedType) + { + optionalPresetIx = i; + break; + } + } + } + + assert(optionalPresetIx.has_value()); + }; + + requestPlanarCacheInit(active.planar->lastBoundPerspectivePresetProjectionIx, IPlanarProjection::CProjection::Perspective); + requestPlanarCacheInit(active.planar->lastBoundOrthoPresetProjectionIx, IPlanarProjection::CProjection::Orthographic); + + auto selectedProjectionType = active.planar->getPlanarProjections()[active.planar->boundProjectionIx.value()].getParameters().m_type; { const char* labels[] = { "Perspective", "Orthographic" }; - int type = static_cast(params.m_type); + int type = static_cast(selectedProjectionType); if (ImGui::Combo("Projection Type", &type, labels, IM_ARRAYSIZE(labels))) + { selectedProjectionType = static_cast(type); + + switch (selectedProjectionType) + { + case IPlanarProjection::CProjection::Perspective: active.planar->boundProjectionIx = active.planar->lastBoundPerspectivePresetProjectionIx.value(); break; + case IPlanarProjection::CProjection::Orthographic: active.planar->boundProjectionIx = active.planar->lastBoundOrthoPresetProjectionIx.value(); break; + default: active.planar->boundProjectionIx = std::nullopt; assert(false); break; + } + } } auto getPresetName = [&](auto ix) -> std::string @@ -1564,7 +1619,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } }; - if (ImGui::BeginCombo("Projection Preset", getPresetName(active.boundProjectionIx).c_str())) + if (ImGui::BeginCombo("Projection Preset", getPresetName(active.planar->boundProjectionIx.value()).c_str())) { auto& projections = active.planar->getPlanarProjections(); @@ -1576,10 +1631,19 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (params.m_type != selectedProjectionType) continue; - bool isSelected = (i == active.boundProjectionIx); + bool isSelected = (i == active.planar->boundProjectionIx.value()); if (ImGui::Selectable(getPresetName(i).c_str(), isSelected)) - active.boundProjectionIx = i; + { + active.planar->boundProjectionIx = i; + + switch (selectedProjectionType) + { + case IPlanarProjection::CProjection::Perspective: active.planar->lastBoundPerspectivePresetProjectionIx = active.planar->boundProjectionIx.value(); break; + case IPlanarProjection::CProjection::Orthographic: active.planar->lastBoundOrthoPresetProjectionIx = active.planar->boundProjectionIx.value(); break; + default: assert(false); break; + } + } if (isSelected) ImGui::SetItemDefaultFocus(); @@ -1588,7 +1652,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } auto* const boundCamera = active.planar->getCamera(); - auto& boundProjection = active.planar->getPlanarProjections()[active.boundProjectionIx]; + auto& boundProjection = active.planar->getPlanarProjections()[active.planar->boundProjectionIx.value()]; assert(not boundProjection.isProjectionSingular()); auto updateParameters = boundProjection.getParameters(); @@ -1929,8 +1993,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication nbl::hlsl::float32_t3x4 m_model = nbl::hlsl::float32_t3x4(1.f); nbl::core::smart_refctd_ptr boundCameraToManipulate; - using planar_projections_range_t = std::vector; - using planar_projection_t = CPlanarProjection; std::vector> m_planarProjections; bool enableActiveCameraMovement = false; @@ -1939,7 +2001,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication { nbl::core::smart_refctd_ptr scene; nbl::core::smart_refctd_ptr planar; - uint32_t boundProjectionIx = 0u; bool allowGizmoAxesToFlip = false; float aspectRatio = 16.f / 9.f; }; diff --git a/common/include/camera/CPlanarProjection.hpp b/common/include/camera/CPlanarProjection.hpp index f726ea959..a85cc39de 100644 --- a/common/include/camera/CPlanarProjection.hpp +++ b/common/include/camera/CPlanarProjection.hpp @@ -30,7 +30,7 @@ namespace nbl::hlsl return m_projections; } - private: + protected: CPlanarProjection(core::smart_refctd_ptr&& camera) : IPlanarProjection(core::smart_refctd_ptr(camera)) {} From a5e77b05f7b4d11bd8defe62d5b7f76cbe28bfbe Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Thu, 19 Dec 2024 16:37:01 +0100 Subject: [PATCH 65/84] fix gizmo ID stack usage --- 61_UI/main.cpp | 120 ++++++------------------------------------------- 1 file changed, 13 insertions(+), 107 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 1dc69874c..cbff869cc 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1119,81 +1119,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuiIO& io = ImGui::GetIO(); ImGuizmo::BeginFrame(); - - //auto projectionMatrices = projections->getLinearProjections(); - { - - /* - TODO: update it - - auto mutableRange = smart_refctd_ptr_static_cast(projections)->getLinearProjections(); - for (uint32_t i = 0u; i < mutableRange.size(); ++i) - { - auto projection = mutableRange.begin() + i; - - if (isPerspective[i]) - { - if (isLH[i]) - projection->setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov[i]), aspectRatio[i], zNear[i], zFar[i])); - else - projection->setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov[i]), aspectRatio[i], zNear[i], zFar[i])); - } - else - { - float viewHeight = viewWidth[i] * invAspectRatio[i]; - - if (isLH[i]) - projection->setProjectionMatrix(buildProjectionMatrixOrthoLH(viewWidth[i], viewHeight, zNear[i], zFar[i])); - else - projection->setProjectionMatrix(buildProjectionMatrixOrthoRH(viewWidth[i], viewHeight, zNear[i], zFar[i])); - } - } - */ - - - } - - /* - * ImGuizmo expects view & perspective matrix to be column major both with 4x4 layout - * and Nabla uses row major matricies - 3x4 matrix for view & 4x4 for projection - - - VIEW: - - ImGuizmo - - | X[0] Y[0] Z[0] 0.0f | - | X[1] Y[1] Z[1] 0.0f | - | X[2] Y[2] Z[2] 0.0f | - | -Dot(X, eye) -Dot(Y, eye) -Dot(Z, eye) 1.0f | - - Nabla - - | X[0] X[1] X[2] -Dot(X, eye) | - | Y[0] Y[1] Y[2] -Dot(Y, eye) | - | Z[0] Z[1] Z[2] -Dot(Z, eye) | - - = transpose(nbl::core::matrix4SIMD()) - - - PERSPECTIVE [PROJECTION CASE]: - - ImGuizmo - - | (temp / temp2) (0.0) (0.0) (0.0) | - | (0.0) (temp / temp3) (0.0) (0.0) | - | ((right + left) / temp2) ((top + bottom) / temp3) ((-zfar - znear) / temp4) (-1.0f) | - | (0.0) (0.0) ((-temp * zfar) / temp4) (0.0) | - - Nabla - - | w (0.0) (0.0) (0.0) | - | (0.0) -h (0.0) (0.0) | - | (0.0) (0.0) (-zFar/(zFar-zNear)) (-zNear*zFar/(zFar-zNear)) | - | (0.0) (0.0) (-1.0) (0.0) | - - = transpose() - - * - */ // TODO: need to inspect where I'm wrong, workaround auto gimbalToImguizmoTRS = [&](const float32_t3x4& nblGimbalTrs) -> float32_t4x4 @@ -1208,31 +1133,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication return trs; }; - - - ImGuizmo::SetID(0u); - - /* - - imguizmoM16InOut.view[0u] = getCastedMatrix(transpose(getMatrix3x4As4x4(firstcamera->getGimbal().getViewMatrix()))); - imguizmoM16InOut.view[1u] = getCastedMatrix(transpose(getMatrix3x4As4x4(secondcamera->getGimbal().getViewMatrix()))); - - for (uint32_t i = 0u; i < ProjectionsCount; ++i) - imguizmoM16InOut.projection[i] = getCastedMatrix(transpose(projectionMatrices[i].getProjectionMatrix())); - - const auto secondCameraGimbalModel = secondcamera->getGimbal()(); - - // we will transform a scene object's model - imguizmoM16InOut.inModel[0] = transpose(getMatrix3x4As4x4(m_model)); - // and second camera's model too - imguizmoM16InOut.inModel[1] = gimbalToImguizmoTRS(getCastedMatrix(secondCameraGimbalModel)); - - */ - { - ImGuizmo::AllowAxisFlip(false); - ImGuizmo::Enable(false); - SImResourceInfo info; info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; @@ -1310,6 +1211,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication } //////////////////////////////////////////////////////////////////////////// + if(enableActiveCameraMovement) + ImGuizmo::Enable(false); + else + ImGuizmo::Enable(true); + + size_t gizmoIx = {}; for (uint32_t windowIx = 0; windowIx < sceneControlBinding.size(); ++windowIx) { // setup imgui window @@ -1344,18 +1251,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication else ImGuizmo::SetOrthographic(false); - // set imguizmo draw lists - ImGuizmo::SetDrawlist(); - ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); // I will assume we need to focus a window to start manipulating objects from it if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) activePlanarIx = windowIx; - - if (windowIx == activePlanarIx && not enableActiveCameraMovement) - ImGuizmo::Enable(true); + + ImGuizmo::SetDrawlist(); ImGuizmoPlanarM16InOut imguizmoPlanar; imguizmoPlanar.view = getCastedMatrix(transpose(getMatrix3x4As4x4(planarViewCameraBound->getGimbal().getViewMatrix()))); @@ -1366,13 +1269,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication for (uint32_t modelIx = 0; modelIx < 1u + m_planarProjections.size(); modelIx++) { + ImGuizmo::PushID(gizmoIx); ++gizmoIx; + const bool isCameraGimbalTarget = modelIx; // I assume scene demo model is 0th ix then cameras ICamera* const targetGimbalManipulationCamera = isCameraGimbalTarget ? m_planarProjections[modelIx - 1u]->getCamera() : nullptr; const bool discard = isCameraGimbalTarget && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; // discard WiP stuff // if we try to manipulate a camera which appears to be the same camera we see scene from then obvsly it doesn't make sense to manipulate its gizmo so we skip it if (targetGimbalManipulationCamera == planarViewCameraBound) + { + ImGuizmo::PopID(); continue; + } ImGuizmoModelM16InOut imguizmoModel; @@ -1385,8 +1293,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication imguizmoModel.inTRS = transpose(getMatrix3x4As4x4(m_model)); imguizmoModel.outTRS = imguizmoModel.inTRS; - - ImGuizmo::PushID(modelIx); { const bool success = ImGuizmo::Manipulate(&imguizmoPlanar.view[0][0], &imguizmoPlanar.projection[0][0], mCurrentGizmoOperation, mCurrentGizmoMode, &imguizmoModel.outTRS[0][0], &imguizmoModel.outDeltaTRS[0][0], useSnap ? &snap[0] : nullptr); From e89b1c1033fe6c68db4c9b2b0af2aa5791c048ff Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 20 Dec 2024 10:42:06 +0100 Subject: [PATCH 66/84] set render window constraints to avoid aspect ratio glitches & division by 0, fix object-to-gizmo visual alignment in a window & camera gizmo manipulation controller bugs after API updates --- 61_UI/main.cpp | 97 +++++++++++++++++----------- common/include/camera/CFPSCamera.hpp | 9 ++- 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index cbff869cc..091af65e9 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -526,7 +526,11 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto orientation = [&]() { auto jret = jCamera["orientation"].get>(); - return glm::quat(jret[0], jret[1], jret[2], jret[3]); + + // order important for glm::quat, + // the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) + // but memory layout (and json) is x,y,z,w + return glm::quat(jret[3], jret[0], jret[1], jret[2]); }(); cameras.emplace_back() = make_smart_refctd_ptr(position, orientation); @@ -1172,7 +1176,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { static std::vector virtualEvents(0x45); - uint32_t vCount; + uint32_t vCount = {}; boundCameraToManipulate->beginInputProcessing(m_nextPresentationTimestamp); { @@ -1192,15 +1196,27 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto* fps = dynamic_cast(boundCameraToManipulate.get()); + float pmSpeed = 1.f; + float prSpeed = 1.f; + if (fps) { - smart_refctd_ptr_static_cast(boundCameraToManipulate)->setMoveSpeedScale(1); - smart_refctd_ptr_static_cast(boundCameraToManipulate)->setRotationSpeedScale(1); + float pmSpeed = fps->getMoveSpeedScale(); + float prSpeed = fps->getRotationSpeedScale(); + + fps->setMoveSpeedScale(1); + fps->setRotationSpeedScale(1); } // NOTE: generated events from ImGuizmo controller are always in world space! if (vCount) boundCameraToManipulate->manipulate({ virtualEvents.data(), vCount }, ICamera::World); + + if (fps) + { + fps->setMoveSpeedScale(pmSpeed); + fps->setRotationSpeedScale(prSpeed); + } } } else @@ -1217,15 +1233,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::Enable(true); size_t gizmoIx = {}; + size_t manipulationCounter = {}; for (uint32_t windowIx = 0; windowIx < sceneControlBinding.size(); ++windowIx) { // setup imgui window ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); ImGui::SetNextWindowPos(ImVec2(400, 20 + windowIx * 420), ImGuiCond_Appearing); + ImGui::SetNextWindowSizeConstraints(ImVec2(0x45, 0x45), ImVec2(7680, 4320)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); const std::string ident = "Render Window \"" + std::to_string(windowIx) + "\""; ImGui::Begin(ident.data(), 0, ImGuiWindowFlags_NoMove); - const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetCursorScreenPos(), cursorPos = ImGui::GetWindowPos(); + const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetWindowPos(), cursorPos = ImGui::GetCursorScreenPos(); // setup bound entities for the window like camera & projections auto& binding = sceneControlBinding[windowIx]; @@ -1260,6 +1279,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::SetDrawlist(); + // we render a scene from view of a camera bound to planar window ImGuizmoPlanarM16InOut imguizmoPlanar; imguizmoPlanar.view = getCastedMatrix(transpose(getMatrix3x4As4x4(planarViewCameraBound->getGimbal().getViewMatrix()))); imguizmoPlanar.projection = getCastedMatrix(transpose(projection.getProjectionMatrix())); @@ -1271,9 +1291,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication { ImGuizmo::PushID(gizmoIx); ++gizmoIx; - const bool isCameraGimbalTarget = modelIx; // I assume scene demo model is 0th ix then cameras + const bool isCameraGimbalTarget = modelIx; // I assume scene demo model is 0th ix, left are cameras ICamera* const targetGimbalManipulationCamera = isCameraGimbalTarget ? m_planarProjections[modelIx - 1u]->getCamera() : nullptr; - const bool discard = isCameraGimbalTarget && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; // discard WiP stuff + bool discard = isCameraGimbalTarget && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; // discard WiP stuff // if we try to manipulate a camera which appears to be the same camera we see scene from then obvsly it doesn't make sense to manipulate its gizmo so we skip it if (targetGimbalManipulationCamera == planarViewCameraBound) @@ -1298,6 +1318,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (success) { + ++manipulationCounter; + discard &= manipulationCounter > 1; + // TMP WIP, our imguizmo controller doesnt support rotation & scale yet and its because // - there are numerical issues with imguizmo decompose/recompose TRS (and the author also says it) // - in rotate mode delta TRS matrix contains translate part (how? no idea, maybe imguizmo bug) and it glitches everything @@ -1314,48 +1337,45 @@ class UISampleApp final : public examples::SimpleWindowedApplication { static std::vector virtualEvents(0x45); + uint32_t vCount = {}; - if (ImGuizmo::IsUsingAny()) + targetGimbalManipulationCamera->beginInputProcessing(m_nextPresentationTimestamp); { - uint32_t vCount; + targetGimbalManipulationCamera->process(nullptr, vCount); - targetGimbalManipulationCamera->beginInputProcessing(m_nextPresentationTimestamp); - { - targetGimbalManipulationCamera->process(nullptr, vCount); + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); - if (virtualEvents.size() < vCount) - virtualEvents.resize(vCount); - - IGimbalController::SUpdateParameters params; - params.imguizmoEvents = { { imguizmoModel.outTRS } }; - targetGimbalManipulationCamera->process(virtualEvents.data(), vCount, params); - } - targetGimbalManipulationCamera->endInputProcessing(); + IGimbalController::SUpdateParameters params; + params.imguizmoEvents = { { imguizmoModel.outDeltaTRS } }; + targetGimbalManipulationCamera->process(virtualEvents.data(), vCount, params); + } + targetGimbalManipulationCamera->endInputProcessing(); - auto* fps = dynamic_cast(targetGimbalManipulationCamera); + auto* fps = dynamic_cast(targetGimbalManipulationCamera); - float pMoveSpeed = 1.f, pRotationSpeed = 1.f; + float pMoveSpeed = 1.f, pRotationSpeed = 1.f; - if (fps) - { - pMoveSpeed = fps->getMoveSpeedScale(); - pRotationSpeed = fps->getRotationSpeedScale(); + if (fps) + { + pMoveSpeed = fps->getMoveSpeedScale(); + pRotationSpeed = fps->getRotationSpeedScale(); - // I start to think controller should be able to set sensitivity to scale magnitudes of generated events - // in order for camera to not keep any magnitude scalars like move or rotation speed scales + // I start to think controller should be able to set sensitivity to scale magnitudes of generated events + // in order for camera to not keep any magnitude scalars like move or rotation speed scales - fps->setMoveSpeedScale(1); - fps->setRotationSpeedScale(1); - } + fps->setMoveSpeedScale(1); + fps->setRotationSpeedScale(1); + } - // NOTE: generated events from ImGuizmo controller are always in world space! + // NOTE: generated events from ImGuizmo controller are always in world space! + if (vCount) targetGimbalManipulationCamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); - if (fps) - { - fps->setMoveSpeedScale(pMoveSpeed); - fps->setRotationSpeedScale(pRotationSpeed); - } + if (fps) + { + fps->setMoveSpeedScale(pMoveSpeed); + fps->setRotationSpeedScale(pRotationSpeed); } } } @@ -1405,6 +1425,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::End(); ImGui::PopStyleColor(1); } + assert(manipulationCounter <= 1u); } // render selected camera view onto full screen else @@ -1415,7 +1436,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetNextWindowSize(io.DisplaySize); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); - const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetCursorScreenPos(), cursorPos = ImGui::GetWindowPos(); + const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetWindowPos(), cursorPos = ImGui::GetCursorScreenPos(); ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); diff --git a/common/include/camera/CFPSCamera.hpp b/common/include/camera/CFPSCamera.hpp index 6efc91f95..3889e5921 100644 --- a/common/include/camera/CFPSCamera.hpp +++ b/common/include/camera/CFPSCamera.hpp @@ -17,7 +17,12 @@ class CFPSCamera final : public ICamera using base_t = ICamera; CFPSCamera(const float64_t3& position, const glm::quat& orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) - : base_t(), m_gimbal({ .position = position, .orientation = orientation }) {} + : base_t(), m_gimbal({ .position = position, .orientation = orientation }) + { + updateKeyboardMapping([](auto& map) { map = m_keyboard_to_virtual_events_preset; }); + updateMouseMapping([](auto& map) { map = m_mouse_to_virtual_events_preset; }); + updateImguizmoMapping([](auto& map) { map = m_imguizmo_to_virtual_events_preset; }); + } ~CFPSCamera() = default; const base_t::keyboard_to_virtual_events_t getKeyboardMappingPreset() const override { return m_keyboard_to_virtual_events_preset; } @@ -101,7 +106,7 @@ class CFPSCamera final : public ICamera // (***) TODO: I need to think whether a camera should own this or controllers should be able // to set sensitivity to scale magnitudes of generated events we put into manipulate method - double m_moveSpeedScale = 1, m_rotationSpeedScale = 1; + double m_moveSpeedScale = 0.01, m_rotationSpeedScale = 0.003; static inline constexpr auto AllowedLocalVirtualEvents = CVirtualGimbalEvent::Translate | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; static inline constexpr auto AllowedWorldVirtualEvents = AllowedLocalVirtualEvents; From 237dd4394e8a4268ee0290cd597e6f6d7318abde Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Fri, 20 Dec 2024 16:50:23 +0100 Subject: [PATCH 67/84] control active render window by switching between bound planar & projection presets, add third camera to the scene - a few minor fixes need to be applied and we start adding new type of camera (Maya incoming) --- 61_UI/cameras.json | 26 ++++++- 61_UI/main.cpp | 185 +++++++++++++++++++++++++++------------------ 2 files changed, 138 insertions(+), 73 deletions(-) diff --git a/61_UI/cameras.json b/61_UI/cameras.json index bd4d7aaa9..105a3ec7e 100644 --- a/61_UI/cameras.json +++ b/61_UI/cameras.json @@ -3,12 +3,17 @@ { "type": "FPS", "position": [-2.238, 1.438, -1.558], - "orientation": [0.253, 0.368, -0.105, 0.888] + "orientation": [0.204, 0.385, -0.088, 0.896] }, { "type": "FPS", "position": [-2.017, 0.386, 0.684], "orientation": [0.047, 0.830, -0.072, 0.55] + }, + { + "type": "FPS", + "position": [2.116, 0.826, 1.152], + "orientation": [0.095, -0.835, 0.152, 0.521] } ], "projections": [ @@ -65,6 +70,25 @@ } } ] + }, + { + "camera": 2, + "planarControllerSet": [ + { + "projection": 0, + "controllers": { + "keyboard": 0, + "mouse": 0 + } + }, + { + "projection": 1, + "controllers": { + "keyboard": 1, + "mouse": 0 + } + } + ] } ], "controllers": { diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 091af65e9..e0031333b 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -12,22 +12,7 @@ using json = nlohmann::json; #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING using planar_projections_range_t = std::vector; -struct CPlanarProjectionWithMeta : public CPlanarProjection -{ - using base_t = CPlanarProjection; - - inline static core::smart_refctd_ptr create(core::smart_refctd_ptr&& camera) - { - if (!camera) return nullptr; - return core::smart_refctd_ptr(new CPlanarProjectionWithMeta(core::smart_refctd_ptr(camera)), core::dont_grab); - } - - std::optional boundProjectionIx = std::nullopt, lastBoundPerspectivePresetProjectionIx = std::nullopt, lastBoundOrthoPresetProjectionIx = std::nullopt; -private: - CPlanarProjectionWithMeta(core::smart_refctd_ptr&& camera) - : base_t::CPlanarProjection(core::smart_refctd_ptr(camera)) {} -}; -using planar_projection_t = CPlanarProjectionWithMeta; +using planar_projection_t = CPlanarProjection; constexpr IGPUImage::SSubresourceRange TripleBufferUsedSubresourceRange = { @@ -703,7 +688,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto cameraIx = jViewport["camera"].get(); auto& planars = m_planarProjections.emplace_back() = planar_projection_t::create(smart_refctd_ptr(cameras[cameraIx])); - planars->boundProjectionIx = 0u; // I will assume I bind first by default if (!jViewport.contains("planarControllerSet")) { @@ -743,15 +727,22 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (m_planarProjections.size() < sceneControlBinding.size()) { - // TODO, temporary assuming it + // TODO, temporary assuming it, I'm not going to implement each possible case now std::cerr << "Expected at least " << std::to_string(sceneControlBinding.size()) << " planars!" << std::endl; return false; } for (uint32_t i = 0u; i < sceneControlBinding.size(); ++i) { - auto& planar = sceneControlBinding[i].planar; - planar = smart_refctd_ptr(m_planarProjections[i]); + auto& binding = sceneControlBinding[i]; + + const auto& projections = m_planarProjections[binding.activePlanarIx = 0]->getPlanarProjections(); + binding.pickDefaultProjections(projections); + + if (i) + binding.boundProjectionIx = binding.lastBoundOrthoPresetProjectionIx.value(); + else + binding.boundProjectionIx = binding.lastBoundPerspectivePresetProjectionIx.value(); } } @@ -1003,8 +994,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication { // wait for picked planar only { - .semaphore = sceneControlBinding[activePlanarIx].scene->semaphore.progress.get(), - .value = sceneControlBinding[activePlanarIx].scene->semaphore.finishedValue + .semaphore = sceneControlBinding[activeRenderWindowIx].scene->semaphore.progress.get(), + .value = sceneControlBinding[activeRenderWindowIx].scene->semaphore.finishedValue } } )); @@ -1096,7 +1087,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (enableActiveCameraMovement) { - auto* camera = m_planarProjections[activePlanarIx]->getCamera(); + auto* camera = m_planarProjections[sceneControlBinding[activeRenderWindowIx].activePlanarIx]->getCamera(); static std::vector virtualEvents(0x45); uint32_t vCount; @@ -1111,7 +1102,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication camera->process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); } camera->endInputProcessing(); - camera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); + + if(vCount) + camera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); } m_ui.manager->update(params); @@ -1248,13 +1241,16 @@ class UISampleApp final : public examples::SimpleWindowedApplication // setup bound entities for the window like camera & projections auto& binding = sceneControlBinding[windowIx]; + auto& planarBound = m_planarProjections[binding.activePlanarIx]; + assert(planarBound); + binding.aspectRatio = contentRegionSize.x / contentRegionSize.y; - auto* planarViewCameraBound = binding.planar->getCamera(); + auto* planarViewCameraBound = planarBound->getCamera(); assert(planarViewCameraBound); - assert(binding.planar->boundProjectionIx.has_value()); + assert(binding.boundProjectionIx.has_value()); - auto& projection = binding.planar->getPlanarProjections()[binding.planar->boundProjectionIx.value()]; + auto& projection = planarBound->getPlanarProjections()[binding.boundProjectionIx.value()]; // first 0th texture is for UI texture atlas, then there are our window textures auto fboImguiTextureID = windowIx + 1u; @@ -1270,14 +1266,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication else ImGuizmo::SetOrthographic(false); + ImGuizmo::SetDrawlist(); ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); // I will assume we need to focus a window to start manipulating objects from it if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) - activePlanarIx = windowIx; - - ImGuizmo::SetDrawlist(); + activeRenderWindowIx = windowIx; // we render a scene from view of a camera bound to planar window ImGuizmoPlanarM16InOut imguizmoPlanar; @@ -1287,6 +1282,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates imguizmoPlanar.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ + static constexpr float identityMatrix[] = + { + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f + }; + + if(binding.enableDebugGridDraw) + ImGuizmo::DrawGrid(&imguizmoPlanar.view[0][0], &imguizmoPlanar.projection[0][0], identityMatrix, 100.f); + for (uint32_t modelIx = 0; modelIx < 1u + m_planarProjections.size(); modelIx++) { ImGuizmo::PushID(gizmoIx); ++gizmoIx; @@ -1430,7 +1436,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication // render selected camera view onto full screen else { - info.textureID = 1u + activePlanarIx; + info.textureID = 1u + activeRenderWindowIx; ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(io.DisplaySize); @@ -1457,7 +1463,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto& binding = sceneControlBinding[i]; auto& hook = binding.scene->object; - auto* boundPlanarCamera = binding.planar->getCamera(); + auto& planarBound = m_planarProjections[binding.activePlanarIx]; + assert(planarBound); + auto* boundPlanarCamera = planarBound->getCamera(); hook.meta.type = type; hook.meta.name = meta.name; @@ -1468,8 +1476,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication viewMatrix = getCastedMatrix(boundPlanarCamera->getGimbal().getViewMatrix()); modelViewMatrix = concatenateBFollowedByA(viewMatrix, m_model); - assert(binding.planar->boundProjectionIx.has_value()); - auto& projection = binding.planar->getPlanarProjections()[binding.planar->boundProjectionIx.value()]; + assert(binding.boundProjectionIx.has_value()); + auto& projection = planarBound->getPlanarProjections()[binding.boundProjectionIx.value()]; auto concatMatrix = mul(getCastedMatrix(projection.getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); modelViewProjectionMatrix = mul(concatMatrix, getMatrix3x4As4x4(m_model)); @@ -1484,41 +1492,44 @@ class UISampleApp final : public examples::SimpleWindowedApplication // Planars { ImGui::Begin("Projection"); - ImGui::Checkbox("Window mode##useWindow", &useWindow); - ImGui::Separator(); - const auto activePlanarIxString = std::to_string(activePlanarIx); - ImGui::Text("Active Planar Ix: %s", activePlanarIxString.c_str()); - - auto& active = sceneControlBinding[activePlanarIx]; - assert(active.planar->boundProjectionIx.has_value()); + const auto activeRenderWindowIxString = std::to_string(activeRenderWindowIx); - auto requestPlanarCacheInit = [&](std::optional& optionalPresetIx, IPlanarProjection::CProjection::ProjectionType requestedType) -> void + ImGui::Text("Active Render Window: %s", activeRenderWindowIxString.c_str()); { - if (not optionalPresetIx.has_value()) - { - const auto& projections = active.planar->getPlanarProjections(); + const size_t planarsCount = m_planarProjections.size(); + assert(planarsCount); - for (uint32_t i = 0u; i < projections.size(); ++i) - { - const auto& params = projections[i].getParameters(); - if (params.m_type == requestedType) - { - optionalPresetIx = i; - break; - } - } + std::vector sbels(planarsCount); + for (size_t i = 0; i < planarsCount; ++i) + sbels[i] = "Planar " + std::to_string(i); + + std::vector labels(planarsCount); + for (size_t i = 0; i < planarsCount; ++i) + labels[i] = sbels[i].c_str(); + + auto& active = sceneControlBinding[activeRenderWindowIx]; + int currentPlanarIx = static_cast(active.activePlanarIx); + if (ImGui::Combo("Active Planar", ¤tPlanarIx, labels.data(), static_cast(labels.size()))) + { + active.activePlanarIx = static_cast(currentPlanarIx); + active.pickDefaultProjections(m_planarProjections[active.activePlanarIx]->getPlanarProjections()); } + } - assert(optionalPresetIx.has_value()); - }; + auto& active = sceneControlBinding[activeRenderWindowIx]; + + assert(active.boundProjectionIx.has_value()); + assert(active.lastBoundPerspectivePresetProjectionIx.has_value()); + assert(active.lastBoundOrthoPresetProjectionIx.has_value()); - requestPlanarCacheInit(active.planar->lastBoundPerspectivePresetProjectionIx, IPlanarProjection::CProjection::Perspective); - requestPlanarCacheInit(active.planar->lastBoundOrthoPresetProjectionIx, IPlanarProjection::CProjection::Orthographic); + const auto activePlanarIxString = std::to_string(active.activePlanarIx); + auto& planarBound = m_planarProjections[active.activePlanarIx]; + assert(planarBound); - auto selectedProjectionType = active.planar->getPlanarProjections()[active.planar->boundProjectionIx.value()].getParameters().m_type; + auto selectedProjectionType = planarBound->getPlanarProjections()[active.boundProjectionIx.value()].getParameters().m_type; { const char* labels[] = { "Perspective", "Orthographic" }; int type = static_cast(selectedProjectionType); @@ -1529,9 +1540,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication switch (selectedProjectionType) { - case IPlanarProjection::CProjection::Perspective: active.planar->boundProjectionIx = active.planar->lastBoundPerspectivePresetProjectionIx.value(); break; - case IPlanarProjection::CProjection::Orthographic: active.planar->boundProjectionIx = active.planar->lastBoundOrthoPresetProjectionIx.value(); break; - default: active.planar->boundProjectionIx = std::nullopt; assert(false); break; + case IPlanarProjection::CProjection::Perspective: active.boundProjectionIx = active.lastBoundPerspectivePresetProjectionIx.value(); break; + case IPlanarProjection::CProjection::Orthographic: active.boundProjectionIx = active.lastBoundOrthoPresetProjectionIx.value(); break; + default: active.boundProjectionIx = std::nullopt; assert(false); break; } } } @@ -1546,9 +1557,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication } }; - if (ImGui::BeginCombo("Projection Preset", getPresetName(active.planar->boundProjectionIx.value()).c_str())) + if (ImGui::BeginCombo("Projection Preset", getPresetName(active.boundProjectionIx.value()).c_str())) { - auto& projections = active.planar->getPlanarProjections(); + auto& projections = planarBound->getPlanarProjections(); for (uint32_t i = 0; i < projections.size(); ++i) { @@ -1558,16 +1569,16 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (params.m_type != selectedProjectionType) continue; - bool isSelected = (i == active.planar->boundProjectionIx.value()); + bool isSelected = (i == active.boundProjectionIx.value()); if (ImGui::Selectable(getPresetName(i).c_str(), isSelected)) { - active.planar->boundProjectionIx = i; + active.boundProjectionIx = i; switch (selectedProjectionType) { - case IPlanarProjection::CProjection::Perspective: active.planar->lastBoundPerspectivePresetProjectionIx = active.planar->boundProjectionIx.value(); break; - case IPlanarProjection::CProjection::Orthographic: active.planar->lastBoundOrthoPresetProjectionIx = active.planar->boundProjectionIx.value(); break; + case IPlanarProjection::CProjection::Perspective: active.lastBoundPerspectivePresetProjectionIx = active.boundProjectionIx.value(); break; + case IPlanarProjection::CProjection::Orthographic: active.lastBoundOrthoPresetProjectionIx = active.boundProjectionIx.value(); break; default: assert(false); break; } } @@ -1578,8 +1589,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::EndCombo(); } - auto* const boundCamera = active.planar->getCamera(); - auto& boundProjection = active.planar->getPlanarProjections()[active.planar->boundProjectionIx.value()]; + auto* const boundCamera = planarBound->getCamera(); + auto& boundProjection = planarBound->getPlanarProjections()[active.boundProjectionIx.value()]; assert(not boundProjection.isProjectionSingular()); auto updateParameters = boundProjection.getParameters(); @@ -1588,6 +1599,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (useWindow) ImGui::Checkbox("Allow axes to flip##allowAxesToFlip", &active.allowGizmoAxesToFlip); + ImGui::Checkbox("Draw debug grid##drawDebugGrid", &active.enableDebugGridDraw); + if (ImGui::RadioButton("LH", boundProjection.isProjectionLeftHanded().value())) isLH = true; @@ -1918,6 +1931,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication // one model object in the world, testing multiuple cameraz for which view is rendered to separate frame buffers (so what they see) with new controller API including imguizmo nbl::hlsl::float32_t3x4 m_model = nbl::hlsl::float32_t3x4(1.f); + + // if we had working IObjectTransform or something similar then it would be it instead, it is "last manipulated object" I need for TRS editor nbl::core::smart_refctd_ptr boundCameraToManipulate; std::vector> m_planarProjections; @@ -1927,14 +1942,40 @@ class UISampleApp final : public examples::SimpleWindowedApplication struct SceneControlBinding { nbl::core::smart_refctd_ptr scene; - nbl::core::smart_refctd_ptr planar; + + uint32_t activePlanarIx = 0u; bool allowGizmoAxesToFlip = false; + bool enableDebugGridDraw = true; float aspectRatio = 16.f / 9.f; + + std::optional boundProjectionIx = std::nullopt, lastBoundPerspectivePresetProjectionIx = std::nullopt, lastBoundOrthoPresetProjectionIx = std::nullopt; + + inline void pickDefaultProjections(const planar_projections_range_t& projections) + { + auto init = [&](std::optional& presetix, IPlanarProjection::CProjection::ProjectionType requestedType) -> void + { + for (uint32_t i = 0u; i < projections.size(); ++i) + { + const auto& params = projections[i].getParameters(); + if (params.m_type == requestedType) + { + presetix = i; + break; + } + } + + assert(presetix.has_value()); + }; + + init(lastBoundPerspectivePresetProjectionIx = std::nullopt, IPlanarProjection::CProjection::Perspective); + init(lastBoundOrthoPresetProjectionIx = std::nullopt, IPlanarProjection::CProjection::Orthographic); + boundProjectionIx = lastBoundPerspectivePresetProjectionIx.value(); + } }; static constexpr inline auto MaxSceneFBOs = 2u; std::array sceneControlBinding; - uint32_t activePlanarIx = 0u; + uint32_t activeRenderWindowIx = 0u; // UI font atlas + viewport FBO color attachment textures constexpr static inline auto TotalUISampleTexturesAmount = 1u + MaxSceneFBOs; From 4f0e0e8c603724217637ce2fdbd8389713764515 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 21 Dec 2024 11:33:58 +0100 Subject: [PATCH 68/84] preserve virtual active event state when required & use bound projection to manipulate active camera in move mode --- 61_UI/include/keysmapping.hpp | 18 ++++++++--- 61_UI/main.cpp | 34 ++++++++++++--------- common/include/camera/IPlanarProjection.hpp | 13 +------- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/61_UI/include/keysmapping.hpp b/61_UI/include/keysmapping.hpp index 83ec22c32..46153660c 100644 --- a/61_UI/include/keysmapping.hpp +++ b/61_UI/include/keysmapping.hpp @@ -3,8 +3,9 @@ #include "common.hpp" -void handleAddMapping(const char* tableID, IGimbalManipulateEncoder* encoder, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) +bool handleAddMapping(const char* tableID, IGimbalManipulateEncoder* encoder, IGimbalManipulateEncoder::EncoderType activeController, CVirtualGimbalEvent::VirtualEventType& selectedEventType, ui::E_KEY_CODE& newKey, ui::E_MOUSE_CODE& newMouseCode, bool& addMode) { + bool anyMapUpdated = false; ImGui::BeginTable(tableID, 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchSame); ImGui::TableSetupColumn("Virtual Event", ImGuiTableColumnFlags_WidthStretch, 0.33f); ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthStretch, 0.33f); @@ -64,6 +65,7 @@ void handleAddMapping(const char* tableID, IGimbalManipulateEncoder* encoder, IG ImGui::TableSetColumnIndex(2); if (ImGui::Button("Confirm Add", ImVec2(100, 30))) { + anyMapUpdated |= true; if (activeController == IGimbalManipulateEncoder::Keyboard) encoder->updateKeyboardMapping([&](auto& keys) { keys[newKey] = selectedEventType; }); else @@ -72,11 +74,15 @@ void handleAddMapping(const char* tableID, IGimbalManipulateEncoder* encoder, IG } ImGui::EndTable(); + + return anyMapUpdated; } -void displayKeyMappingsAndVirtualStatesInline(IGimbalManipulateEncoder* encoder, bool spawnWindow = false) +bool displayKeyMappingsAndVirtualStatesInline(IGimbalManipulateEncoder* encoder, bool spawnWindow = false) { - if (!encoder) return; + bool anyMapUpdated = false; + + if (!encoder) return anyMapUpdated; struct MappingState { @@ -143,6 +149,7 @@ void displayKeyMappingsAndVirtualStatesInline(IGimbalManipulateEncoder* encoder, ImGui::TableSetColumnIndex(4); if (ImGui::Button(("Delete##deleteKey" + std::to_string(static_cast(keyboardCode))).c_str())) { + anyMapUpdated |= true; encoder->updateKeyboardMapping([keyboardCode](auto& keys) { keys.erase(keyboardCode); }); break; } @@ -152,7 +159,7 @@ void displayKeyMappingsAndVirtualStatesInline(IGimbalManipulateEncoder* encoder, if (state.addMode) { ImGui::Separator(); - handleAddMapping("AddKeyboardMappingTable", encoder, state.activeController, state.selectedEventType, state.newKey, state.newMouseCode, state.addMode); + anyMapUpdated |= handleAddMapping("AddKeyboardMappingTable", encoder, state.activeController, state.selectedEventType, state.newKey, state.newMouseCode, state.addMode); } ImGui::EndTabItem(); @@ -200,6 +207,7 @@ void displayKeyMappingsAndVirtualStatesInline(IGimbalManipulateEncoder* encoder, ImGui::TableSetColumnIndex(4); if (ImGui::Button(("Delete##deleteMouse" + std::to_string(static_cast(mouseCode))).c_str())) { + anyMapUpdated |= true; encoder->updateMouseMapping([mouseCode](auto& mouse) { mouse.erase(mouseCode); }); break; } @@ -219,6 +227,8 @@ void displayKeyMappingsAndVirtualStatesInline(IGimbalManipulateEncoder* encoder, if (spawnWindow) ImGui::End(); + + return anyMapUpdated; } #endif // __NBL_KEYSMAPPING_H_INCLUDED__ \ No newline at end of file diff --git a/61_UI/main.cpp b/61_UI/main.cpp index e0031333b..796242223 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -732,12 +732,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } + // init render window planar references - we make all render windows start with focus on first + // planar but in a way that first window has the planar's perspective preset bound & second orthographic for (uint32_t i = 0u; i < sceneControlBinding.size(); ++i) { auto& binding = sceneControlBinding[i]; - const auto& projections = m_planarProjections[binding.activePlanarIx = 0]->getPlanarProjections(); - binding.pickDefaultProjections(projections); + auto& planar = m_planarProjections[binding.activePlanarIx = 0]; + binding.pickDefaultProjections(planar->getPlanarProjections()); if (i) binding.boundProjectionIx = binding.lastBoundOrthoPresetProjectionIx.value(); @@ -1087,21 +1089,26 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (enableActiveCameraMovement) { - auto* camera = m_planarProjections[sceneControlBinding[activeRenderWindowIx].activePlanarIx]->getCamera(); + auto& binding = sceneControlBinding[activeRenderWindowIx]; + auto& planar = m_planarProjections[binding.activePlanarIx]; + auto* camera = planar->getCamera(); + + assert(binding.boundProjectionIx.has_value()); + auto& projection = planar->getPlanarProjections()[binding.boundProjectionIx.value()]; static std::vector virtualEvents(0x45); uint32_t vCount; - camera->beginInputProcessing(m_nextPresentationTimestamp); + projection.beginInputProcessing(m_nextPresentationTimestamp); { - camera->process(nullptr, vCount); + projection.process(nullptr, vCount); if (virtualEvents.size() < vCount) virtualEvents.resize(vCount); - camera->process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); + projection.process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); } - camera->endInputProcessing(); + projection.endInputProcessing(); if(vCount) camera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); @@ -1495,6 +1502,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Checkbox("Window mode##useWindow", &useWindow); ImGui::Separator(); + auto& active = sceneControlBinding[activeRenderWindowIx]; const auto activeRenderWindowIxString = std::to_string(activeRenderWindowIx); ImGui::Text("Active Render Window: %s", activeRenderWindowIxString.c_str()); @@ -1510,7 +1518,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication for (size_t i = 0; i < planarsCount; ++i) labels[i] = sbels[i].c_str(); - auto& active = sceneControlBinding[activeRenderWindowIx]; + int currentPlanarIx = static_cast(active.activePlanarIx); if (ImGui::Combo("Active Planar", ¤tPlanarIx, labels.data(), static_cast(labels.size()))) { @@ -1519,8 +1527,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - auto& active = sceneControlBinding[activeRenderWindowIx]; - assert(active.boundProjectionIx.has_value()); assert(active.lastBoundPerspectivePresetProjectionIx.has_value()); assert(active.lastBoundOrthoPresetProjectionIx.has_value()); @@ -1557,6 +1563,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } }; + bool updateBoundVirtualMaps = false; if (ImGui::BeginCombo("Projection Preset", getPresetName(active.boundProjectionIx.value()).c_str())) { auto& projections = planarBound->getPlanarProjections(); @@ -1574,6 +1581,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::Selectable(getPresetName(i).c_str(), isSelected)) { active.boundProjectionIx = i; + updateBoundVirtualMaps |= true; switch (selectedProjectionType) { @@ -1713,16 +1721,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::TreeNodeEx("Virtual Event Mappings", flags)) { - auto* encoder = static_cast(&boundProjection); - displayKeyMappingsAndVirtualStatesInline(encoder); + displayKeyMappingsAndVirtualStatesInline(&boundProjection); ImGui::TreePop(); } ImGui::TreePop(); } - - boundCamera->updateKeyboardMapping([&](auto& map) { map = boundProjection.getKeyboardVirtualEventMap(); }); - boundCamera->updateMouseMapping([&](auto& map) { map = boundProjection.getMouseVirtualEventMap(); }); } ImGui::End(); diff --git a/common/include/camera/IPlanarProjection.hpp b/common/include/camera/IPlanarProjection.hpp index 4ca9a0475..731b9fd5b 100644 --- a/common/include/camera/IPlanarProjection.hpp +++ b/common/include/camera/IPlanarProjection.hpp @@ -9,7 +9,7 @@ namespace nbl::hlsl class IPlanarProjection : public ILinearProjection { public: - struct CProjection : public ILinearProjection::CProjection, public IGimbalManipulateEncoder + struct CProjection : public ILinearProjection::CProjection, public IGimbalController { using base_t = ILinearProjection::CProjection; @@ -88,21 +88,10 @@ class IPlanarProjection : public ILinearProjection base_t::setProjectionMatrix(buildProjectionMatrixOrthoRH(orthoWidth, viewHeight, zNear, zFar)); } - virtual void updateKeyboardMapping(const std::function& mapKeys) override { mapKeys(m_keyboardVirtualEventMap); } - virtual void updateMouseMapping(const std::function& mapKeys) override { mapKeys(m_mouseVirtualEventMap); } - virtual void updateImguizmoMapping(const std::function& mapKeys) override { mapKeys(m_imguizmoVirtualEventMap); } - - virtual const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() const override { return m_keyboardVirtualEventMap; } - virtual const mouse_to_virtual_events_t& getMouseVirtualEventMap() const override { return m_mouseVirtualEventMap; } - virtual const imguizmo_to_virtual_events_t& getImguizmoVirtualEventMap() const override { return m_imguizmoVirtualEventMap; } inline const ProjectionParameters& getParameters() const { return m_parameters; } private: CProjection() = default; ProjectionParameters m_parameters; - - keyboard_to_virtual_events_t m_keyboardVirtualEventMap; - mouse_to_virtual_events_t m_mouseVirtualEventMap; - imguizmo_to_virtual_events_t m_imguizmoVirtualEventMap; }; protected: From ae342b53e80745bef42eec89f078365f7f7739d2 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sat, 21 Dec 2024 13:01:02 +0100 Subject: [PATCH 69/84] correct projection updates --- 61_UI/cameras.json | 6 +-- 61_UI/main.cpp | 27 +++++-------- common/include/camera/IPlanarProjection.hpp | 43 ++++++++++++++------- 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/61_UI/cameras.json b/61_UI/cameras.json index 105a3ec7e..80e09d059 100644 --- a/61_UI/cameras.json +++ b/61_UI/cameras.json @@ -21,15 +21,13 @@ "type": "perspective", "fov": 60.0, "zNear": 0.1, - "zFar": 100.0, - "leftHanded": true + "zFar": 100.0 }, { "type": "orthographic", "orthoWidth": 10.0, "zNear": 0.1, - "zFar": 100.0, - "leftHanded": true + "zFar": 100.0 } ], "viewports": [ diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 796242223..8aad11ab4 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -539,7 +539,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (jProjection.contains("type")) { float zNear, zFar; - bool leftHanded; if (!jProjection.contains("zNear")) { @@ -553,15 +552,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - if (!jProjection.contains("leftHanded")) - { - "Expected \"leftHanded\" keyword for planar projection definition!"; - return false; - } - zNear = jProjection["zNear"].get(); zFar = jProjection["zFar"].get(); - leftHanded = jProjection["leftHanded"].get(); if (jProjection["type"] == "perspective") { @@ -572,7 +564,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } float fov = jProjection["fov"].get(); - projections.emplace_back(IPlanarProjection::CProjection::create(leftHanded, zNear, zFar, fov)); + projections.emplace_back(IPlanarProjection::CProjection::create(zNear, zFar, fov)); } else if (jProjection["type"] == "orthographic") @@ -584,7 +576,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } float orthoWidth = jProjection["orthoWidth"].get(); - projections.emplace_back(IPlanarProjection::CProjection::create(leftHanded, zNear, zFar, orthoWidth)); + projections.emplace_back(IPlanarProjection::CProjection::create(zNear, zFar, orthoWidth)); } else { @@ -1258,6 +1250,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication assert(binding.boundProjectionIx.has_value()); auto& projection = planarBound->getPlanarProjections()[binding.boundProjectionIx.value()]; + projection.update(binding.leftHandedProjection, binding.aspectRatio); // first 0th texture is for UI texture atlas, then there are our window textures auto fboImguiTextureID = windowIx + 1u; @@ -1602,20 +1595,19 @@ class UISampleApp final : public examples::SimpleWindowedApplication assert(not boundProjection.isProjectionSingular()); auto updateParameters = boundProjection.getParameters(); - bool isLH = boundProjection.isProjectionLeftHanded().value(); if (useWindow) ImGui::Checkbox("Allow axes to flip##allowAxesToFlip", &active.allowGizmoAxesToFlip); ImGui::Checkbox("Draw debug grid##drawDebugGrid", &active.enableDebugGridDraw); - if (ImGui::RadioButton("LH", boundProjection.isProjectionLeftHanded().value())) - isLH = true; + if (ImGui::RadioButton("LH", active.leftHandedProjection)) + active.leftHandedProjection = true; ImGui::SameLine(); - if (ImGui::RadioButton("RH", not isLH)) - isLH = false; + if (ImGui::RadioButton("RH", not active.leftHandedProjection)) + active.leftHandedProjection = false; ImGui::SliderFloat("zNear", &updateParameters.m_zNear, 0.1f, 100.f, "%.2f", ImGuiSliderFlags_Logarithmic); ImGui::SliderFloat("zFar", &updateParameters.m_zFar, 110.f, 10000.f, "%.1f", ImGuiSliderFlags_Logarithmic); @@ -1625,13 +1617,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication case IPlanarProjection::CProjection::Perspective: { ImGui::SliderFloat("Fov", &updateParameters.m_planar.perspective.fov, 20.f, 150.f, "%.1f", ImGuiSliderFlags_Logarithmic); - boundProjection.setPerspective(isLH, updateParameters.m_zNear, updateParameters.m_zFar, updateParameters.m_planar.perspective.fov, active.aspectRatio); + boundProjection.setPerspective(updateParameters.m_zNear, updateParameters.m_zFar, updateParameters.m_planar.perspective.fov); } break; case IPlanarProjection::CProjection::Orthographic: { ImGui::SliderFloat("Ortho width", &updateParameters.m_planar.orthographic.orthoWidth, 1.f, 20.f, "%.1f", ImGuiSliderFlags_Logarithmic); - boundProjection.setOrthographic(isLH, updateParameters.m_zNear, updateParameters.m_zFar, updateParameters.m_planar.orthographic.orthoWidth, active.aspectRatio); + boundProjection.setOrthographic(updateParameters.m_zNear, updateParameters.m_zFar, updateParameters.m_planar.orthographic.orthoWidth); } break; default: break; @@ -1951,6 +1943,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication bool allowGizmoAxesToFlip = false; bool enableDebugGridDraw = true; float aspectRatio = 16.f / 9.f; + bool leftHandedProjection = true; std::optional boundProjectionIx = std::nullopt, lastBoundPerspectivePresetProjectionIx = std::nullopt, lastBoundOrthoPresetProjectionIx = std::nullopt; diff --git a/common/include/camera/IPlanarProjection.hpp b/common/include/camera/IPlanarProjection.hpp index 731b9fd5b..1fccf0ef4 100644 --- a/common/include/camera/IPlanarProjection.hpp +++ b/common/include/camera/IPlanarProjection.hpp @@ -60,32 +60,47 @@ class IPlanarProjection : public ILinearProjection float m_zFar; }; - inline void setPerspective(bool leftHanded = true, float zNear = 0.1f, float zFar = 100.f, float fov = 60.f, float aspectRatio = 16.f / 9.f) + inline void update(bool leftHanded, float aspectRatio) + { + switch (m_parameters.m_type) + { + case Perspective: + { + const auto& fov = m_parameters.m_planar.perspective.fov; + + if (leftHanded) + base_t::setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio, m_parameters.m_zNear, m_parameters.m_zFar)); + else + base_t::setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio, m_parameters.m_zNear, m_parameters.m_zFar)); + } break; + + case Orthographic: + { + const auto& orthoW = m_parameters.m_planar.orthographic.orthoWidth; + const auto viewHeight = orthoW * core::reciprocal(aspectRatio); + + if (leftHanded) + base_t::setProjectionMatrix(buildProjectionMatrixOrthoLH(orthoW, viewHeight, m_parameters.m_zNear, m_parameters.m_zFar)); + else + base_t::setProjectionMatrix(buildProjectionMatrixOrthoRH(orthoW, viewHeight, m_parameters.m_zNear, m_parameters.m_zFar)); + } break; + } + } + + inline void setPerspective(float zNear = 0.1f, float zFar = 100.f, float fov = 60.f) { m_parameters.m_type = Perspective; m_parameters.m_planar.perspective.fov = fov; m_parameters.m_zNear = zNear; m_parameters.m_zFar = zFar; - - if (leftHanded) - base_t::setProjectionMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), aspectRatio, zNear, zFar)); - else - base_t::setProjectionMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), aspectRatio, zNear, zFar)); } - inline void setOrthographic(bool leftHanded = true, float zNear = 0.1f, float zFar = 100.f, float orthoWidth = 10.f, float aspectRatio = 16.f / 9.f) + inline void setOrthographic(float zNear = 0.1f, float zFar = 100.f, float orthoWidth = 10.f) { m_parameters.m_type = Orthographic; m_parameters.m_planar.orthographic.orthoWidth = orthoWidth; m_parameters.m_zNear = zNear; m_parameters.m_zFar = zFar; - - const auto viewHeight = orthoWidth * core::reciprocal(aspectRatio); - - if (leftHanded) - base_t::setProjectionMatrix(buildProjectionMatrixOrthoLH(orthoWidth, viewHeight, zNear, zFar)); - else - base_t::setProjectionMatrix(buildProjectionMatrixOrthoRH(orthoWidth, viewHeight, zNear, zFar)); } inline const ProjectionParameters& getParameters() const { return m_parameters; } From 1c8f289cb5247743584cfa0f199dd11b02509e95 Mon Sep 17 00:00:00 2001 From: YasInvolved Date: Sun, 22 Dec 2024 12:23:24 +0100 Subject: [PATCH 70/84] added cameraz.json to builtin resources --- 61_UI/CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/61_UI/CMakeLists.txt b/61_UI/CMakeLists.txt index 1930cb17f..87e1aa646 100644 --- a/61_UI/CMakeLists.txt +++ b/61_UI/CMakeLists.txt @@ -17,5 +17,19 @@ if(NBL_BUILD_IMGUI) LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} geometryCreatorSpirvBRD) endif() +if(NBL_EMBED_BUILTIN_RESOURCES) + set(_BR_TARGET_ ${EXECUTABLE_NAME}_builtinResourceData) + + get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) + get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) + get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) + + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "${RES_FILE}") + + ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "" "nbl::this_example::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") + + LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} ${_BR_TARGET_}) +endif() + add_dependencies(${EXECUTABLE_NAME} argparse) target_include_directories(${EXECUTABLE_NAME} PUBLIC $) \ No newline at end of file From c5d6b1107bdcda8adf7cd7383698aadf74311a15 Mon Sep 17 00:00:00 2001 From: YasInvolved Date: Sun, 22 Dec 2024 12:43:41 +0100 Subject: [PATCH 71/84] move default config to app_resources --- 61_UI/CMakeLists.txt | 8 ++++++-- 61_UI/{ => app_resources}/cameras.json | 0 61_UI/main.cpp | 11 +++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) rename 61_UI/{ => app_resources}/cameras.json (100%) diff --git a/61_UI/CMakeLists.txt b/61_UI/CMakeLists.txt index 87e1aa646..42425b0a8 100644 --- a/61_UI/CMakeLists.txt +++ b/61_UI/CMakeLists.txt @@ -19,14 +19,18 @@ endif() if(NBL_EMBED_BUILTIN_RESOURCES) set(_BR_TARGET_ ${EXECUTABLE_NAME}_builtinResourceData) + set(RESOURCE_DIR "app_resources") get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) - LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "${RES_FILE}") + file(GLOB_RECURSE BUILTIN_RESOURCE_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}" CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}/*") + foreach(RES_FILE ${BUILTIN_RESOURCE_FILES}) + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "${RES_FILE}") + endforeach() - ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "" "nbl::this_example::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") + ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${RESOURCE_DIR}" "nbl::this_example::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} ${_BR_TARGET_}) endif() diff --git a/61_UI/cameras.json b/61_UI/app_resources/cameras.json similarity index 100% rename from 61_UI/cameras.json rename to 61_UI/app_resources/cameras.json diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 8aad11ab4..3a45eb7a1 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -10,6 +10,7 @@ using json = nlohmann::json; #include "keysmapping.hpp" #include "camera/CCubeProjection.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING +#include "nbl/builtin/CArchive.h" using planar_projections_range_t = std::vector; using planar_projection_t = CPlanarProjection; @@ -473,15 +474,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication { const auto cameraJsonFile = program.get("--file"); + json j; std::ifstream file(cameraJsonFile.c_str()); if (!file.is_open()) { - std::cerr << "Error: Cannot open input \"" << cameraJsonFile.c_str() << "\" json file."; + m_logger->log("Cannot open input \"%s\" json file. Switching to default config."); return false; } - - json j; - file >> j; + else + { + file >> j; + } std::vector> cameras; for (const auto& jCamera : j["cameras"]) From cb06b7105c4543a2a829c789521d912da3606106 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 22 Dec 2024 12:48:29 +0100 Subject: [PATCH 72/84] allow to pick object from the scene in TRS editor, add more help-texts --- 61_UI/main.cpp | 368 ++++++++++++++++++++++++++++--------------------- 1 file changed, 214 insertions(+), 154 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 8aad11ab4..46c0c2040 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -14,6 +14,17 @@ using json = nlohmann::json; using planar_projections_range_t = std::vector; using planar_projection_t = CPlanarProjection; +// the only reason for those is to remind we must go with transpose & 4x4 matrices +struct ImGuizmoPlanarM16InOut +{ + float32_t4x4 view, projection; +}; + +struct ImGuizmoModelM16InOut +{ + float32_t4x4 inTRS, outTRS, outDeltaTRS; +}; + constexpr IGPUImage::SSubresourceRange TripleBufferUsedSubresourceRange = { .aspectMask = IGPUImage::EAF_COLOR_BIT, @@ -453,9 +464,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); - for (uint32_t i = 0u; i < sceneControlBinding.size(); ++i) + for (uint32_t i = 0u; i < windowControlBinding.size(); ++i) { - auto& scene = sceneControlBinding[i].scene; + auto& scene = windowControlBinding[i].scene; scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources), dpyInfo.resX, dpyInfo.resY); if (!scene) @@ -717,18 +728,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - if (m_planarProjections.size() < sceneControlBinding.size()) + if (m_planarProjections.size() < windowControlBinding.size()) { // TODO, temporary assuming it, I'm not going to implement each possible case now - std::cerr << "Expected at least " << std::to_string(sceneControlBinding.size()) << " planars!" << std::endl; + std::cerr << "Expected at least " << std::to_string(windowControlBinding.size()) << " planars!" << std::endl; return false; } // init render window planar references - we make all render windows start with focus on first // planar but in a way that first window has the planar's perspective preset bound & second orthographic - for (uint32_t i = 0u; i < sceneControlBinding.size(); ++i) + for (uint32_t i = 0u; i < windowControlBinding.size(); ++i) { - auto& binding = sceneControlBinding[i]; + auto& binding = windowControlBinding[i]; auto& planar = m_planarProjections[binding.activePlanarIx = 0]; binding.pickDefaultProjections(planar->getPlanarProjections()); @@ -756,12 +767,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].desc = core::smart_refctd_ptr(m_ui.manager->getFontAtlasView()); writes[nbl::ext::imgui::UI::FontAtlasTexId].info = descriptorInfo.data() + nbl::ext::imgui::UI::FontAtlasTexId; - for (uint32_t i = 0; i < sceneControlBinding.size(); ++i) + for (uint32_t i = 0; i < windowControlBinding.size(); ++i) { const auto textureIx = i + 1u; descriptorInfo[textureIx].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - descriptorInfo[textureIx].desc = sceneControlBinding[i].scene->getColorAttachment(); + descriptorInfo[textureIx].desc = windowControlBinding[i].scene->getColorAttachment(); writes[textureIx].info = descriptorInfo.data() + textureIx; writes[textureIx].info = descriptorInfo.data() + textureIx; @@ -836,10 +847,10 @@ class UISampleApp final : public examples::SimpleWindowedApplication }; if (useWindow) - for (auto binding : sceneControlBinding) + for (auto binding : windowControlBinding) renderOfflineScene(binding.scene); else - renderOfflineScene(sceneControlBinding.front().scene.get()); // just to not render to all at once + renderOfflineScene(windowControlBinding.front().scene.get()); // just to not render to all at once const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; const IGPUCommandBuffer::SRenderpassBeginInfo info = { @@ -970,13 +981,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication { // wait for first planar scene view fb { - .semaphore = sceneControlBinding[0].scene->semaphore.progress.get(), - .value = sceneControlBinding[0].scene->semaphore.finishedValue + .semaphore = windowControlBinding[0].scene->semaphore.progress.get(), + .value = windowControlBinding[0].scene->semaphore.finishedValue }, // and second one too { - .semaphore = sceneControlBinding[1].scene->semaphore.progress.get(), - .value = sceneControlBinding[1].scene->semaphore.finishedValue + .semaphore = windowControlBinding[1].scene->semaphore.progress.get(), + .value = windowControlBinding[1].scene->semaphore.finishedValue } } )); @@ -988,8 +999,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication { // wait for picked planar only { - .semaphore = sceneControlBinding[activeRenderWindowIx].scene->semaphore.progress.get(), - .value = sceneControlBinding[activeRenderWindowIx].scene->semaphore.finishedValue + .semaphore = windowControlBinding[activeRenderWindowIx].scene->semaphore.progress.get(), + .value = windowControlBinding[activeRenderWindowIx].scene->semaphore.finishedValue } } )); @@ -1081,7 +1092,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (enableActiveCameraMovement) { - auto& binding = sceneControlBinding[activeRenderWindowIx]; + auto& binding = windowControlBinding[activeRenderWindowIx]; auto& planar = m_planarProjections[binding.activePlanarIx]; auto* camera = planar->getCamera(); @@ -1115,109 +1126,15 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuiIO& io = ImGui::GetIO(); ImGuizmo::BeginFrame(); - - // TODO: need to inspect where I'm wrong, workaround - auto gimbalToImguizmoTRS = [&](const float32_t3x4& nblGimbalTrs) -> float32_t4x4 - { - // *do not transpose whole matrix*, only the translate part - float32_t4x4 trs = getMatrix3x4As4x4(nblGimbalTrs); - trs[3] = float32_t4(nblGimbalTrs[0][3], nblGimbalTrs[1][3], nblGimbalTrs[2][3], 1.f); - trs[0][3] = 0.f; - trs[1][3] = 0.f; - trs[2][3] = 0.f; - - return trs; - }; - { SImResourceInfo info; info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; - // render camera views onto GUIs + // render bound planar camera views onto GUI windows if (useWindow) { - // the only reason for those is to remind we must go with transpose & 4x4 matrices - struct ImGuizmoPlanarM16InOut - { - float32_t4x4 view, projection; - }; - - struct ImGuizmoModelM16InOut - { - float32_t4x4 inTRS, outTRS, outDeltaTRS; - }; - - //////////////////////////////////////////////////////////////////////////// - // ABS TRS control with editor, only single object can be manipulated at time - { - ImGuizmoModelM16InOut imguizmoModel; - - if (boundCameraToManipulate) - imguizmoModel.inTRS = gimbalToImguizmoTRS(getCastedMatrix(boundCameraToManipulate->getGimbal()())); - else - imguizmoModel.inTRS = transpose(getMatrix3x4As4x4(m_model)); - - imguizmoModel.outTRS = imguizmoModel.inTRS; - - // TODO! DELTA TRS IS ONLY TRANSLATE TEMPORARY - TransformEditor(&imguizmoModel.outTRS[0][0], &imguizmoModel.outDeltaTRS); - - // generate virtual events given delta TRS matrix - if (boundCameraToManipulate) - { - { - static std::vector virtualEvents(0x45); - - uint32_t vCount = {}; - - boundCameraToManipulate->beginInputProcessing(m_nextPresentationTimestamp); - { - boundCameraToManipulate->process(nullptr, vCount); - - if (virtualEvents.size() < vCount) - virtualEvents.resize(vCount); - - IGimbalController::SUpdateParameters params; - params.imguizmoEvents = { { imguizmoModel.outDeltaTRS } }; - boundCameraToManipulate->process(virtualEvents.data(), vCount, params); - } - boundCameraToManipulate->endInputProcessing(); - - // I start to think controller should be able to set sensitivity to scale magnitudes of generated events - // in order for camera to not keep any magnitude scalars like move or rotation speed scales - - auto* fps = dynamic_cast(boundCameraToManipulate.get()); - - float pmSpeed = 1.f; - float prSpeed = 1.f; - - if (fps) - { - float pmSpeed = fps->getMoveSpeedScale(); - float prSpeed = fps->getRotationSpeedScale(); - - fps->setMoveSpeedScale(1); - fps->setRotationSpeedScale(1); - } - - // NOTE: generated events from ImGuizmo controller are always in world space! - if (vCount) - boundCameraToManipulate->manipulate({ virtualEvents.data(), vCount }, ICamera::World); - - if (fps) - { - fps->setMoveSpeedScale(pmSpeed); - fps->setRotationSpeedScale(prSpeed); - } - } - } - else - { - // for scene demo model full affine transformation without limits is assumed - m_model = float32_t3x4(transpose(imguizmoModel.outTRS)); - } - } - //////////////////////////////////////////////////////////////////////////// + // ABS TRS editor to manipulate bound object + TransformEditor(); if(enableActiveCameraMovement) ImGuizmo::Enable(false); @@ -1226,7 +1143,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication size_t gizmoIx = {}; size_t manipulationCounter = {}; - for (uint32_t windowIx = 0; windowIx < sceneControlBinding.size(); ++windowIx) + for (uint32_t windowIx = 0; windowIx < windowControlBinding.size(); ++windowIx) { // setup imgui window ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); @@ -1239,7 +1156,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetWindowPos(), cursorPos = ImGui::GetCursorScreenPos(); // setup bound entities for the window like camera & projections - auto& binding = sceneControlBinding[windowIx]; + auto& binding = windowControlBinding[windowIx]; auto& planarBound = m_planarProjections[binding.activePlanarIx]; assert(planarBound); @@ -1336,6 +1253,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (targetGimbalManipulationCamera) { boundCameraToManipulate = smart_refctd_ptr(targetGimbalManipulationCamera); + boundPlanarCameraIxToManipulate = modelIx - 1u; /* testing imguizmo controller for target camera, we use delta world imguizmo TRS matrix to generate virtual events @@ -1358,30 +1276,33 @@ class UISampleApp final : public examples::SimpleWindowedApplication } targetGimbalManipulationCamera->endInputProcessing(); - auto* fps = dynamic_cast(targetGimbalManipulationCamera); - float pMoveSpeed = 1.f, pRotationSpeed = 1.f; - - if (fps) + if (vCount) { - pMoveSpeed = fps->getMoveSpeedScale(); - pRotationSpeed = fps->getRotationSpeedScale(); + auto* fps = dynamic_cast(targetGimbalManipulationCamera); - // I start to think controller should be able to set sensitivity to scale magnitudes of generated events - // in order for camera to not keep any magnitude scalars like move or rotation speed scales + float pMoveSpeed = 1.f, pRotationSpeed = 1.f; - fps->setMoveSpeedScale(1); - fps->setRotationSpeedScale(1); - } + if (fps) + { + pMoveSpeed = fps->getMoveSpeedScale(); + pRotationSpeed = fps->getRotationSpeedScale(); - // NOTE: generated events from ImGuizmo controller are always in world space! - if (vCount) + // I start to think controller should be able to set sensitivity to scale magnitudes of generated events + // in order for camera to not keep any magnitude scalars like move or rotation speed scales + + fps->setMoveSpeedScale(1); + fps->setRotationSpeedScale(1); + } + + // NOTE: generated events from ImGuizmo controller are always in world space! targetGimbalManipulationCamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); - if (fps) - { - fps->setMoveSpeedScale(pMoveSpeed); - fps->setRotationSpeedScale(pRotationSpeed); + if (fps) + { + fps->setMoveSpeedScale(pMoveSpeed); + fps->setRotationSpeedScale(pRotationSpeed); + } } } } @@ -1390,11 +1311,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication // again, for scene demo model full affine transformation without limits is assumed m_model = float32_t3x4(transpose(imguizmoModel.outTRS)); boundCameraToManipulate = nullptr; + boundPlanarCameraIxToManipulate = std::nullopt; } } } - if (ImGuizmo::IsOver()) + if (ImGuizmo::IsOver() and not ImGuizmo::IsUsingAny() && not enableActiveCameraMovement) { ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); @@ -1458,9 +1380,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto type = static_cast(gcIndex); const auto& [gpu, meta] = references[type]; - for (uint32_t i = 0u; i < sceneControlBinding.size(); ++i) + for (uint32_t i = 0u; i < windowControlBinding.size(); ++i) { - auto& binding = sceneControlBinding[i]; + auto& binding = windowControlBinding[i]; auto& hook = binding.scene->object; auto& planarBound = m_planarProjections[binding.activePlanarIx]; @@ -1495,7 +1417,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Checkbox("Window mode##useWindow", &useWindow); ImGui::Separator(); - auto& active = sceneControlBinding[activeRenderWindowIx]; + auto& active = windowControlBinding[activeRenderWindowIx]; const auto activeRenderWindowIxString = std::to_string(activeRenderWindowIx); ImGui::Text("Active Render Window: %s", activeRenderWindowIxString.c_str()); @@ -1511,7 +1433,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication for (size_t i = 0; i < planarsCount; ++i) labels[i] = sbels[i].c_str(); - int currentPlanarIx = static_cast(active.activePlanarIx); if (ImGui::Combo("Active Planar", ¤tPlanarIx, labels.data(), static_cast(labels.size()))) { @@ -1680,6 +1601,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto flags = ImGuiTreeNodeFlags_DefaultOpen; if (ImGui::TreeNodeEx("Bound Camera", flags)) { + ImGui::Text("Type: %s", boundCamera->getIdentifier().data()); + ImGui::Text("Object Ix: %s", std::to_string(active.activePlanarIx + 1u).c_str()); + ImGui::Separator(); { auto* fps = dynamic_cast(boundCamera); @@ -1703,8 +1627,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto& orientation = gimbal.getOrientation(); const auto viewMatrix = getCastedMatrix(gimbal.getViewMatrix()); - ImGui::Text("Type: %s", boundCamera->getIdentifier().data()); - ImGui::Separator(); addMatrixTable("Position", ("PositionTable_" + activePlanarIxString).c_str(), 1, 3, &position[0], false); addMatrixTable("Orientation (Quaternion)", ("OrientationTable_" + activePlanarIxString).c_str(), 1, 4, &orientation[0], false); addMatrixTable("View Matrix", ("ViewMatrixTable_" + activePlanarIxString).c_str(), 3, 4, &viewMatrix[0][0], false); @@ -1725,7 +1647,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } - inline void TransformEditor(float* m16TRSmatrix, IGimbalController::input_imguizmo_event_t* deltaTRS = nullptr) + inline void TransformEditor() { static float bounds[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; static float boundsSnap[] = { 0.1f, 0.1f, 0.1f }; @@ -1735,13 +1657,48 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); ImGui::Begin("TRS Editor"); - ImGui::SameLine(); ImGuiIO& io = ImGui::GetIO(); - ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); - if (ImGuizmo::IsUsing()) - ImGui::Text("Using gizmo"); - ImGui::Separator(); + { + const size_t objectsCount = m_planarProjections.size() + 1u; + assert(objectsCount); + + std::vector sbels(objectsCount); + for (size_t i = 0; i < objectsCount; ++i) + sbels[i] = "Object " + std::to_string(i); + + std::vector labels(objectsCount); + for (size_t i = 0; i < objectsCount; ++i) + labels[i] = sbels[i].c_str(); + + int activeObject = boundCameraToManipulate ? static_cast(boundPlanarCameraIxToManipulate.value() + 1u) : 0; + if (ImGui::Combo("Active Object", &activeObject, labels.data(), static_cast(labels.size()))) + { + const auto newActiveObject = static_cast(activeObject); + + if (newActiveObject) // camera + { + boundPlanarCameraIxToManipulate = newActiveObject - 1u; + ICamera* const targetGimbalManipulationCamera = m_planarProjections[boundPlanarCameraIxToManipulate.value()]->getCamera(); + boundCameraToManipulate = smart_refctd_ptr(targetGimbalManipulationCamera); + } + else // gc model + { + boundPlanarCameraIxToManipulate = std::nullopt; + boundCameraToManipulate = nullptr; + } + } + } + + ImGuizmoModelM16InOut imguizmoModel; + + if (boundCameraToManipulate) + imguizmoModel.inTRS = gimbalToImguizmoTRS(getCastedMatrix(boundCameraToManipulate->getGimbal()())); + else + imguizmoModel.inTRS = transpose(getMatrix3x4As4x4(m_model)); + + imguizmoModel.outTRS = imguizmoModel.inTRS; + float* m16TRSmatrix = &imguizmoModel.outTRS[0][0]; std::string indent; if (boundCameraToManipulate) @@ -1750,6 +1707,35 @@ class UISampleApp final : public examples::SimpleWindowedApplication indent = "Geometry Creator Object"; ImGui::Text("Identifier: \"%s\"", indent.c_str()); + { + if (ImGuizmo::IsUsingAny()) + ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "Gizmo: In Use"); + else + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Gizmo: Idle"); + + if (ImGui::IsItemHovered()) + { + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.2f, 0.2f, 0.2f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); + + ImVec2 mousePos = ImGui::GetMousePos(); + ImGui::SetNextWindowPos(ImVec2(mousePos.x + 10, mousePos.y + 10), ImGuiCond_Always); + + ImGui::Begin("HoverOverlay", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings); + + ImGui::Text("Right-click and drag on the gizmo to manipulate the object."); + + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + } + ImGui::Separator(); if (!boundCameraToManipulate) @@ -1794,9 +1780,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication float32_t3 matrixTranslation, matrixRotation, matrixScale; IGimbalController::input_imguizmo_event_t decomposed, recomposed; - - if (deltaTRS) - *deltaTRS = IGimbalController::input_imguizmo_event_t(1); + imguizmoModel.outDeltaTRS = IGimbalController::input_imguizmo_event_t(1); ImGuizmo::DecomposeMatrixToComponents(m16TRSmatrix, &matrixTranslation[0], &matrixRotation[0], &matrixScale[0]); decomposed = *reinterpret_cast(m16TRSmatrix); @@ -1821,8 +1805,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication recomposed = *reinterpret_cast(m16TRSmatrix); // TODO AND NOTE: I only take care of translate part temporary! - if(deltaTRS) - deltaTRS->operator[](3) = recomposed[3] - decomposed[3]; + imguizmoModel.outDeltaTRS[3] = recomposed[3] - decomposed[3]; if (mCurrentGizmoOperation != ImGuizmo::SCALE) { @@ -1849,6 +1832,68 @@ class UISampleApp final : public examples::SimpleWindowedApplication } ImGui::End(); + + { + // generate virtual events given delta TRS matrix + if (boundCameraToManipulate) + { + { + static std::vector virtualEvents(0x45); + + if (not enableActiveCameraMovement) + { + uint32_t vCount = {}; + + boundCameraToManipulate->beginInputProcessing(m_nextPresentationTimestamp); + { + boundCameraToManipulate->process(nullptr, vCount); + + if (virtualEvents.size() < vCount) + virtualEvents.resize(vCount); + + IGimbalController::SUpdateParameters params; + params.imguizmoEvents = { { imguizmoModel.outDeltaTRS } }; + boundCameraToManipulate->process(virtualEvents.data(), vCount, params); + } + boundCameraToManipulate->endInputProcessing(); + + // I start to think controller should be able to set sensitivity to scale magnitudes of generated events + // in order for camera to not keep any magnitude scalars like move or rotation speed scales + + if (vCount) + { + auto* fps = dynamic_cast(boundCameraToManipulate.get()); + + float pmSpeed = 1.f; + float prSpeed = 1.f; + + if (fps) + { + pmSpeed = fps->getMoveSpeedScale(); + prSpeed = fps->getRotationSpeedScale(); + + fps->setMoveSpeedScale(1); + fps->setRotationSpeedScale(1); + } + + // NOTE: generated events from ImGuizmo controller are always in world space! + boundCameraToManipulate->manipulate({ virtualEvents.data(), vCount }, ICamera::World); + + if (fps) + { + fps->setMoveSpeedScale(pmSpeed); + fps->setRotationSpeedScale(prSpeed); + } + } + } + } + } + else + { + // for scene demo model full affine transformation without limits is assumed + m_model = float32_t3x4(transpose(imguizmoModel.outTRS)); + } + } } inline void addMatrixTable(const char* topText, const char* tableName, int rows, int columns, const float* pointer, bool withSeparator = true) @@ -1877,6 +1922,19 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Separator(); } + // TODO: need to inspect where I'm wrong, workaround + inline float32_t4x4 gimbalToImguizmoTRS(const float32_t3x4& nblGimbalTrs) + { + // *do not transpose whole matrix*, only the translate part + float32_t4x4 trs = getMatrix3x4As4x4(nblGimbalTrs); + trs[3] = float32_t4(nblGimbalTrs[0][3], nblGimbalTrs[1][3], nblGimbalTrs[2][3], 1.f); + trs[0][3] = 0.f; + trs[1][3] = 0.f; + trs[2][3] = 0.f; + + return trs; + }; + std::chrono::seconds timeout = std::chrono::seconds(0x7fffFFFFu); clock_t::time_point start; @@ -1929,13 +1987,15 @@ class UISampleApp final : public examples::SimpleWindowedApplication nbl::hlsl::float32_t3x4 m_model = nbl::hlsl::float32_t3x4(1.f); // if we had working IObjectTransform or something similar then it would be it instead, it is "last manipulated object" I need for TRS editor - nbl::core::smart_refctd_ptr boundCameraToManipulate; + // in reality we should store range of those IObjectTransforem interface range & index to object representing last manipulated one + nbl::core::smart_refctd_ptr boundCameraToManipulate = nullptr; + std::optional boundPlanarCameraIxToManipulate = std::nullopt; std::vector> m_planarProjections; bool enableActiveCameraMovement = false; - struct SceneControlBinding + struct windowControlBinding { nbl::core::smart_refctd_ptr scene; @@ -1971,7 +2031,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication }; static constexpr inline auto MaxSceneFBOs = 2u; - std::array sceneControlBinding; + std::array windowControlBinding; uint32_t activeRenderWindowIx = 0u; // UI font atlas + viewport FBO color attachment textures From dbc9baa4e0e38e93a9e9c90d242ee7e863e09f0c Mon Sep 17 00:00:00 2001 From: YasInvolved Date: Sun, 22 Dec 2024 13:06:41 +0100 Subject: [PATCH 73/84] load default config if file specified in parameter is not found --- 61_UI/main.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 3a45eb7a1..e125206d3 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -10,7 +10,7 @@ using json = nlohmann::json; #include "keysmapping.hpp" #include "camera/CCubeProjection.hpp" #include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING -#include "nbl/builtin/CArchive.h" +#include "nbl/this_example/builtin/CArchive.h" using planar_projections_range_t = std::vector; using planar_projection_t = CPlanarProjection; @@ -478,8 +478,16 @@ class UISampleApp final : public examples::SimpleWindowedApplication std::ifstream file(cameraJsonFile.c_str()); if (!file.is_open()) { - m_logger->log("Cannot open input \"%s\" json file. Switching to default config."); - return false; + m_logger->log("Cannot open input \"%s\" json file. Switching to default config.", ILogger::ELL_WARNING, cameraJsonFile.c_str()); + auto assets = make_smart_refctd_ptr(smart_refctd_ptr(m_logger)); + auto pFile = assets->getFile("cameras.json", IFile::ECF_READ, ""); + + string config; + IFile::success_t result; + config.resize(pFile->getSize()); + pFile->read(result, config.data(), 0, pFile->getSize()); + + j = json::parse(config); } else { From 7a96b2b4339641f3a1d2ceeeae9e7d3bf9c5c249 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Sun, 22 Dec 2024 13:23:25 +0100 Subject: [PATCH 74/84] make DepthFboAttachmentFormat (gc scene fbo) EF_D32_SFLOAT to increase precision --- common/include/CGeomtryCreatorScene.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/include/CGeomtryCreatorScene.hpp b/common/include/CGeomtryCreatorScene.hpp index 444a58b4d..7180d4019 100644 --- a/common/include/CGeomtryCreatorScene.hpp +++ b/common/include/CGeomtryCreatorScene.hpp @@ -20,7 +20,7 @@ using namespace system struct Traits { static constexpr auto DefaultFramebufferW = 1280u, DefaultFramebufferH = 720u; - static constexpr auto ColorFboAttachmentFormat = nbl::asset::EF_R8G8B8A8_SRGB, DepthFboAttachmentFormat = nbl::asset::EF_D16_UNORM; + static constexpr auto ColorFboAttachmentFormat = nbl::asset::EF_R8G8B8A8_SRGB, DepthFboAttachmentFormat = nbl::asset::EF_D32_SFLOAT; static constexpr auto Samples = nbl::video::IGPUImage::ESCF_1_BIT; static constexpr nbl::video::IGPUCommandBuffer::SClearColorValue clearColor = { .float32 = {0.f,0.f,0.f,1.f} }; static constexpr nbl::video::IGPUCommandBuffer::SClearDepthStencilValue clearDepth = { .depth = 0.f }; From 2dcdb564b8a213f4b0257eadd1e689b27138bbce Mon Sep 17 00:00:00 2001 From: YasInvolved Date: Sun, 22 Dec 2024 15:24:34 +0100 Subject: [PATCH 75/84] split viewports and planars in JSON --- 61_UI/app_resources/cameras.json | 67 +++++++++----------------------- 61_UI/main.cpp | 62 ++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 49 deletions(-) diff --git a/61_UI/app_resources/cameras.json b/61_UI/app_resources/cameras.json index 80e09d059..5f8d5b489 100644 --- a/61_UI/app_resources/cameras.json +++ b/61_UI/app_resources/cameras.json @@ -31,62 +31,33 @@ } ], "viewports": [ + { + "projection": 0, + "controllers": { + "keyboard": 0, + "mouse": 0 + } + }, + { + "projection": 1, + "controllers": { + "keyboard": 1, + "mouse": 0 + } + } + ], + "planars": [ { "camera": 0, - "planarControllerSet": [ - { - "projection": 0, - "controllers": { - "keyboard": 0, - "mouse": 0 - } - }, - { - "projection": 1, - "controllers": { - "keyboard": 1, - "mouse": 0 - } - } - ] + "viewports": [0, 1] }, { "camera": 1, - "planarControllerSet": [ - { - "projection": 0, - "controllers": { - "keyboard": 0, - "mouse": 0 - } - }, - { - "projection": 1, - "controllers": { - "keyboard": 1, - "mouse": 0 - } - } - ] + "viewports": [0, 1] }, { "camera": 2, - "planarControllerSet": [ - { - "projection": 0, - "controllers": { - "keyboard": 0, - "mouse": 0 - } - }, - { - "projection": 1, - "controllers": { - "keyboard": 1, - "mouse": 0 - } - } - ] + "viewports": [0, 1] } ], "controllers": { diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 0233717fd..07d981826 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -690,7 +690,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - if (j.contains("viewports")) + /*if (j.contains("viewports")) { for (const auto& jViewport : j["viewports"]) { @@ -737,6 +737,66 @@ class UISampleApp final : public examples::SimpleWindowedApplication { std::cerr << "Expected \"viewports\" keyword in JSON!" << std::endl; return false; + }*/ + + if (j.contains("viewports") && j.contains("planars")) + { + for (const auto& jPlanar : j["planars"]) + { + if (!jPlanar.contains("camera")) + { + logFail("Expected \"camera\" value in planar object"); + return false; + } + + if (!jPlanar.contains("viewports")) + { + logFail("Expected \"viewports\" list in planar object"); + return false; + } + + const auto cameraIx = jPlanar["camera"].get(); + auto boundViewports = jPlanar["viewports"].get>(); + + auto& planars = m_planarProjections.emplace_back() = planar_projection_t::create(smart_refctd_ptr(cameras[cameraIx])); + for (const auto viewportIx : boundViewports) + { + auto& viewport = j["viewports"][viewportIx]; + if (!viewport.contains("projection") || !viewport.contains("controllers")) + { + logFail("\"projection\" or \"controllers\" missing in viewport object index %d", viewportIx); + return false; + } + + if (!viewport["controllers"].contains("keyboard")) + { + logFail("\"keyboard\" value missing in controllers in viewport object index %d", viewportIx); + return false; + } + + if (!viewport["controllers"].contains("mouse")) + { + logFail("\"mouse\" value missing in controllers in viewport object index %d", viewportIx); + return false; + } + + auto keyboardIx = viewport["controllers"]["keyboard"].get(); + auto mouseIx = viewport["controllers"]["mouse"].get(); + + auto projectionIx = viewport["projection"].get(); + auto keyboardControllerIx = viewport["controllers"]["keyboard"].get(); + auto mouseControllerIx = viewport["controllers"]["mouse"].get(); + + auto& projection = planars->getPlanarProjections().emplace_back(projections[projectionIx]); + projection.updateKeyboardMapping([&](auto& map) { map = controllers.keyboard[keyboardControllerIx]; }); + projection.updateMouseMapping([&](auto& map) { map = controllers.mouse[mouseControllerIx]; }); + } + } + } + else + { + logFail("Expected \"viewports\" and \"planars\" lists in JSON"); + return false; } if (m_planarProjections.size() < windowControlBinding.size()) From 53c626fe2a7925051f65ba19a0f09a676e66c81a Mon Sep 17 00:00:00 2001 From: YasInvolved Date: Sun, 22 Dec 2024 15:41:13 +0100 Subject: [PATCH 76/84] replace cerr with logFail to improve logging --- 61_UI/main.cpp | 87 +++++++++++--------------------------------------- 1 file changed, 19 insertions(+), 68 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 07d981826..d142192d6 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -459,7 +459,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (!resources) { - m_logger->log("Could not create geometry creator gpu resources!", ILogger::ELL_ERROR); + logFail("Could not create geometry creator gpu resources!"); return false; } @@ -472,7 +472,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (!scene) { - m_logger->log("Could not create geometry creator scene[%d]!", ILogger::ELL_ERROR, i); + logFail("Could not create geometry creator scene[%d]!", i); return false; } } @@ -514,13 +514,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication { if (!jCamera.contains("position")) { - std::cerr << "Expected \"position\" keyword for camera definition!"; + logFail("Expected \"position\" keyword for camera definition!"); return false; } if (!jCamera.contains("orientation")) { - std::cerr << "Expected \"orientation\" keyword for camera definition!"; + logFail("Expected \"orientation\" keyword for camera definition!"); return false; } @@ -544,13 +544,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication } else { - std::cerr << "Unsupported camera type!"; + logFail("Unsupported camera type!"); return false; } } else { - std::cerr << "Expected \"type\" keyword for camera definition!"; + logFail("Expected \"type\" keyword for camera definition!"); return false; } } @@ -564,13 +564,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (!jProjection.contains("zNear")) { - "Expected \"zNear\" keyword for planar projection definition!"; + logFail("Expected \"zNear\" keyword for planar projection definition!"); return false; } if (!jProjection.contains("zFar")) { - "Expected \"zFar\" keyword for planar projection definition!"; + logFail("Expected \"zFar\" keyword for planar projection definition!"); return false; } @@ -581,7 +581,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { if (!jProjection.contains("fov")) { - "Expected \"fov\" keyword for planar perspective projection definition!"; + logFail("Expected \"fov\" keyword for planar perspective projection definition!"); return false; } @@ -593,7 +593,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication { if (!jProjection.contains("orthoWidth")) { - "Expected \"orthoWidth\" keyword for planar orthographic projection definition!"; + logFail("Expected \"orthoWidth\" keyword for planar orthographic projection definition!"); return false; } @@ -602,7 +602,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } else { - std::cerr << "Unsupported projection!"; + logFail("Unsupported projection!"); return false; } } @@ -631,7 +631,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (nativeCode == EKC_NONE) { - std::cerr << "Invalid native key \"" << key.c_str() << "\" code mapping for keyboard controller!" << std::endl; + logFail("Invalid native key \"%s\" code mapping for keyboard controller", key.c_str()); return false; } @@ -640,14 +640,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication } else { - std::cerr << "Expected \"mappings\" keyword for keyboard controller definition!" << std::endl; + logFail("Expected \"mappings\" keyword for keyboard controller definition!"); return false; } } } else { - std::cerr << "Expected \"keyboard\" keyword in controllers definition!" << std::endl; + logFail("Expected \"keyboard\" keyword in controllers definition!"); return false; } @@ -664,7 +664,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (nativeCode == EMC_NONE) { - std::cerr << "Invalid native key \"" << key.c_str() << "\" code mapping for mouse controller!" << std::endl; + logFail("Invalid native key \"%s\" code mapping for mouse controller", key.c_str()); return false; } @@ -673,72 +673,23 @@ class UISampleApp final : public examples::SimpleWindowedApplication } else { - std::cerr << "Expected \"mappings\" keyword for mouse controller definition!" << std::endl; + logFail("Expected \"mappings\" keyword for mouse controller definition!"); return false; } } } else { - std::cerr << "Expected \"mouse\" keyword in controllers definition!" << std::endl; + logFail("Expected \"mouse\" keyword in controllers definition"); return false; } } else { - std::cerr << "Expected \"controllers\" keyword in JSON!" << std::endl; + logFail("Expected \"controllers\" keyword in controllers JSON"); return false; } - /*if (j.contains("viewports")) - { - for (const auto& jViewport : j["viewports"]) - { - if (!jViewport.contains("camera")) - { - std::cerr << "Expected \"camera\" keyword in viewport definition!" << std::endl; - return false; - } - - const auto cameraIx = jViewport["camera"].get(); - auto& planars = m_planarProjections.emplace_back() = planar_projection_t::create(smart_refctd_ptr(cameras[cameraIx])); - - if (!jViewport.contains("planarControllerSet")) - { - std::cerr << "Expected \"planarControllerSet\" keyword in viewport definition!" << std::endl; - return false; - } - - for (const auto& jPlanarController : jViewport["planarControllerSet"]) - { - if (!jPlanarController.contains("projection")) - { - std::cerr << "Expected \"projection\" keyword in planarControllerSet!" << std::endl; - return false; - } - - if (!jPlanarController.contains("controllers")) - { - std::cerr << "Expected \"controllers\" keyword in planarControllerSet!" << std::endl; - return false; - } - - auto projectionIx = jPlanarController["projection"].get(); - auto keyboardControllerIx = jPlanarController["controllers"]["keyboard"].get(); - auto mouseControllerIx = jPlanarController["controllers"]["mouse"].get(); - - auto& projection = planars->getPlanarProjections().emplace_back(projections[projectionIx]); - projection.updateKeyboardMapping([&](auto& map) { map = controllers.keyboard[keyboardControllerIx]; }); - projection.updateMouseMapping([&](auto& map) { map = controllers.mouse[mouseControllerIx]; }); - } - } - } - else - { - std::cerr << "Expected \"viewports\" keyword in JSON!" << std::endl; - return false; - }*/ - if (j.contains("viewports") && j.contains("planars")) { for (const auto& jPlanar : j["planars"]) @@ -802,7 +753,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (m_planarProjections.size() < windowControlBinding.size()) { // TODO, temporary assuming it, I'm not going to implement each possible case now - std::cerr << "Expected at least " << std::to_string(windowControlBinding.size()) << " planars!" << std::endl; + logFail("Expected at least %d planars", windowControlBinding.size()); return false; } From 454289e40266a78c3827777602b55eee1484d067 Mon Sep 17 00:00:00 2001 From: YasInvolved Date: Sun, 22 Dec 2024 15:49:41 +0100 Subject: [PATCH 77/84] moved data parsing to before gfx initialization --- 61_UI/main.cpp | 470 ++++++++++++++++++++++++------------------------- 1 file changed, 234 insertions(+), 236 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index d142192d6..195505e11 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -275,219 +275,12 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (!base_t::onAppInitialized(std::move(system))) return false; - // Create asset manager - m_assetManager = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); - - // First create the resources that don't depend on a swapchain - m_semaphore = m_device->createSemaphore(m_realFrameIx); - if (!m_semaphore) - return logFail("Failed to Create a Semaphore!"); - - // The nice thing about having a triple buffer is that you don't need to do acrobatics to account for the formats available to the surface. - // You can transcode to the swapchain's format while copying, and I actually recommend to do surface rotation, tonemapping and OETF application there. - const auto format = asset::EF_R8G8B8A8_SRGB; - // Could be more clever and use the copy Triple Buffer to Swapchain as an opportunity to do a MSAA resolve or something - const auto samples = IGPUImage::ESCF_1_BIT; - - // Create the renderpass - { - const IGPURenderpass::SCreationParams::SColorAttachmentDescription colorAttachments[] = { - {{ - { - .format = format, - .samples = samples, - .mayAlias = false - }, - /*.loadOp = */IGPURenderpass::LOAD_OP::CLEAR, - /*.storeOp = */IGPURenderpass::STORE_OP::STORE, - /*.initialLayout = */IGPUImage::LAYOUT::UNDEFINED, // because we clear we don't care about contents when we grab the triple buffer img again - /*.finalLayout = */IGPUImage::LAYOUT::TRANSFER_SRC_OPTIMAL // put it already in the correct layout for the blit operation - }}, - IGPURenderpass::SCreationParams::ColorAttachmentsEnd - }; - IGPURenderpass::SCreationParams::SSubpassDescription subpasses[] = { - {}, - IGPURenderpass::SCreationParams::SubpassesEnd - }; - subpasses[0].colorAttachments[0] = { .render = {.attachmentIndex = 0,.layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL} }; - // We actually need external dependencies to ensure ordering of the Implicit Layout Transitions relative to the semaphore signals - IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = { - // wipe-transition to ATTACHMENT_OPTIMAL - { - .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, - .dstSubpass = 0, - .memoryBarrier = { - // we can have NONE as Sources because the semaphore wait is ALL_COMMANDS - // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 - .dstStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, - .dstAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT - } - // leave view offsets and flags default - }, - // ATTACHMENT_OPTIMAL to PRESENT_SRC - { - .srcSubpass = 0, - .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, - .memoryBarrier = { - .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, - .srcAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT - // we can have NONE as the Destinations because the semaphore signal is ALL_COMMANDS - // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 - } - // leave view offsets and flags default - }, - IGPURenderpass::SCreationParams::DependenciesEnd - }; - - IGPURenderpass::SCreationParams params = {}; - params.colorAttachments = colorAttachments; - params.subpasses = subpasses; - params.dependencies = dependencies; - m_renderpass = m_device->createRenderpass(params); - if (!m_renderpass) - return logFail("Failed to Create a Renderpass!"); - } - - // We just live life in easy mode and have the Swapchain Creation Parameters get deduced from the surface. - // We don't need any control over the format of the swapchain because we'll be only using Renderpasses this time! - // TODO: improve the queue allocation/choice and allocate a dedicated presentation queue to improve responsiveness and race to present. - if (!m_surface || !m_surface->init(m_surface->pickQueue(m_device.get()), std::make_unique(), {})) - return logFail("Failed to Create a Swapchain!"); - - // Normally you'd want to recreate these images whenever the swapchain is resized in some increment, like 64 pixels or something. - // But I'm super lazy here and will just create "worst case sized images" and waste all the VRAM I can get. - const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); - for (auto i = 0; i < MaxFramesInFlight; i++) - { - auto& image = m_tripleBuffers[i]; - { - IGPUImage::SCreationParams params = {}; - params = asset::IImage::SCreationParams{ - .type = IGPUImage::ET_2D, - .samples = samples, - .format = format, - .extent = {dpyInfo.resX,dpyInfo.resY,1}, - .mipLevels = 1, - .arrayLayers = 1, - .flags = IGPUImage::ECF_NONE, - // in this example I'll be using a renderpass to clear the image, and then a blit to copy it to the swapchain - .usage = IGPUImage::EUF_RENDER_ATTACHMENT_BIT | IGPUImage::EUF_TRANSFER_SRC_BIT - }; - image = m_device->createImage(std::move(params)); - if (!image) - return logFail("Failed to Create Triple Buffer Image!"); - - // use dedicated allocations, we have plenty of allocations left, even on Win32 - if (!m_device->allocate(image->getMemoryReqs(), image.get()).isValid()) - return logFail("Failed to allocate Device Memory for Image %d", i); - } - image->setObjectDebugName(("Triple Buffer Image " + std::to_string(i)).c_str()); - - // create framebuffers for the images - { - auto imageView = m_device->createImageView({ - .flags = IGPUImageView::ECF_NONE, - // give it a Transfer SRC usage flag so we can transition to the Tranfer SRC layout with End Renderpass - .subUsages = IGPUImage::EUF_RENDER_ATTACHMENT_BIT | IGPUImage::EUF_TRANSFER_SRC_BIT, - .image = core::smart_refctd_ptr(image), - .viewType = IGPUImageView::ET_2D, - .format = format - }); - const auto& imageParams = image->getCreationParameters(); - IGPUFramebuffer::SCreationParams params = { { - .renderpass = core::smart_refctd_ptr(m_renderpass), - .depthStencilAttachments = nullptr, - .colorAttachments = &imageView.get(), - .width = imageParams.extent.width, - .height = imageParams.extent.height, - .layers = imageParams.arrayLayers - } }; - m_framebuffers[i] = m_device->createFramebuffer(std::move(params)); - if (!m_framebuffers[i]) - return logFail("Failed to Create a Framebuffer for Image %d", i); - } - } - - // This time we'll create all CommandBuffers from one CommandPool, to keep life simple. However the Pool must support individually resettable CommandBuffers - // because they cannot be pre-recorded because the fraembuffers/swapchain images they use will change when a swapchain recreates. - auto pool = m_device->createCommandPool(getGraphicsQueue()->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); - if (!pool || !pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { m_cmdBufs.data(),MaxFramesInFlight }, core::smart_refctd_ptr(m_logger))) - return logFail("Failed to Create CommandBuffers!"); - - // UI - { - { - nbl::ext::imgui::UI::SCreationParameters params; - params.resources.texturesInfo = { .setIx = 0u, .bindingIx = 0u }; - params.resources.samplersInfo = { .setIx = 0u, .bindingIx = 1u }; - params.assetManager = m_assetManager; - params.pipelineCache = nullptr; - params.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, TotalUISampleTexturesAmount); - params.renderpass = smart_refctd_ptr(m_renderpass); - params.streamingBuffer = nullptr; - params.subpassIx = 0u; - params.transfer = getTransferUpQueue(); - params.utilities = m_utils; - - m_ui.manager = nbl::ext::imgui::UI::create(std::move(params)); - } - - if (!m_ui.manager) - return false; - - // note that we use default layout provided by our extension, but you are free to create your own by filling nbl::ext::imgui::UI::S_CREATION_PARAMETERS::resources - const auto* descriptorSetLayout = m_ui.manager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); - - IDescriptorPool::SCreateInfo descriptorPoolInfo = {}; - descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = (uint32_t)nbl::ext::imgui::UI::DefaultSamplerIx::COUNT; - descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = TotalUISampleTexturesAmount; - descriptorPoolInfo.maxSets = 1u; - descriptorPoolInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT; - - m_descriptorSetPool = m_device->createDescriptorPool(std::move(descriptorPoolInfo)); - assert(m_descriptorSetPool); - - m_descriptorSetPool->createDescriptorSets(1u, &descriptorSetLayout, &m_ui.descriptorSet); - assert(m_ui.descriptorSet); - - m_ui.manager->registerListener([this]() -> void { imguiListen(); }); - } - - // Geometry Creator Render Scene FBOs - { - resources = ResourcesBundle::create(m_device.get(), m_logger.get(), getGraphicsQueue(), m_assetManager->getGeometryCreator()); - - if (!resources) - { - logFail("Could not create geometry creator gpu resources!"); - return false; - } - - const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); - - for (uint32_t i = 0u; i < windowControlBinding.size(); ++i) - { - auto& scene = windowControlBinding[i].scene; - scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources), dpyInfo.resX, dpyInfo.resY); - - if (!scene) - { - logFail("Could not create geometry creator scene[%d]!", i); - return false; - } - } - } - - oracle.reportBeginFrameRecord(); - - // json file with camera inputs - // @Yas: TODO: THIS NEEDS BETTER VALIDATION AND LOGS ON FAILURE (+ status logs would be nice)!!! { const auto cameraJsonFile = program.get("--file"); json j; std::ifstream file(cameraJsonFile.c_str()); - if (!file.is_open()) + if (!file.is_open()) { m_logger->log("Cannot open input \"%s\" json file. Switching to default config.", ILogger::ELL_WARNING, cameraJsonFile.c_str()); auto assets = make_smart_refctd_ptr(smart_refctd_ptr(m_logger)); @@ -506,7 +299,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication } std::vector> cameras; - for (const auto& jCamera : j["cameras"]) + for (const auto& jCamera : j["cameras"]) { if (jCamera.contains("type")) { @@ -524,21 +317,21 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - auto position = [&]() - { - auto jret = jCamera["position"].get>(); - return float32_t3(jret[0], jret[1], jret[2]); - }(); + auto position = [&]() + { + auto jret = jCamera["position"].get>(); + return float32_t3(jret[0], jret[1], jret[2]); + }(); auto orientation = [&]() - { - auto jret = jCamera["orientation"].get>(); + { + auto jret = jCamera["orientation"].get>(); - // order important for glm::quat, - // the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) - // but memory layout (and json) is x,y,z,w - return glm::quat(jret[3], jret[0], jret[1], jret[2]); - }(); + // order important for glm::quat, + // the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) + // but memory layout (and json) is x,y,z,w + return glm::quat(jret[3], jret[0], jret[1], jret[2]); + }(); cameras.emplace_back() = make_smart_refctd_ptr(position, orientation); } @@ -614,18 +407,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication std::vector mouse; } controllers; - if (j.contains("controllers")) + if (j.contains("controllers")) { const auto& jControllers = j["controllers"]; - if (jControllers.contains("keyboard")) + if (jControllers.contains("keyboard")) { - for (const auto& jKeyboard : jControllers["keyboard"]) + for (const auto& jKeyboard : jControllers["keyboard"]) { - if (jKeyboard.contains("mappings")) + if (jKeyboard.contains("mappings")) { auto& controller = controllers.keyboard.emplace_back(); - for (const auto& [key, value] : jKeyboard["mappings"].items()) + for (const auto& [key, value] : jKeyboard["mappings"].items()) { const auto nativeCode = stringToKeyCode(key.c_str()); @@ -638,27 +431,27 @@ class UISampleApp final : public examples::SimpleWindowedApplication controller[nativeCode] = CVirtualGimbalEvent::stringToVirtualEvent(value.get()); } } - else + else { logFail("Expected \"mappings\" keyword for keyboard controller definition!"); return false; } } } - else + else { logFail("Expected \"keyboard\" keyword in controllers definition!"); return false; } - if (jControllers.contains("mouse")) + if (jControllers.contains("mouse")) { - for (const auto& jMouse : jControllers["mouse"]) + for (const auto& jMouse : jControllers["mouse"]) { - if (jMouse.contains("mappings")) + if (jMouse.contains("mappings")) { auto& controller = controllers.mouse.emplace_back(); - for (const auto& [key, value] : jMouse["mappings"].items()) + for (const auto& [key, value] : jMouse["mappings"].items()) { const auto nativeCode = stringToMouseCode(key.c_str()); @@ -671,20 +464,20 @@ class UISampleApp final : public examples::SimpleWindowedApplication controller[nativeCode] = CVirtualGimbalEvent::stringToVirtualEvent(value.get()); } } - else + else { logFail("Expected \"mappings\" keyword for mouse controller definition!"); return false; } } } - else + else { logFail("Expected \"mouse\" keyword in controllers definition"); return false; } } - else + else { logFail("Expected \"controllers\" keyword in controllers JSON"); return false; @@ -762,7 +555,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication for (uint32_t i = 0u; i < windowControlBinding.size(); ++i) { auto& binding = windowControlBinding[i]; - + auto& planar = m_planarProjections[binding.activePlanarIx = 0]; binding.pickDefaultProjections(planar->getPlanarProjections()); @@ -773,6 +566,211 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } + // Create asset manager + m_assetManager = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + + // First create the resources that don't depend on a swapchain + m_semaphore = m_device->createSemaphore(m_realFrameIx); + if (!m_semaphore) + return logFail("Failed to Create a Semaphore!"); + + // The nice thing about having a triple buffer is that you don't need to do acrobatics to account for the formats available to the surface. + // You can transcode to the swapchain's format while copying, and I actually recommend to do surface rotation, tonemapping and OETF application there. + const auto format = asset::EF_R8G8B8A8_SRGB; + // Could be more clever and use the copy Triple Buffer to Swapchain as an opportunity to do a MSAA resolve or something + const auto samples = IGPUImage::ESCF_1_BIT; + + // Create the renderpass + { + const IGPURenderpass::SCreationParams::SColorAttachmentDescription colorAttachments[] = { + {{ + { + .format = format, + .samples = samples, + .mayAlias = false + }, + /*.loadOp = */IGPURenderpass::LOAD_OP::CLEAR, + /*.storeOp = */IGPURenderpass::STORE_OP::STORE, + /*.initialLayout = */IGPUImage::LAYOUT::UNDEFINED, // because we clear we don't care about contents when we grab the triple buffer img again + /*.finalLayout = */IGPUImage::LAYOUT::TRANSFER_SRC_OPTIMAL // put it already in the correct layout for the blit operation + }}, + IGPURenderpass::SCreationParams::ColorAttachmentsEnd + }; + IGPURenderpass::SCreationParams::SSubpassDescription subpasses[] = { + {}, + IGPURenderpass::SCreationParams::SubpassesEnd + }; + subpasses[0].colorAttachments[0] = { .render = {.attachmentIndex = 0,.layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL} }; + // We actually need external dependencies to ensure ordering of the Implicit Layout Transitions relative to the semaphore signals + IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = { + // wipe-transition to ATTACHMENT_OPTIMAL + { + .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .dstSubpass = 0, + .memoryBarrier = { + // we can have NONE as Sources because the semaphore wait is ALL_COMMANDS + // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 + .dstStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + } + // leave view offsets and flags default + }, + // ATTACHMENT_OPTIMAL to PRESENT_SRC + { + .srcSubpass = 0, + .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .memoryBarrier = { + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + // we can have NONE as the Destinations because the semaphore signal is ALL_COMMANDS + // https://github.com/KhronosGroup/Vulkan-Docs/issues/2319 + } + // leave view offsets and flags default + }, + IGPURenderpass::SCreationParams::DependenciesEnd + }; + + IGPURenderpass::SCreationParams params = {}; + params.colorAttachments = colorAttachments; + params.subpasses = subpasses; + params.dependencies = dependencies; + m_renderpass = m_device->createRenderpass(params); + if (!m_renderpass) + return logFail("Failed to Create a Renderpass!"); + } + + // We just live life in easy mode and have the Swapchain Creation Parameters get deduced from the surface. + // We don't need any control over the format of the swapchain because we'll be only using Renderpasses this time! + // TODO: improve the queue allocation/choice and allocate a dedicated presentation queue to improve responsiveness and race to present. + if (!m_surface || !m_surface->init(m_surface->pickQueue(m_device.get()), std::make_unique(), {})) + return logFail("Failed to Create a Swapchain!"); + + // Normally you'd want to recreate these images whenever the swapchain is resized in some increment, like 64 pixels or something. + // But I'm super lazy here and will just create "worst case sized images" and waste all the VRAM I can get. + const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); + for (auto i = 0; i < MaxFramesInFlight; i++) + { + auto& image = m_tripleBuffers[i]; + { + IGPUImage::SCreationParams params = {}; + params = asset::IImage::SCreationParams{ + .type = IGPUImage::ET_2D, + .samples = samples, + .format = format, + .extent = {dpyInfo.resX,dpyInfo.resY,1}, + .mipLevels = 1, + .arrayLayers = 1, + .flags = IGPUImage::ECF_NONE, + // in this example I'll be using a renderpass to clear the image, and then a blit to copy it to the swapchain + .usage = IGPUImage::EUF_RENDER_ATTACHMENT_BIT | IGPUImage::EUF_TRANSFER_SRC_BIT + }; + image = m_device->createImage(std::move(params)); + if (!image) + return logFail("Failed to Create Triple Buffer Image!"); + + // use dedicated allocations, we have plenty of allocations left, even on Win32 + if (!m_device->allocate(image->getMemoryReqs(), image.get()).isValid()) + return logFail("Failed to allocate Device Memory for Image %d", i); + } + image->setObjectDebugName(("Triple Buffer Image " + std::to_string(i)).c_str()); + + // create framebuffers for the images + { + auto imageView = m_device->createImageView({ + .flags = IGPUImageView::ECF_NONE, + // give it a Transfer SRC usage flag so we can transition to the Tranfer SRC layout with End Renderpass + .subUsages = IGPUImage::EUF_RENDER_ATTACHMENT_BIT | IGPUImage::EUF_TRANSFER_SRC_BIT, + .image = core::smart_refctd_ptr(image), + .viewType = IGPUImageView::ET_2D, + .format = format + }); + const auto& imageParams = image->getCreationParameters(); + IGPUFramebuffer::SCreationParams params = { { + .renderpass = core::smart_refctd_ptr(m_renderpass), + .depthStencilAttachments = nullptr, + .colorAttachments = &imageView.get(), + .width = imageParams.extent.width, + .height = imageParams.extent.height, + .layers = imageParams.arrayLayers + } }; + m_framebuffers[i] = m_device->createFramebuffer(std::move(params)); + if (!m_framebuffers[i]) + return logFail("Failed to Create a Framebuffer for Image %d", i); + } + } + + // This time we'll create all CommandBuffers from one CommandPool, to keep life simple. However the Pool must support individually resettable CommandBuffers + // because they cannot be pre-recorded because the fraembuffers/swapchain images they use will change when a swapchain recreates. + auto pool = m_device->createCommandPool(getGraphicsQueue()->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + if (!pool || !pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { m_cmdBufs.data(),MaxFramesInFlight }, core::smart_refctd_ptr(m_logger))) + return logFail("Failed to Create CommandBuffers!"); + + // UI + { + { + nbl::ext::imgui::UI::SCreationParameters params; + params.resources.texturesInfo = { .setIx = 0u, .bindingIx = 0u }; + params.resources.samplersInfo = { .setIx = 0u, .bindingIx = 1u }; + params.assetManager = m_assetManager; + params.pipelineCache = nullptr; + params.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, TotalUISampleTexturesAmount); + params.renderpass = smart_refctd_ptr(m_renderpass); + params.streamingBuffer = nullptr; + params.subpassIx = 0u; + params.transfer = getTransferUpQueue(); + params.utilities = m_utils; + + m_ui.manager = nbl::ext::imgui::UI::create(std::move(params)); + } + + if (!m_ui.manager) + return false; + + // note that we use default layout provided by our extension, but you are free to create your own by filling nbl::ext::imgui::UI::S_CREATION_PARAMETERS::resources + const auto* descriptorSetLayout = m_ui.manager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); + + IDescriptorPool::SCreateInfo descriptorPoolInfo = {}; + descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = (uint32_t)nbl::ext::imgui::UI::DefaultSamplerIx::COUNT; + descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = TotalUISampleTexturesAmount; + descriptorPoolInfo.maxSets = 1u; + descriptorPoolInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT; + + m_descriptorSetPool = m_device->createDescriptorPool(std::move(descriptorPoolInfo)); + assert(m_descriptorSetPool); + + m_descriptorSetPool->createDescriptorSets(1u, &descriptorSetLayout, &m_ui.descriptorSet); + assert(m_ui.descriptorSet); + + m_ui.manager->registerListener([this]() -> void { imguiListen(); }); + } + + // Geometry Creator Render Scene FBOs + { + resources = ResourcesBundle::create(m_device.get(), m_logger.get(), getGraphicsQueue(), m_assetManager->getGeometryCreator()); + + if (!resources) + { + logFail("Could not create geometry creator gpu resources!"); + return false; + } + + const auto dpyInfo = m_winMgr->getPrimaryDisplayInfo(); + + for (uint32_t i = 0u; i < windowControlBinding.size(); ++i) + { + auto& scene = windowControlBinding[i].scene; + scene = CScene::create(smart_refctd_ptr(m_device), smart_refctd_ptr(m_logger), getGraphicsQueue(), smart_refctd_ptr(resources), dpyInfo.resX, dpyInfo.resY); + + if (!scene) + { + logFail("Could not create geometry creator scene[%d]!", i); + return false; + } + } + } + + oracle.reportBeginFrameRecord(); + if (base_t::argv.size() >= 3 && argv[1] == "-timeout_seconds") timeout = std::chrono::seconds(std::atoi(argv[2].c_str())); start = clock_t::now(); From db0b2af9c016951da7953b84a6c28aef26872738 Mon Sep 17 00:00:00 2001 From: YasInvolved Date: Sun, 22 Dec 2024 21:36:54 +0100 Subject: [PATCH 78/84] switch to system cursor when outside of viewport --- 61_UI/main.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 195505e11..e047505df 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1663,6 +1663,22 @@ class UISampleApp final : public examples::SimpleWindowedApplication } } + { + ImGuiIO& io = ImGui::GetIO(); + ImVec2 mousePos = ImGui::GetMousePos(); + ImVec2 viewportSize = io.DisplaySize; + auto* cc = m_window->getCursorControl(); + + if (mousePos.x < 0.0f || mousePos.y < 0.0f || mousePos.x > viewportSize.x || mousePos.y > viewportSize.y) + { + cc->setVisible(true); + } + else + { + cc->setVisible(false); + } + } + ImGui::End(); } } From 5a888aa4ae8e1afaaba77a8061d147395a90f280 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 23 Dec 2024 08:29:01 +0100 Subject: [PATCH 79/84] ImGuizmo last invocation cache needs to be inspected, add a few comments --- 61_UI/main.cpp | 42 ++++++++++++++++++--- common/include/camera/IGimbalController.hpp | 16 ++++---- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 46c0c2040..13e34404b 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1143,6 +1143,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication size_t gizmoIx = {}; size_t manipulationCounter = {}; + const std::optional modelInUseIx = ImGuizmo::IsUsingAny() ? std::optional(boundPlanarCameraIxToManipulate.has_value() ? 1u + boundPlanarCameraIxToManipulate.value() : 0u) : std::optional(std::nullopt); + for (uint32_t windowIx = 0; windowIx < windowControlBinding.size(); ++windowIx) { // setup imgui window @@ -1214,11 +1216,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication { ImGuizmo::PushID(gizmoIx); ++gizmoIx; - const bool isCameraGimbalTarget = modelIx; // I assume scene demo model is 0th ix, left are cameras + const bool isCameraGimbalTarget = modelIx; // I assume scene demo model is 0th ix, left are planar cameras ICamera* const targetGimbalManipulationCamera = isCameraGimbalTarget ? m_planarProjections[modelIx - 1u]->getCamera() : nullptr; bool discard = isCameraGimbalTarget && mCurrentGizmoOperation != ImGuizmo::TRANSLATE; // discard WiP stuff // if we try to manipulate a camera which appears to be the same camera we see scene from then obvsly it doesn't make sense to manipulate its gizmo so we skip it + // EDIT: it actually makes some sense if you assume render planar view is rendered with ortho projection, but we would need to add imguizmo controller virtual map + // to ban forward/backward in this mode if this condition is true if (targetGimbalManipulationCamera == planarViewCameraBound) { ImGuizmo::PopID(); @@ -1242,11 +1246,38 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (success) { ++manipulationCounter; - discard &= manipulationCounter > 1; + discard |= manipulationCounter > 1; + + /* + + NOTE to self & TODO: I must be killing ImGuizmo last invocation cache or something with my delta events (or at least I dont + see now where I'm *maybe* using its API incorrectly, the problem is I get translation parts in delta matrix when doing + rotations hence it glitches my cameras -> on the other hand I can see it kinda correctly outputs the delta matrix when + gc model is bound - // TMP WIP, our imguizmo controller doesnt support rotation & scale yet and its because - // - there are numerical issues with imguizmo decompose/recompose TRS (and the author also says it) - // - in rotate mode delta TRS matrix contains translate part (how? no idea, maybe imguizmo bug) and it glitches everything + auto postprocessDeltaManipulation = [](const float32_t4x4& inDeltaMatrix, float32_t4x4& outDeltaMatrix, ImGuizmo::OPERATION operation) -> void + { + struct + { + float32_t3 dTranslation, dRotation, dScale; + } world; + + ImGuizmo::DecomposeMatrixToComponents(&inDeltaMatrix[0][0], &world.dTranslation[0], &world.dRotation[0], &world.dScale[0]); + + if (operation == ImGuizmo::TRANSLATE) + { + world.dRotation = float32_t3(0, 0, 0); + } + else if (operation == ImGuizmo::ROTATE) + { + world.dTranslation = float32_t3(0, 0, 0); + } + + ImGuizmo::RecomposeMatrixFromComponents(&world.dTranslation[0], &world.dRotation[0], &world.dScale[0], &outDeltaMatrix[0][0]); + }; + + postprocessDeltaManipulation(imguizmoModel.outDeltaTRS, imguizmoModel.outDeltaTRS, mCurrentGizmoOperation); + */ if (!discard) { @@ -1832,7 +1863,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication } ImGui::End(); - { // generate virtual events given delta TRS matrix if (boundCameraToManipulate) diff --git a/common/include/camera/IGimbalController.hpp b/common/include/camera/IGimbalController.hpp index c7a0c507a..841a5291b 100644 --- a/common/include/camera/IGimbalController.hpp +++ b/common/include/camera/IGimbalController.hpp @@ -387,9 +387,7 @@ class IGimbalController : public IGimbalManipulateEncoder float32_t3 dTranslation, dRotation, dScale; } world; - // TODO: since I assume the delta matrix is from imguizmo I must follow its API (but this one I could actually steal & rewrite to Nabla to not call imguizmo stuff here <- its TEMP!!!!!!!) - // - there are numerical issues with imguizmo decompose/recompose TRS (and the author also says it, we should have this util in Nabla and work with doubles not floats) - // - in rotate mode delta TRS matrix contains translate part (how? no idea, maybe imguizmo bug) and it glitches everything, I get translations e-07 or something + // TODO: write it in Nabla, this is temp ImGuizmo::DecomposeMatrixToComponents(&deltaWorldTRS[0][0], &world.dTranslation[0], &world.dRotation[0], &world.dScale[0]); // Delta translation impulse @@ -398,14 +396,14 @@ class IGimbalController : public IGimbalManipulateEncoder requestMagnitudeUpdateWithScalar(0.f, world.dTranslation[2], std::abs(world.dTranslation[2]), gimbal_event_t::MoveForward, gimbal_event_t::MoveBackward, m_imguizmoVirtualEventMap); // Delta rotation impulse - //requestMagnitudeUpdateWithScalar(0.f, world.dRotation[0], std::abs(world.dRotation[0]), gimbal_event_t::TiltUp , gimbal_event_t::TiltDown, m_imguizmoVirtualEventMap); - //requestMagnitudeUpdateWithScalar(0.f, world.dRotation[1], std::abs(world.dRotation[1]), gimbal_event_t::PanRight, gimbal_event_t::PanLeft, m_imguizmoVirtualEventMap); - //requestMagnitudeUpdateWithScalar(0.f, world.dRotation[2], std::abs(world.dRotation[2]), gimbal_event_t::RollRight, gimbal_event_t::RollLeft, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, world.dRotation[0], std::abs(world.dRotation[0]), gimbal_event_t::TiltUp , gimbal_event_t::TiltDown, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, world.dRotation[1], std::abs(world.dRotation[1]), gimbal_event_t::PanRight, gimbal_event_t::PanLeft, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(0.f, world.dRotation[2], std::abs(world.dRotation[2]), gimbal_event_t::RollRight, gimbal_event_t::RollLeft, m_imguizmoVirtualEventMap); // Delta scale impulse - //requestMagnitudeUpdateWithScalar(1.f, world.dScale[0], std::abs(world.dScale[0]), gimbal_event_t::ScaleXInc, gimbal_event_t::ScaleXDec, m_imguizmoVirtualEventMap); - //requestMagnitudeUpdateWithScalar(1.f, world.dScale[1], std::abs(world.dScale[1]), gimbal_event_t::ScaleYInc, gimbal_event_t::ScaleYDec, m_imguizmoVirtualEventMap); - //requestMagnitudeUpdateWithScalar(1.f, world.dScale[2], std::abs(world.dScale[2]), gimbal_event_t::ScaleZInc, gimbal_event_t::ScaleZDec, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(1.f, world.dScale[0], std::abs(world.dScale[0]), gimbal_event_t::ScaleXInc, gimbal_event_t::ScaleXDec, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(1.f, world.dScale[1], std::abs(world.dScale[1]), gimbal_event_t::ScaleYInc, gimbal_event_t::ScaleYDec, m_imguizmoVirtualEventMap); + requestMagnitudeUpdateWithScalar(1.f, world.dScale[2], std::abs(world.dScale[2]), gimbal_event_t::ScaleZInc, gimbal_event_t::ScaleZDec, m_imguizmoVirtualEventMap); } postprocess(m_imguizmoVirtualEventMap, output, count); From 374f5cf2a8aec7e6ed95fb2920f7b68afb28f4e7 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 23 Dec 2024 08:55:20 +0100 Subject: [PATCH 80/84] fix a little loading bug after the merge --- 61_UI/main.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 12b914614..a32c6aff8 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -255,7 +255,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication argparse::ArgumentParser program("Virtual camera event system demo"); program.add_argument("--file") - .required() .help("Path to json file with camera inputs"); try @@ -276,13 +275,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; { - const auto cameraJsonFile = program.get("--file"); + const std::optional cameraJsonFile = program.is_used("--file") ? program.get("--file") : std::optional(std::nullopt); json j; - std::ifstream file(cameraJsonFile.c_str()); + auto file = cameraJsonFile.has_value() ? std::ifstream(cameraJsonFile.value()) : std::ifstream(); if (!file.is_open()) { - m_logger->log("Cannot open input \"%s\" json file. Switching to default config.", ILogger::ELL_WARNING, cameraJsonFile.c_str()); + if (cameraJsonFile.has_value()) + m_logger->log("Cannot open input \"%s\" json file. Switching to default config.", ILogger::ELL_WARNING, cameraJsonFile.value().c_str()); + else + m_logger->log("No input json file provided. Switching to default config.", ILogger::ELL_WARNING); + auto assets = make_smart_refctd_ptr(smart_refctd_ptr(m_logger)); auto pFile = assets->getFile("cameras.json", IFile::ECF_READ, ""); From 6ed9686297804ba5df97a5d3cb5fbf89c3ddbae0 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 23 Dec 2024 09:14:56 +0100 Subject: [PATCH 81/84] minor visual bug with cursor --- 61_UI/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/61_UI/main.cpp b/61_UI/main.cpp index a32c6aff8..ad4801aa9 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -1705,7 +1705,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (mousePos.x < 0.0f || mousePos.y < 0.0f || mousePos.x > viewportSize.x || mousePos.y > viewportSize.y) { - cc->setVisible(true); + if (not enableActiveCameraMovement) + cc->setVisible(true); } else { From 6231737b62576453c0ef3b5e9cb2e33831aceb77 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 23 Dec 2024 12:16:08 +0100 Subject: [PATCH 82/84] add new Free Lock camera to the scene, update cameras.json input, fix active window FBO rendering bugs in full screen mode --- 61_UI/app_resources/cameras.json | 36 +++- 61_UI/include/common.hpp | 1 + 61_UI/main.cpp | 188 ++++++++++---------- common/include/camera/CFPSCamera.hpp | 26 +-- common/include/camera/CFreeLockCamera.hpp | 143 +++++++++++++++ common/include/camera/ICamera.hpp | 22 ++- common/include/camera/IGimbalController.hpp | 5 - 7 files changed, 292 insertions(+), 129 deletions(-) create mode 100644 common/include/camera/CFreeLockCamera.hpp diff --git a/61_UI/app_resources/cameras.json b/61_UI/app_resources/cameras.json index 5f8d5b489..18ab7d5b6 100644 --- a/61_UI/app_resources/cameras.json +++ b/61_UI/app_resources/cameras.json @@ -11,7 +11,7 @@ "orientation": [0.047, 0.830, -0.072, 0.55] }, { - "type": "FPS", + "type": "Free", "position": [2.116, 0.826, 1.152], "orientation": [0.095, -0.835, 0.152, 0.521] } @@ -21,13 +21,13 @@ "type": "perspective", "fov": 60.0, "zNear": 0.1, - "zFar": 100.0 + "zFar": 110.0 }, { "type": "orthographic", "orthoWidth": 10.0, "zNear": 0.1, - "zFar": 100.0 + "zFar": 110.0 } ], "viewports": [ @@ -44,6 +44,18 @@ "keyboard": 1, "mouse": 0 } + }, + { + "projection": 0, + "controllers": { + "keyboard": 2 + } + }, + { + "projection": 1, + "controllers": { + "keyboard": 1 + } } ], "planars": [ @@ -57,7 +69,7 @@ }, { "camera": 2, - "viewports": [0, 1] + "viewports": [2, 3] } ], "controllers": { @@ -81,6 +93,22 @@ "A": "MoveLeft", "D": "MoveRight" } + }, + { + "mappings": { + "W": "MoveForward", + "S": "MoveBackward", + "A": "MoveLeft", + "D": "MoveRight", + "E": "MoveUp", + "Q": "MoveDown", + "I": "TiltDown", + "K": "TiltUp", + "J": "PanLeft", + "L": "PanRight", + "U": "RollRight", + "O": "RollLeft" + } } ], "mouse": [ diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index 4033afc14..203bd4ed9 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -7,6 +7,7 @@ // common api #include "camera/CFPSCamera.hpp" +#include "camera/CFreeLockCamera.hpp" #include "SimpleWindowedApplication.hpp" #include "InputSystem.hpp" diff --git a/61_UI/main.cpp b/61_UI/main.cpp index ad4801aa9..eb8e7e196 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -306,38 +306,38 @@ class UISampleApp final : public examples::SimpleWindowedApplication { if (jCamera.contains("type")) { - if (jCamera["type"] == "FPS") + if (!jCamera.contains("position")) { - if (!jCamera.contains("position")) - { - logFail("Expected \"position\" keyword for camera definition!"); - return false; - } + logFail("Expected \"position\" keyword for camera definition!"); + return false; + } - if (!jCamera.contains("orientation")) - { - logFail("Expected \"orientation\" keyword for camera definition!"); - return false; - } + if (!jCamera.contains("orientation")) + { + logFail("Expected \"orientation\" keyword for camera definition!"); + return false; + } - auto position = [&]() - { - auto jret = jCamera["position"].get>(); - return float32_t3(jret[0], jret[1], jret[2]); - }(); + auto position = [&]() + { + auto jret = jCamera["position"].get>(); + return float32_t3(jret[0], jret[1], jret[2]); + }(); - auto orientation = [&]() - { - auto jret = jCamera["orientation"].get>(); + auto orientation = [&]() + { + auto jret = jCamera["orientation"].get>(); - // order important for glm::quat, - // the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) - // but memory layout (and json) is x,y,z,w - return glm::quat(jret[3], jret[0], jret[1], jret[2]); - }(); + // order important for glm::quat, + // the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) + // but memory layout (and json) is x,y,z,w + return glm::quat(jret[3], jret[0], jret[1], jret[2]); + }(); + if (jCamera["type"] == "FPS") cameras.emplace_back() = make_smart_refctd_ptr(position, orientation); - } + else if (jCamera["type"] == "Free") + cameras.emplace_back() = make_smart_refctd_ptr(position, orientation); else { logFail("Unsupported camera type!"); @@ -383,7 +383,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication float fov = jProjection["fov"].get(); projections.emplace_back(IPlanarProjection::CProjection::create(zNear, zFar, fov)); - } else if (jProjection["type"] == "orthographic") { @@ -505,7 +504,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication const auto cameraIx = jPlanar["camera"].get(); auto boundViewports = jPlanar["viewports"].get>(); - auto& planars = m_planarProjections.emplace_back() = planar_projection_t::create(smart_refctd_ptr(cameras[cameraIx])); + auto& planar = m_planarProjections.emplace_back() = planar_projection_t::create(smart_refctd_ptr(cameras[cameraIx])); for (const auto viewportIx : boundViewports) { auto& viewport = j["viewports"][viewportIx]; @@ -515,28 +514,36 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - if (!viewport["controllers"].contains("keyboard")) + const auto projectionIx = viewport["projection"].get(); + auto& projection = planar->getPlanarProjections().emplace_back(projections[projectionIx]); + + const bool hasKeyboardBound = viewport["controllers"].contains("keyboard"); + const bool hasMouseBound = viewport["controllers"].contains("mouse"); + + if (hasKeyboardBound) { - logFail("\"keyboard\" value missing in controllers in viewport object index %d", viewportIx); - return false; + auto keyboardControllerIx = viewport["controllers"]["keyboard"].get(); + projection.updateKeyboardMapping([&](auto& map) { map = controllers.keyboard[keyboardControllerIx]; }); } + else + projection.updateKeyboardMapping([&](auto& map) { map = {}; }); // clean the map if not bound - if (!viewport["controllers"].contains("mouse")) + if (hasMouseBound) { - logFail("\"mouse\" value missing in controllers in viewport object index %d", viewportIx); - return false; + auto mouseControllerIx = viewport["controllers"]["mouse"].get(); + projection.updateMouseMapping([&](auto& map) { map = controllers.mouse[mouseControllerIx]; }); } + else + projection.updateMouseMapping([&](auto& map) { map = {}; }); // clean the map if not bound + } - auto keyboardIx = viewport["controllers"]["keyboard"].get(); - auto mouseIx = viewport["controllers"]["mouse"].get(); - - auto projectionIx = viewport["projection"].get(); - auto keyboardControllerIx = viewport["controllers"]["keyboard"].get(); - auto mouseControllerIx = viewport["controllers"]["mouse"].get(); - - auto& projection = planars->getPlanarProjections().emplace_back(projections[projectionIx]); - projection.updateKeyboardMapping([&](auto& map) { map = controllers.keyboard[keyboardControllerIx]; }); - projection.updateMouseMapping([&](auto& map) { map = controllers.mouse[mouseControllerIx]; }); + { + auto* camera = planar->getCamera(); + { + camera->updateKeyboardMapping([&](auto& map) { map = camera->getKeyboardMappingPreset(); }); + camera->updateMouseMapping([&](auto& map) { map = camera->getMouseMappingPreset(); }); + camera->updateImguizmoMapping([&](auto& map) { map = camera->getImguizmoMappingPreset(); }); + } } } } @@ -873,7 +880,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication for (auto binding : windowControlBinding) renderOfflineScene(binding.scene); else - renderOfflineScene(windowControlBinding.front().scene.get()); // just to not render to all at once + renderOfflineScene(windowControlBinding[activeRenderWindowIx].scene.get()); const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; const IGPUCommandBuffer::SRenderpassBeginInfo info = { @@ -1213,7 +1220,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); // I will assume we need to focus a window to start manipulating objects from it - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) activeRenderWindowIx = windowIx; // we render a scene from view of a camera bound to planar window @@ -1330,33 +1337,22 @@ class UISampleApp final : public examples::SimpleWindowedApplication } targetGimbalManipulationCamera->endInputProcessing(); - if (vCount) { - auto* fps = dynamic_cast(targetGimbalManipulationCamera); - - float pMoveSpeed = 1.f, pRotationSpeed = 1.f; + const float pMoveSpeed = targetGimbalManipulationCamera->getMoveSpeedScale(); + const float pRotationSpeed = targetGimbalManipulationCamera->getRotationSpeedScale(); - if (fps) - { - pMoveSpeed = fps->getMoveSpeedScale(); - pRotationSpeed = fps->getRotationSpeedScale(); + // I start to think controller should be able to set sensitivity to scale magnitudes of generated events + // in order for camera to not keep any magnitude scalars like move or rotation speed scales - // I start to think controller should be able to set sensitivity to scale magnitudes of generated events - // in order for camera to not keep any magnitude scalars like move or rotation speed scales - - fps->setMoveSpeedScale(1); - fps->setRotationSpeedScale(1); - } + targetGimbalManipulationCamera->setMoveSpeedScale(1); + targetGimbalManipulationCamera->setRotationSpeedScale(1); // NOTE: generated events from ImGuizmo controller are always in world space! targetGimbalManipulationCamera->manipulate({ virtualEvents.data(), vCount }, ICamera::World); - if (fps) - { - fps->setMoveSpeedScale(pMoveSpeed); - fps->setRotationSpeedScale(pRotationSpeed); - } + targetGimbalManipulationCamera->setMoveSpeedScale(pMoveSpeed); + targetGimbalManipulationCamera->setRotationSpeedScale(pRotationSpeed); } } } @@ -1419,10 +1415,26 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); // fully transparent fake window ImGui::Begin("FullScreenWindow", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs); const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetWindowPos(), cursorPos = ImGui::GetCursorScreenPos(); + { + auto& binding = windowControlBinding[activeRenderWindowIx]; + auto& planarBound = m_planarProjections[binding.activePlanarIx]; + assert(planarBound); + + binding.aspectRatio = contentRegionSize.x / contentRegionSize.y; + auto* planarViewCameraBound = planarBound->getCamera(); + + assert(planarViewCameraBound); + assert(binding.boundProjectionIx.has_value()); + + auto& projection = planarBound->getPlanarProjections()[binding.boundProjectionIx.value()]; + projection.update(binding.leftHandedProjection, binding.aspectRatio); + } ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); + + ImGui::End(); ImGui::PopStyleColor(1); } @@ -1574,7 +1586,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (useWindow) ImGui::Checkbox("Allow axes to flip##allowAxesToFlip", &active.allowGizmoAxesToFlip); - ImGui::Checkbox("Draw debug grid##drawDebugGrid", &active.enableDebugGridDraw); + if(useWindow) + ImGui::Checkbox("Draw debug grid##drawDebugGrid", &active.enableDebugGridDraw); if (ImGui::RadioButton("LH", active.leftHandedProjection)) active.leftHandedProjection = true; @@ -1584,6 +1597,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (ImGui::RadioButton("RH", not active.leftHandedProjection)) active.leftHandedProjection = false; + updateParameters.m_zNear = std::clamp(updateParameters.m_zNear, 0.1f, 100.f); + updateParameters.m_zFar = std::clamp(updateParameters.m_zFar, 110.f, 10000.f); + ImGui::SliderFloat("zNear", &updateParameters.m_zNear, 0.1f, 100.f, "%.2f", ImGuiSliderFlags_Logarithmic); ImGui::SliderFloat("zFar", &updateParameters.m_zFar, 110.f, 10000.f, "%.1f", ImGuiSliderFlags_Logarithmic); @@ -1659,22 +1675,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Text("Object Ix: %s", std::to_string(active.activePlanarIx + 1u).c_str()); ImGui::Separator(); { - auto* fps = dynamic_cast(boundCamera); - - if (fps) - { - float moveSpeed = fps->getMoveSpeedScale(); - float rotationSpeed = fps->getRotationSpeedScale(); + float moveSpeed = boundCamera->getMoveSpeedScale(); + float rotationSpeed = boundCamera->getRotationSpeedScale(); - ImGui::SliderFloat("Move speed factor", &moveSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); - ImGui::SliderFloat("Rotate speed factor", &rotationSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("Move speed factor", &moveSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("Rotate speed factor", &rotationSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); - fps->setMoveSpeedScale(moveSpeed); - fps->setRotationSpeedScale(rotationSpeed); - } + boundCamera->setMoveSpeedScale(moveSpeed); + boundCamera->setRotationSpeedScale(rotationSpeed); } - if (ImGui::TreeNodeEx("World Data", ImGuiTreeNodeFlags_None)) + if (ImGui::TreeNodeEx("World Data", flags)) { auto& gimbal = boundCamera->getGimbal(); const auto position = getCastedVector(gimbal.getPosition()); @@ -1932,28 +1943,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (vCount) { - auto* fps = dynamic_cast(boundCameraToManipulate.get()); - - float pmSpeed = 1.f; - float prSpeed = 1.f; + const float pmSpeed = boundCameraToManipulate->getMoveSpeedScale(); + const float prSpeed = boundCameraToManipulate->getRotationSpeedScale(); - if (fps) - { - pmSpeed = fps->getMoveSpeedScale(); - prSpeed = fps->getRotationSpeedScale(); - - fps->setMoveSpeedScale(1); - fps->setRotationSpeedScale(1); - } + boundCameraToManipulate->setMoveSpeedScale(1); + boundCameraToManipulate->setRotationSpeedScale(1); // NOTE: generated events from ImGuizmo controller are always in world space! boundCameraToManipulate->manipulate({ virtualEvents.data(), vCount }, ICamera::World); - if (fps) - { - fps->setMoveSpeedScale(pmSpeed); - fps->setRotationSpeedScale(prSpeed); - } + boundCameraToManipulate->setMoveSpeedScale(pmSpeed); + boundCameraToManipulate->setRotationSpeedScale(prSpeed); } } } diff --git a/common/include/camera/CFPSCamera.hpp b/common/include/camera/CFPSCamera.hpp index 3889e5921..cb00fe442 100644 --- a/common/include/camera/CFPSCamera.hpp +++ b/common/include/camera/CFPSCamera.hpp @@ -17,12 +17,7 @@ class CFPSCamera final : public ICamera using base_t = ICamera; CFPSCamera(const float64_t3& position, const glm::quat& orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) - : base_t(), m_gimbal({ .position = position, .orientation = orientation }) - { - updateKeyboardMapping([](auto& map) { map = m_keyboard_to_virtual_events_preset; }); - updateMouseMapping([](auto& map) { map = m_mouse_to_virtual_events_preset; }); - updateImguizmoMapping([](auto& map) { map = m_imguizmo_to_virtual_events_preset; }); - } + : base_t(), m_gimbal({ .position = position, .orientation = orientation }) {} ~CFPSCamera() = default; const base_t::keyboard_to_virtual_events_t getKeyboardMappingPreset() const override { return m_keyboard_to_virtual_events_preset; } @@ -86,28 +81,9 @@ class CFPSCamera final : public ICamera return "FPS Camera"; } - // (***) - inline void setMoveSpeedScale(double scalar) - { - m_moveSpeedScale = scalar; - } - - // (***) - inline void setRotationSpeedScale(double scalar) - { - m_rotationSpeedScale = scalar; - } - - inline double getMoveSpeedScale() const { return m_moveSpeedScale; } - inline double getRotationSpeedScale() const { return m_rotationSpeedScale; } - private: typename base_t::CGimbal m_gimbal; - // (***) TODO: I need to think whether a camera should own this or controllers should be able - // to set sensitivity to scale magnitudes of generated events we put into manipulate method - double m_moveSpeedScale = 0.01, m_rotationSpeedScale = 0.003; - static inline constexpr auto AllowedLocalVirtualEvents = CVirtualGimbalEvent::Translate | CVirtualGimbalEvent::TiltUp | CVirtualGimbalEvent::TiltDown | CVirtualGimbalEvent::PanRight | CVirtualGimbalEvent::PanLeft; static inline constexpr auto AllowedWorldVirtualEvents = AllowedLocalVirtualEvents; diff --git a/common/include/camera/CFreeLockCamera.hpp b/common/include/camera/CFreeLockCamera.hpp new file mode 100644 index 000000000..0788bb048 --- /dev/null +++ b/common/include/camera/CFreeLockCamera.hpp @@ -0,0 +1,143 @@ +// Copyright (C) 2018-2024 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _C_FREE_CAMERA_HPP_ +#define _C_FREE_CAMERA_HPP_ + +#include "ICamera.hpp" +#include + +namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE +{ + +// Free Lock Camera +class CFreeCamera final : public ICamera +{ +public: + using base_t = ICamera; + + CFreeCamera(const float64_t3& position, const glm::quat& orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + : base_t(), m_gimbal({ .position = position, .orientation = orientation }) {} + ~CFreeCamera() = default; + + const base_t::keyboard_to_virtual_events_t getKeyboardMappingPreset() const override { return m_keyboard_to_virtual_events_preset; } + const base_t::mouse_to_virtual_events_t getMouseMappingPreset() const override { return m_mouse_to_virtual_events_preset; } + const base_t::imguizmo_to_virtual_events_t getImguizmoMappingPreset() const override { return m_imguizmo_to_virtual_events_preset; } + + const typename base_t::CGimbal& getGimbal() override + { + return m_gimbal; + } + + virtual bool manipulate(std::span virtualEvents, base_t::ManipulationMode mode) override + { + if (virtualEvents.empty()) + return false; + + const auto gOrientation = m_gimbal.getOrientation(); + + // TODO: I make assumption what world base is now (at least temporary), I need to think of this but maybe each ITransformObject should know what its world is + // in ideal scenario we would define this crazy enum with all possible standard bases + const auto impulse = mode == base_t::Local ? m_gimbal.accumulate(virtualEvents) + : m_gimbal.accumulate(virtualEvents, { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 }); + + const auto pitchQuat = glm::angleAxis(impulse.dVirtualRotation.x * m_rotationSpeedScale, glm::vec3(1.0f, 0.0f, 0.0f)); + const auto yawQuat = glm::angleAxis(impulse.dVirtualRotation.y * m_rotationSpeedScale, glm::vec3(0.0f, 1.0f, 0.0f)); + const auto rollQuat = glm::angleAxis(impulse.dVirtualRotation.z * m_rotationSpeedScale, glm::vec3(0.0f, 0.0f, 1.0f)); + + const auto newOrientation = glm::normalize(gOrientation * yawQuat * pitchQuat * rollQuat); + const float64_t3 newPosition = m_gimbal.getPosition() + impulse.dVirtualTranslate * m_moveSpeedScale; + + bool manipulated = true; + + m_gimbal.begin(); + { + m_gimbal.setOrientation(newOrientation); + m_gimbal.setPosition(newPosition); + } + m_gimbal.end(); + + manipulated &= bool(m_gimbal.getManipulationCounter()); + + if (manipulated) + m_gimbal.updateView(); + + return manipulated; + } + + virtual const uint32_t getAllowedVirtualEvents(base_t::ManipulationMode mode) override + { + switch (mode) + { + case base_t::Local: return AllowedLocalVirtualEvents; + case base_t::World: return AllowedWorldVirtualEvents; + default: return CVirtualGimbalEvent::None; + } + } + + virtual const std::string_view getIdentifier() override + { + return "Free-Look Camera"; + } + +private: + typename base_t::CGimbal m_gimbal; + + static inline constexpr auto AllowedLocalVirtualEvents = CVirtualGimbalEvent::Translate | CVirtualGimbalEvent::Rotate; + static inline constexpr auto AllowedWorldVirtualEvents = AllowedLocalVirtualEvents; + + static inline const auto m_keyboard_to_virtual_events_preset = []() + { + typename base_t::keyboard_to_virtual_events_t preset; + + preset[ui::E_KEY_CODE::EKC_W] = CVirtualGimbalEvent::MoveForward; + preset[ui::E_KEY_CODE::EKC_S] = CVirtualGimbalEvent::MoveBackward; + preset[ui::E_KEY_CODE::EKC_A] = CVirtualGimbalEvent::MoveLeft; + preset[ui::E_KEY_CODE::EKC_D] = CVirtualGimbalEvent::MoveRight; + preset[ui::E_KEY_CODE::EKC_I] = CVirtualGimbalEvent::TiltDown; + preset[ui::E_KEY_CODE::EKC_K] = CVirtualGimbalEvent::TiltUp; + preset[ui::E_KEY_CODE::EKC_J] = CVirtualGimbalEvent::PanLeft; + preset[ui::E_KEY_CODE::EKC_L] = CVirtualGimbalEvent::PanRight; + preset[ui::E_KEY_CODE::EKC_Q] = CVirtualGimbalEvent::RollLeft; + preset[ui::E_KEY_CODE::EKC_E] = CVirtualGimbalEvent::RollRight; + + return preset; + }(); + + static inline const auto m_mouse_to_virtual_events_preset = []() + { + typename base_t::mouse_to_virtual_events_t preset; + + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_X] = CVirtualGimbalEvent::PanRight; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_X] = CVirtualGimbalEvent::PanLeft; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_Y] = CVirtualGimbalEvent::TiltUp; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_Y] = CVirtualGimbalEvent::TiltDown; + + return preset; + }(); + + static inline const auto m_imguizmo_to_virtual_events_preset = []() + { + typename base_t::imguizmo_to_virtual_events_t preset; + + preset[CVirtualGimbalEvent::MoveForward] = CVirtualGimbalEvent::MoveForward; + preset[CVirtualGimbalEvent::MoveBackward] = CVirtualGimbalEvent::MoveBackward; + preset[CVirtualGimbalEvent::MoveLeft] = CVirtualGimbalEvent::MoveLeft; + preset[CVirtualGimbalEvent::MoveRight] = CVirtualGimbalEvent::MoveRight; + preset[CVirtualGimbalEvent::MoveUp] = CVirtualGimbalEvent::MoveUp; + preset[CVirtualGimbalEvent::MoveDown] = CVirtualGimbalEvent::MoveDown; + preset[CVirtualGimbalEvent::TiltDown] = CVirtualGimbalEvent::TiltDown; + preset[CVirtualGimbalEvent::TiltUp] = CVirtualGimbalEvent::TiltUp; + preset[CVirtualGimbalEvent::PanLeft] = CVirtualGimbalEvent::PanLeft; + preset[CVirtualGimbalEvent::PanRight] = CVirtualGimbalEvent::PanRight; + preset[CVirtualGimbalEvent::RollLeft] = CVirtualGimbalEvent::RollLeft; + preset[CVirtualGimbalEvent::RollRight] = CVirtualGimbalEvent::RollRight; + + return preset; + }(); +}; + +} + +#endif // _C_FREE_CAMERA_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICamera.hpp b/common/include/camera/ICamera.hpp index 33890fef1..9af3e27d9 100644 --- a/common/include/camera/ICamera.hpp +++ b/common/include/camera/ICamera.hpp @@ -71,7 +71,7 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted }; ICamera() {} - ~ICamera() = default; + virtual ~ICamera() = default; // Returns a gimbal which *models the camera view* virtual const CGimbal& getGimbal() = 0u; @@ -85,6 +85,26 @@ class ICamera : public IGimbalController, virtual public core::IReferenceCounted // Identifier of a camera type virtual const std::string_view getIdentifier() = 0u; + + // (***) + inline void setMoveSpeedScale(double scalar) + { + m_moveSpeedScale = scalar; + } + + // (***) + inline void setRotationSpeedScale(double scalar) + { + m_rotationSpeedScale = scalar; + } + + inline double getMoveSpeedScale() const { return m_moveSpeedScale; } + inline double getRotationSpeedScale() const { return m_rotationSpeedScale; } + +protected: + // (***) TODO: I need to think whether a camera should own this or controllers should be able + // to set sensitivity to scale magnitudes of generated events we put into manipulate method + double m_moveSpeedScale = 0.01, m_rotationSpeedScale = 0.003; }; } diff --git a/common/include/camera/IGimbalController.hpp b/common/include/camera/IGimbalController.hpp index 841a5291b..770b5ed16 100644 --- a/common/include/camera/IGimbalController.hpp +++ b/common/include/camera/IGimbalController.hpp @@ -76,13 +76,8 @@ struct IGimbalManipulateEncoder using mouse_to_virtual_events_t = std::unordered_map; using imguizmo_to_virtual_events_t = std::unordered_map; - //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map virtual const keyboard_to_virtual_events_t getKeyboardMappingPreset() const { return {}; } - - //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map virtual const mouse_to_virtual_events_t getMouseMappingPreset() const { return {}; } - - //! default preset with encode_keyboard_code_t to gimbal_virtual_event_t map virtual const imguizmo_to_virtual_events_t getImguizmoMappingPreset() const { return {}; } virtual const keyboard_to_virtual_events_t& getKeyboardVirtualEventMap() const = 0; From 38168d3a1b461b45a3861762a96516f5f2b1f134 Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 23 Dec 2024 15:04:11 +0100 Subject: [PATCH 83/84] make default positioning sexy, update input constants --- 61_UI/app_resources/cameras.json | 8 +-- 61_UI/main.cpp | 83 ++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/61_UI/app_resources/cameras.json b/61_UI/app_resources/cameras.json index 18ab7d5b6..3e9769e73 100644 --- a/61_UI/app_resources/cameras.json +++ b/61_UI/app_resources/cameras.json @@ -2,8 +2,8 @@ "cameras": [ { "type": "FPS", - "position": [-2.238, 1.438, -1.558], - "orientation": [0.204, 0.385, -0.088, 0.896] + "position": [-2.438, 1.995, -3.130], + "orientation": [0.195, 0.311, -0.065, 0.928] }, { "type": "FPS", @@ -19,13 +19,13 @@ "projections": [ { "type": "perspective", - "fov": 60.0, + "fov": 40.0, "zNear": 0.1, "zFar": 110.0 }, { "type": "orthographic", - "orthoWidth": 10.0, + "orthoWidth": 16.0, "zNear": 0.1, "zFar": 110.0 } diff --git a/61_UI/main.cpp b/61_UI/main.cpp index eb8e7e196..2d630b8d1 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -752,6 +752,28 @@ class UISampleApp final : public examples::SimpleWindowedApplication assert(m_ui.descriptorSet); m_ui.manager->registerListener([this]() -> void { imguiListen(); }); + { + const auto ds = float32_t2{ m_window->getWidth(), m_window->getHeight() }; + + wInit.trsEditor.iPos = iPaddingOffset; + wInit.trsEditor.iSize = { ds.x * 0.1, ds.y - wInit.trsEditor.iPos.y * 2 }; + + wInit.planars.iSize = { ds.x * 0.2, ds.y - iPaddingOffset.y * 2 }; + wInit.planars.iPos = { ds.x - wInit.planars.iSize.x - iPaddingOffset.x, 0 + iPaddingOffset.y }; + + { + float leftX = wInit.trsEditor.iPos.x + wInit.trsEditor.iSize.x + iPaddingOffset.x; + float eachXSize = wInit.planars.iPos.x - (wInit.trsEditor.iPos.x + wInit.trsEditor.iSize.x) - 2*iPaddingOffset.x; + float eachYSize = (ds.y - 2 * iPaddingOffset.y - (wInit.renderWindows.size() - 1) * iPaddingOffset.y) / wInit.renderWindows.size(); + + for (size_t i = 0; i < wInit.renderWindows.size(); ++i) + { + auto& rw = wInit.renderWindows[i]; + rw.iPos = { leftX, (1+i) * iPaddingOffset.y + i * eachYSize }; + rw.iSize = { eachXSize, eachYSize }; + } + } + } } // Geometry Creator Render Scene FBOs @@ -1177,16 +1199,30 @@ class UISampleApp final : public examples::SimpleWindowedApplication for (uint32_t windowIx = 0; windowIx < windowControlBinding.size(); ++windowIx) { - // setup imgui window - ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_Appearing); - ImGui::SetNextWindowPos(ImVec2(400, 20 + windowIx * 420), ImGuiCond_Appearing); + // setup + { + const auto& rw = wInit.renderWindows[windowIx]; + ImGui::SetNextWindowPos({ rw.iPos.x, rw.iPos.y }, ImGuiCond_Appearing); + ImGui::SetNextWindowSize({ rw.iSize.x, rw.iSize.y }, ImGuiCond_Appearing); + } ImGui::SetNextWindowSizeConstraints(ImVec2(0x45, 0x45), ImVec2(7680, 4320)); ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImVec4)ImColor(0.35f, 0.3f, 0.3f)); const std::string ident = "Render Window \"" + std::to_string(windowIx) + "\""; - ImGui::Begin(ident.data(), 0, ImGuiWindowFlags_NoMove); + + ImGui::Begin(ident.data(), 0); const ImVec2 contentRegionSize = ImGui::GetContentRegionAvail(), windowPos = ImGui::GetWindowPos(), cursorPos = ImGui::GetCursorScreenPos(); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + { + const auto mPos = ImGui::GetMousePos(); + + if (mPos.x < cursorPos.x || mPos.y < cursorPos.y || mPos.x > cursorPos.x + contentRegionSize.x || mPos.y > cursorPos.y + contentRegionSize.y) + window->Flags = ImGuiWindowFlags_None; + else + window->Flags = ImGuiWindowFlags_NoMove; + } + // setup bound entities for the window like camera & projections auto& binding = windowControlBinding[windowIx]; auto& planarBound = m_planarProjections[binding.activePlanarIx]; @@ -1201,6 +1237,9 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto& projection = planarBound->getPlanarProjections()[binding.boundProjectionIx.value()]; projection.update(binding.leftHandedProjection, binding.aspectRatio); + // TODO: + // would be nice to normalize imguizmo visual vectors (possible with styles) + // first 0th texture is for UI texture atlas, then there are our window textures auto fboImguiTextureID = windowIx + 1u; info.textureID = fboImguiTextureID; @@ -1466,6 +1505,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication assert(binding.boundProjectionIx.has_value()); auto& projection = planarBound->getPlanarProjections()[binding.boundProjectionIx.value()]; + projection.update(binding.leftHandedProjection, binding.aspectRatio); auto concatMatrix = mul(getCastedMatrix(projection.getProjectionMatrix()), getMatrix3x4As4x4(viewMatrix)); modelViewProjectionMatrix = mul(concatMatrix, getMatrix3x4As4x4(m_model)); @@ -1479,7 +1519,13 @@ class UISampleApp final : public examples::SimpleWindowedApplication // Planars { - ImGui::Begin("Projection"); + // setup + { + ImGui::SetNextWindowPos({ wInit.planars.iPos.x, wInit.planars.iPos.y }, ImGuiCond_Appearing); + ImGui::SetNextWindowSize({ wInit.planars.iSize.x, wInit.planars.iSize.y }, ImGuiCond_Appearing); + } + + ImGui::Begin("Planar projection"); ImGui::Checkbox("Window mode##useWindow", &useWindow); ImGui::Separator(); @@ -1613,7 +1659,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication case IPlanarProjection::CProjection::Orthographic: { - ImGui::SliderFloat("Ortho width", &updateParameters.m_planar.orthographic.orthoWidth, 1.f, 20.f, "%.1f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("Ortho width", &updateParameters.m_planar.orthographic.orthoWidth, 1.f, 30.f, "%.1f", ImGuiSliderFlags_Logarithmic); boundProjection.setOrthographic(updateParameters.m_zNear, updateParameters.m_zFar, updateParameters.m_planar.orthographic.orthoWidth); } break; @@ -1736,11 +1782,15 @@ class UISampleApp final : public examples::SimpleWindowedApplication static bool boundSizing = false; static bool boundSizingSnap = false; - ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); - ImGui::Begin("TRS Editor"); - ImGuiIO& io = ImGui::GetIO(); + + // setup + { + ImGui::SetNextWindowPos({ wInit.trsEditor.iPos.x, wInit.trsEditor.iPos.y }, ImGuiCond_Appearing); + ImGui::SetNextWindowSize({ wInit.trsEditor.iSize.x, wInit.trsEditor.iSize.y }, ImGuiCond_Appearing); + } + + ImGui::Begin("TRS Editor"); { const size_t objectsCount = m_planarProjections.size() + 1u; assert(objectsCount); @@ -2124,6 +2174,19 @@ class UISampleApp final : public examples::SimpleWindowedApplication float snap[3] = { 1.f, 1.f, 1.f }; bool firstFrame = true; + const float32_t2 iPaddingOffset = float32_t2(10, 10); + + struct ImWindowInit + { + float32_t2 iPos, iSize; + }; + + struct + { + ImWindowInit trsEditor; + ImWindowInit planars; + std::array renderWindows; + } wInit; }; NBL_MAIN_FUNC(UISampleApp) \ No newline at end of file From 9f9c77eda5539650b9f6d0974581e989f980886b Mon Sep 17 00:00:00 2001 From: AnastaZIuk Date: Mon, 30 Dec 2024 17:01:56 +0100 Subject: [PATCH 84/84] create COrbitCamera.hpp & add to the demo example --- 61_UI/app_resources/cameras.json | 42 +++- 61_UI/include/common.hpp | 1 + 61_UI/main.cpp | 124 ++++++++--- common/include/camera/COrbitCamera.hpp | 221 ++++++++++++++++++++ common/include/camera/IGimbal.hpp | 2 +- common/include/camera/IGimbalController.hpp | 3 +- 6 files changed, 363 insertions(+), 30 deletions(-) create mode 100644 common/include/camera/COrbitCamera.hpp diff --git a/61_UI/app_resources/cameras.json b/61_UI/app_resources/cameras.json index 3e9769e73..7e76aef83 100644 --- a/61_UI/app_resources/cameras.json +++ b/61_UI/app_resources/cameras.json @@ -6,9 +6,9 @@ "orientation": [0.195, 0.311, -0.065, 0.928] }, { - "type": "FPS", + "type": "Orbit", "position": [-2.017, 0.386, 0.684], - "orientation": [0.047, 0.830, -0.072, 0.55] + "target": [0, 0, 0] }, { "type": "Free", @@ -56,6 +56,20 @@ "controllers": { "keyboard": 1 } + }, + { + "projection": 0, + "controllers": { + "keyboard": 3, + "mouse": 1 + } + }, + { + "projection": 1, + "controllers": { + "keyboard": 3, + "mouse": 1 + } } ], "planars": [ @@ -65,7 +79,7 @@ }, { "camera": 1, - "viewports": [0, 1] + "viewports": [4, 5] }, { "camera": 2, @@ -109,6 +123,16 @@ "U": "RollRight", "O": "RollLeft" } + }, + { + "mappings": { + "W": "MoveRight", + "S": "MoveLeft", + "A": "MoveDown", + "D": "MoveUp", + "E": "MoveForward", + "Q": "MoveBackward" + } } ], "mouse": [ @@ -119,6 +143,18 @@ "RELATIVE_POSITIVE_MOVEMENT_Y": "TiltUp", "RELATIVE_NEGATIVE_MOVEMENT_Y": "TiltDown" } + }, + { + "mappings": { + "RELATIVE_POSITIVE_MOVEMENT_X": "MoveUp", + "RELATIVE_NEGATIVE_MOVEMENT_X": "MoveDown", + "RELATIVE_POSITIVE_MOVEMENT_Y": "MoveRight", + "RELATIVE_NEGATIVE_MOVEMENT_Y": "MoveLeft", + "VERTICAL_POSITIVE_SCROLL": "MoveForward", + "HORIZONTAL_POSITIVE_SCROLL": "MoveForward", + "VERTICAL_NEGATIVE_SCROLL": "MoveBackward", + "HORIZONTAL_NEGATIVE_SCROLL": "MoveBackward" + } } ] } diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index 203bd4ed9..0a709d5b4 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -8,6 +8,7 @@ // common api #include "camera/CFPSCamera.hpp" #include "camera/CFreeLockCamera.hpp" +#include "camera/COrbitCamera.hpp" #include "SimpleWindowedApplication.hpp" #include "InputSystem.hpp" diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 2d630b8d1..3d52fb14c 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -312,32 +312,55 @@ class UISampleApp final : public examples::SimpleWindowedApplication return false; } - if (!jCamera.contains("orientation")) - { - logFail("Expected \"orientation\" keyword for camera definition!"); - return false; - } + const bool withOrientation = jCamera.contains("orientation"); auto position = [&]() - { - auto jret = jCamera["position"].get>(); - return float32_t3(jret[0], jret[1], jret[2]); - }(); + { + auto jret = jCamera["position"].get>(); + return float32_t3(jret[0], jret[1], jret[2]); + }(); - auto orientation = [&]() - { - auto jret = jCamera["orientation"].get>(); + auto getOrientation = [&]() + { + auto jret = jCamera["orientation"].get>(); - // order important for glm::quat, - // the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) - // but memory layout (and json) is x,y,z,w - return glm::quat(jret[3], jret[0], jret[1], jret[2]); - }(); + // order important for glm::quat, + // the ctor is GLM_FUNC_QUALIFIER GLM_CONSTEXPR qua::qua(T _w, T _x, T _y, T _z) + // but memory layout (and json) is x,y,z,w + return glm::quat(jret[3], jret[0], jret[1], jret[2]); + }; + + auto getTarget = [&]() + { + auto jret = jCamera["target"].get>(); + return float32_t3(jret[0], jret[1], jret[2]); + }; if (jCamera["type"] == "FPS") - cameras.emplace_back() = make_smart_refctd_ptr(position, orientation); + { + if (!withOrientation) + { + logFail("Expected \"orientation\" keyword for FPS camera definition!"); + return false; + } + + cameras.emplace_back() = make_smart_refctd_ptr(position, getOrientation()); + } else if (jCamera["type"] == "Free") - cameras.emplace_back() = make_smart_refctd_ptr(position, orientation); + { + if (!withOrientation) + { + logFail("Expected \"orientation\" keyword for Free camera definition!"); + return false; + } + + cameras.emplace_back() = make_smart_refctd_ptr(position, getOrientation()); + } + else if (jCamera["type"] == "Orbit") + { + auto& camera = cameras.emplace_back() = make_smart_refctd_ptr(position, getTarget()); + camera->setMoveSpeedScale(0.2); + } else { logFail("Unsupported camera type!"); @@ -1152,7 +1175,7 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto& projection = planar->getPlanarProjections()[binding.boundProjectionIx.value()]; static std::vector virtualEvents(0x45); - uint32_t vCount; + uint32_t vCount = {}; projection.beginInputProcessing(m_nextPresentationTimestamp); { @@ -1161,11 +1184,33 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (virtualEvents.size() < vCount) virtualEvents.resize(vCount); - projection.process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); + auto* orbit = dynamic_cast(camera); + + if (orbit) + { + uint32_t vKeyboardEventsCount = {}, vMouseEventsCount = {}; + + projection.processKeyboard(nullptr, vKeyboardEventsCount, {}); + projection.processMouse(nullptr, vMouseEventsCount, {}); + + auto* output = virtualEvents.data(); + + projection.processKeyboard(output, vKeyboardEventsCount, params.keyboardEvents); + output += vKeyboardEventsCount; + + if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) + projection.processMouse(output, vMouseEventsCount, params.mouseEvents); + else + vMouseEventsCount = 0; + + vCount = vKeyboardEventsCount + vMouseEventsCount; + } + else + projection.process(virtualEvents.data(), vCount, { params.keyboardEvents, params.mouseEvents }); } projection.endInputProcessing(); - if(vCount) + if (vCount) camera->manipulate({ virtualEvents.data(), vCount }, ICamera::Local); } @@ -1182,6 +1227,24 @@ class UISampleApp final : public examples::SimpleWindowedApplication SImResourceInfo info; info.samplerIx = (uint16_t)nbl::ext::imgui::UI::DefaultSamplerIx::USER; + + // ORBIT CAMERA TEST + { + for (auto& planar : m_planarProjections) + { + auto* camera = planar->getCamera(); + + auto* orbit = dynamic_cast(camera); + + if (orbit) + { + auto targetPostion = transpose(getMatrix3x4As4x4(m_model))[3]; + orbit->target(targetPostion); + orbit->manipulate({}, {}); + } + } + } + // render bound planar camera views onto GUI windows if (useWindow) { @@ -1472,8 +1535,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Image(info, contentRegionSize); ImGuizmo::SetRect(cursorPos.x, cursorPos.y, contentRegionSize.x, contentRegionSize.y); - - ImGui::End(); ImGui::PopStyleColor(1); } @@ -1721,14 +1782,27 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Text("Object Ix: %s", std::to_string(active.activePlanarIx + 1u).c_str()); ImGui::Separator(); { + auto* orbit = dynamic_cast(boundCamera); + float moveSpeed = boundCamera->getMoveSpeedScale(); float rotationSpeed = boundCamera->getRotationSpeedScale(); ImGui::SliderFloat("Move speed factor", &moveSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); - ImGui::SliderFloat("Rotate speed factor", &rotationSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); + + if(not orbit) + ImGui::SliderFloat("Rotate speed factor", &rotationSpeed, 0.0001f, 10.f, "%.4f", ImGuiSliderFlags_Logarithmic); boundCamera->setMoveSpeedScale(moveSpeed); boundCamera->setRotationSpeedScale(rotationSpeed); + + { + if (orbit) + { + float distance = orbit->getDistance(); + ImGui::SliderFloat("Distance", &distance, COrbitCamera::MinDistance, COrbitCamera::MaxDistance, "%.4f", ImGuiSliderFlags_Logarithmic); + orbit->setDistance(distance); + } + } } if (ImGui::TreeNodeEx("World Data", flags)) diff --git a/common/include/camera/COrbitCamera.hpp b/common/include/camera/COrbitCamera.hpp new file mode 100644 index 000000000..936b1e51f --- /dev/null +++ b/common/include/camera/COrbitCamera.hpp @@ -0,0 +1,221 @@ +#ifndef _C_ORBIT_CAMERA_HPP_ +#define _C_ORBIT_CAMERA_HPP_ + +#include "ICamera.hpp" +#include + +namespace nbl::hlsl +{ + +class COrbitCamera final : public ICamera +{ +public: + using base_t = ICamera; + + COrbitCamera(const float64_t3& position, const float64_t3& target) + : base_t(), m_targetPosition(target), m_distance(length(m_targetPosition - position)), m_gimbal({ .position = position, .orientation = glm::quat(glm::vec3(0, 0, 0)) }) {} + ~COrbitCamera() = default; + + const base_t::keyboard_to_virtual_events_t getKeyboardMappingPreset() const override { return m_keyboard_to_virtual_events_preset; } + const base_t::mouse_to_virtual_events_t getMouseMappingPreset() const override { return m_mouse_to_virtual_events_preset; } + const base_t::imguizmo_to_virtual_events_t getImguizmoMappingPreset() const override { return m_imguizmo_to_virtual_events_preset; } + + const typename base_t::CGimbal& getGimbal() override { return m_gimbal; } + + inline bool setDistance(float d) + { + const auto clamped = std::clamp(d, MinDistance, MaxDistance); + const bool ok = clamped == d; + + m_distance = clamped; + + return ok; + } + + inline void target(const float64_t3& p) + { + m_targetPosition = p; + } + + virtual bool manipulate(std::span virtualEvents, base_t::ManipulationMode mode) override + { + // position on the sphere + auto S = [&](double u, double v) -> float64_t3 + { + return float64_t3 + { + std::cos(v) * std::cos(u), + std::cos(v) * std::sin(u), + std::sin(v) + } * (double) m_distance; + }; + + /* + // partial derivative of S with respect to u + auto Sdu = [&](double u, double v) -> float64_t3 + { + return float64_t3 + { + -std::cos(v) * std::sin(u), + std::cos(v)* std::cos(u), + 0 + } * (double) m_distance; + }; + */ + + double deltaU = {}, deltaV = {}, deltaDistance = {}; + + for (const auto& it : virtualEvents) + { + if (it.type == it.MoveForward) + deltaDistance += it.magnitude; + else if (it.type == it.MoveBackward) + deltaDistance -= it.magnitude; + else if (it.type == it.MoveUp) + deltaU += it.magnitude; + else if (it.type == it.MoveDown) + deltaU -= it.magnitude; + else if (it.type == it.MoveRight) + deltaV += it.magnitude; + else if (it.type == it.MoveLeft) + deltaV -= it.magnitude; + } + + // TODO! + constexpr auto nastyScalar = 0.01; + deltaU *= nastyScalar * m_moveSpeedScale; + deltaV *= nastyScalar * m_moveSpeedScale; + + u += deltaU; + v += deltaV; + + m_distance = std::clamp(m_distance += deltaDistance * nastyScalar, MinDistance, MaxDistance); + + // partial derivative of S with respect to v + auto Sdv = [&](double u, double v) -> float64_t3 + { + return float64_t3 + { + -std::sin(v) * std::cos(u), + -std::sin(v) * std::sin(u), + std::cos(v) + } * (double) m_distance; + }; + + const auto localSpherePostion = S(u, v); + const auto newPosition = localSpherePostion + m_targetPosition; + + // note we are not using Sdu (though we could!) + // instead we benefit from forward we have for free when moving on sphere surface + // and given up vector obtained from partial derivative we can easily get right vector, this way + // we don't have frenet frame flip we would have with Sdu, however it could be adjusted anyway, less code + + const auto newUp = normalize(Sdv(u, v)); + const auto newForward = normalize(-localSpherePostion); + const auto newRight = normalize(cross(newUp, newForward)); + + const auto newOrientation = glm::quat_cast + ( + glm::dmat3 + { + newRight, + newUp, + newForward + } + ); + + m_gimbal.begin(); + { + m_gimbal.setPosition(newPosition); + m_gimbal.setOrientation(newOrientation); + } + m_gimbal.end(); + + bool manipulated = bool(m_gimbal.getManipulationCounter()); + + if (manipulated) + m_gimbal.updateView(); + + return manipulated; + } + + virtual const uint32_t getAllowedVirtualEvents(base_t::ManipulationMode mode) override + { + switch (mode) + { + case base_t::Local: return AllowedLocalVirtualEvents; + case base_t::World: return AllowedWorldVirtualEvents; + default: return CVirtualGimbalEvent::None; + } + } + + virtual const std::string_view getIdentifier() override + { + return "Orbit Camera"; + } + + inline float getDistance() { return m_distance; } + inline double getU() { return u; } + inline double getV() { return v; } + + static inline constexpr float MinDistance = 0.1f; + static inline constexpr float MaxDistance = 10000.f; + +private: + float64_t3 m_targetPosition; + float m_distance; + typename base_t::CGimbal m_gimbal; + + double u = {}, v = {}; + + static inline constexpr auto AllowedLocalVirtualEvents = CVirtualGimbalEvent::Translate; + static inline constexpr auto AllowedWorldVirtualEvents = CVirtualGimbalEvent::None; + + static inline const auto m_keyboard_to_virtual_events_preset = []() + { + typename base_t::keyboard_to_virtual_events_t preset; + + preset[ui::E_KEY_CODE::EKC_W] = CVirtualGimbalEvent::MoveUp; + preset[ui::E_KEY_CODE::EKC_S] = CVirtualGimbalEvent::MoveDown; + preset[ui::E_KEY_CODE::EKC_A] = CVirtualGimbalEvent::MoveLeft; + preset[ui::E_KEY_CODE::EKC_D] = CVirtualGimbalEvent::MoveRight; + preset[ui::E_KEY_CODE::EKC_E] = CVirtualGimbalEvent::MoveForward; + preset[ui::E_KEY_CODE::EKC_Q] = CVirtualGimbalEvent::MoveBackward; + + return preset; + }(); + + static inline const auto m_mouse_to_virtual_events_preset = []() + { + typename base_t::mouse_to_virtual_events_t preset; + + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_X] = CVirtualGimbalEvent::MoveRight; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_X] = CVirtualGimbalEvent::MoveLeft; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_POSITIVE_MOVEMENT_Y] = CVirtualGimbalEvent::MoveUp; + preset[ui::E_MOUSE_CODE::EMC_RELATIVE_NEGATIVE_MOVEMENT_Y] = CVirtualGimbalEvent::MoveDown; + preset[ui::E_MOUSE_CODE::EMC_VERTICAL_POSITIVE_SCROLL] = CVirtualGimbalEvent::MoveForward; + preset[ui::E_MOUSE_CODE::EMC_HORIZONTAL_POSITIVE_SCROLL] = CVirtualGimbalEvent::MoveForward; + preset[ui::E_MOUSE_CODE::EMC_VERTICAL_NEGATIVE_SCROLL] = CVirtualGimbalEvent::MoveBackward; + preset[ui::E_MOUSE_CODE::EMC_HORIZONTAL_NEGATIVE_SCROLL] = CVirtualGimbalEvent::MoveBackward; + + return preset; + }(); + + static inline const auto m_imguizmo_to_virtual_events_preset = []() + { + typename base_t::imguizmo_to_virtual_events_t preset; + + preset[CVirtualGimbalEvent::MoveForward] = CVirtualGimbalEvent::MoveForward; + preset[CVirtualGimbalEvent::MoveBackward] = CVirtualGimbalEvent::MoveBackward; + preset[CVirtualGimbalEvent::MoveLeft] = CVirtualGimbalEvent::MoveLeft; + preset[CVirtualGimbalEvent::MoveRight] = CVirtualGimbalEvent::MoveRight; + preset[CVirtualGimbalEvent::MoveUp] = CVirtualGimbalEvent::MoveUp; + preset[CVirtualGimbalEvent::MoveDown] = CVirtualGimbalEvent::MoveDown; + + return preset; + }(); +}; + +} + +#endif // _C_ORBIT_CAMERA_HPP_ \ No newline at end of file diff --git a/common/include/camera/IGimbal.hpp b/common/include/camera/IGimbal.hpp index 1a94affc5..c70c2bbab 100644 --- a/common/include/camera/IGimbal.hpp +++ b/common/include/camera/IGimbal.hpp @@ -317,7 +317,7 @@ namespace nbl::hlsl move(getZAxis() * distance); } - void end() + inline void end() { m_isManipulating = false; } diff --git a/common/include/camera/IGimbalController.hpp b/common/include/camera/IGimbalController.hpp index 770b5ed16..7b4ba365e 100644 --- a/common/include/camera/IGimbalController.hpp +++ b/common/include/camera/IGimbalController.hpp @@ -182,7 +182,6 @@ class IGimbalController : public IGimbalManipulateEncoder virtual const mouse_to_virtual_events_t& getMouseVirtualEventMap() const override { return m_mouseVirtualEventMap; } virtual const imguizmo_to_virtual_events_t& getImguizmoVirtualEventMap() const override { return m_imguizmoVirtualEventMap; } -private: /** * @brief Processes keyboard events to generate virtual manipulation events. * @@ -405,6 +404,8 @@ class IGimbalController : public IGimbalManipulateEncoder } } +private: + //! helper utility, for any controller this should be called before any update of hash map void preprocess(auto& map) {