diff --git a/examples/ExampleApps/MovingCubeApp.cpp b/examples/ExampleApps/MovingCubeApp.cpp index a1311f9..472583e 100644 --- a/examples/ExampleApps/MovingCubeApp.cpp +++ b/examples/ExampleApps/MovingCubeApp.cpp @@ -14,65 +14,90 @@ #include "Scene/SimpleDrawable.hpp" #include "Scene/Camera.hpp" #include "Scene/SimpleAnimationController.hpp" +#include "Scene/SequenceAnimationController.hpp" #include "Input/InputManager.hpp" #include "Host/GraphicsAppManager.hpp" #include "Base/EngineConfiguration.hpp" +#include "Base/Logger.hpp" #include "Controller/FreeCamCameraController.hpp" namespace OpenVulkano { + namespace + { + struct SceneElement + { + Scene::Geometry m_geometry; + Scene::SimpleDrawable m_drawable; + Scene::Node m_node; + }; + } class MovingCubeAppImpl final : public MovingCubeApp { - OpenVulkano::Scene::Scene m_scene; - OpenVulkano::Scene::PerspectiveCamera m_camera; - OpenVulkano::FreeCamCameraController m_cameraControl; - OpenVulkano::Scene::Material m_material; - OpenVulkano::Scene::Shader m_shader; + Scene::Scene m_scene; + Scene::PerspectiveCamera m_camera; + FreeCamCameraController m_cameraControl; + Scene::Material m_material; + Scene::Shader m_shader; - OpenVulkano::Scene::Geometry m_geometry; - OpenVulkano::Scene::SimpleDrawable m_drawable; - OpenVulkano::Scene::Node m_node; - std::unique_ptr m_animationController; + Scene::SimpleAnimationController m_simpleAnimationController; + Scene::SequenceAnimationController m_sequenceAnimationController; + + SceneElement m_whiteBox; + SceneElement m_redBox; + + void CreateSceneElement(SceneElement *dest, const Math::Vector4f &color, float scale) + { + dest->m_geometry.InitCube(scale, scale, scale, color); + dest->m_drawable.Init(&m_shader, &dest->m_geometry, &m_material); + + dest->m_node.Init(); + m_scene.GetRoot()->AddChild(&dest->m_node); + dest->m_node.SetUpdateFrequency(Scene::UpdateFrequency::Always); + dest->m_node.AddDrawable(&dest->m_drawable); + dest->m_node.SetMatrix(Math::Matrix4f(1)); + } public: void Init() override { - auto engineConfig = OpenVulkano::EngineConfiguration::GetEngineConfiguration(); + auto engineConfig = EngineConfiguration::GetEngineConfiguration(); m_camera.Init(70, 16, 9, 0.1, 100); // m_camera.SetMatrix(OpenVulkano::Math::Utils::translate(OpenVulkano::Math::Matrix4f(1), OpenVulkano::Math::Vector3f_SIMD(0, 0, -50))); m_scene.Init(); m_scene.SetCamera(&m_camera); - m_shader.AddShaderProgram(OpenVulkano::ShaderProgramType::VERTEX, "Shader/basic"); - m_shader.AddShaderProgram(OpenVulkano::ShaderProgramType::FRAGMENT, "Shader/basic"); - m_shader.AddVertexInputDescription(OpenVulkano::Vertex::GetVertexInputDescription()); - - m_geometry.InitCube(); - m_drawable.Init(&m_shader, &m_geometry, &m_material); - - m_node.Init(); - m_scene.GetRoot()->AddChild(&m_node); - m_node.SetUpdateFrequency(OpenVulkano::Scene::UpdateFrequency::Always); - m_node.AddDrawable(&m_drawable); - m_node.SetMatrix(OpenVulkano::Math::Matrix4f(1)); + m_shader.AddShaderProgram(ShaderProgramType::VERTEX, "Shader/basic"); + m_shader.AddShaderProgram(ShaderProgramType::FRAGMENT, "Shader/basic"); + m_shader.AddVertexInputDescription(Vertex::GetVertexInputDescription()); GetGraphicsAppManager()->GetRenderer()->SetScene(&m_scene); m_cameraControl.Init(&m_camera); m_cameraControl.SetDefaultKeybindings(); - m_animationController = std::make_unique(); - m_animationController->SetNode(&m_node); - m_animationController->SetDuration(3); - - OpenVulkano::Math::Pose srcPose(OpenVulkano::Math::Quaternion(), OpenVulkano::Math::Vector3f_SIMD(-3, 0, 0)); - OpenVulkano::Math::Pose destPose(OpenVulkano::Math::Quaternion(), OpenVulkano::Math::Vector3f_SIMD(3, 0, 0)); - m_animationController->SetPoses(srcPose, destPose); + CreateSceneElement(&m_whiteBox, Math::Vector4f(1, 1, 1, 1), 1); + CreateSceneElement(&m_redBox, Math::Vector4f(1, 0.2, 0.2, 1.0), 0.3); - m_animationController->m_completionEvent += EventHandler(this, &MovingCubeAppImpl::OnAnimationCompleted); + m_simpleAnimationController.SetNode(&m_whiteBox.m_node); + m_simpleAnimationController.SetDuration(3); + Math::Pose srcPose(Math::Quaternion(), Math::Vector3f_SIMD(-3, 0, 0)); + Math::Pose destPose(Math::Quaternion(), Math::Vector3f_SIMD(3, 0, 0)); + m_simpleAnimationController.SetPoses(srcPose, destPose); + m_simpleAnimationController.m_completionEvent += EventHandler(this, &MovingCubeAppImpl::OnSimpleAnimationCompleted); + + m_sequenceAnimationController.EnableLoop(true); + m_sequenceAnimationController.SetNode(&m_redBox.m_node); + m_sequenceAnimationController.AddAnimationStep(Math::PoseF(Math::Utils::normalize(Math::QuaternionF(1, 0, 0, 1)), Math::Vector3f_SIMD(0, 0, 1)), 5); + m_sequenceAnimationController.AddAnimationStep(Math::PoseF(Math::Utils::normalize(Math::QuaternionF(2, 1, 0, 1)), Math::Vector3f_SIMD(1, 1, -1)), 3); + m_sequenceAnimationController.AddAnimationStep(Math::PoseF(Math::Utils::normalize(Math::QuaternionF(1, 1, 1, 1)), Math::Vector3f_SIMD(2, 1, -2)), 3); + m_sequenceAnimationController.AddAnimationStep(Math::PoseF(Math::Utils::normalize(Math::QuaternionF(0, 1, 1, 0)), Math::Vector3f_SIMD(2, 1, -1)), 3); + m_sequenceAnimationController.AddAnimationStep(Math::PoseF(Math::Utils::normalize(Math::QuaternionF(3, 2, 1, 1)), Math::Vector3f_SIMD(1, 1, -1)), 3); + m_sequenceAnimationController.AddAnimationStep(Math::PoseF(Math::Utils::normalize(Math::QuaternionF(0, 0, 1, 1)), Math::Vector3f_SIMD(0, 1, 0)), 1); + m_sequenceAnimationController.SetAnimationPoseResetTime(10); } - void OnAnimationCompleted(OpenVulkano::Scene::SimpleAnimationController *anim) + void OnSimpleAnimationCompleted(Scene::SimpleAnimationController *anim) { anim->SwapPoses(); anim->Reset(); @@ -81,7 +106,8 @@ namespace OpenVulkano void Tick() override { m_cameraControl.Tick(); - m_animationController->Tick(); + m_simpleAnimationController.Tick(); + m_sequenceAnimationController.Tick(); } void Close() override diff --git a/openVulkanoCpp/Math/Pose.hpp b/openVulkanoCpp/Math/Pose.hpp index 6ee6506..22fbee8 100644 --- a/openVulkanoCpp/Math/Pose.hpp +++ b/openVulkanoCpp/Math/Pose.hpp @@ -44,9 +44,14 @@ namespace OpenVulkano::Math } } - [[nodiscard]] Quaternion& GetOrientation() const { return m_orientation; } + bool operator==(const Math::Pose &otherPose) const + { + return (GetOrientation() == otherPose.GetOrientation()) && (GetPosition() == otherPose.GetPosition()); + } - [[nodiscard]] Vector3_SIMD GetPosition() const { return m_position; } + [[nodiscard]] const Quaternion& GetOrientation() const { return m_orientation; } + + [[nodiscard]] const Vector3_SIMD& GetPosition() const { return m_position; } [[nodiscard]] Pose Interpolate(const Pose& otherPose, T mixFactor) const { diff --git a/openVulkanoCpp/Scene/SequenceAnimationController.cpp b/openVulkanoCpp/Scene/SequenceAnimationController.cpp new file mode 100644 index 0000000..989b938 --- /dev/null +++ b/openVulkanoCpp/Scene/SequenceAnimationController.cpp @@ -0,0 +1,130 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "Scene/SequenceAnimationController.hpp" + +namespace OpenVulkano::Scene +{ + SequenceAnimationController::SequenceAnimationController() + { + m_animationController.m_completionEvent += EventHandler(this, &SequenceAnimationController::SequenceCompletedCallback); + } + + const SequenceAnimationController::PoseDurationPair& SequenceAnimationController::GetStep(int index) const + { + if(index >= 0 && index < m_steps.size()) + return m_steps[index]; + return PoseDurationPair(); + } + + void SequenceAnimationController::SequenceCompletedCallback(SimpleAnimationController *ignored) + { + if(m_steps.empty()) + return; + + if(m_currentStep < m_steps.size() - 1) + { + m_currentStep++; + m_animationController.SetPoses(m_animationController.GetTargetPose(), m_steps[m_currentStep].first); + m_animationController.SetDuration(m_steps[m_currentStep].second); + m_animationController.Reset(); + } + else + { + if(m_loop) + { + // NOTE(vb): Maybe compare steps with some epsilon value + if(m_steps.size() > 1 && ((m_steps.front().first == m_steps.back().first) || m_resetTime == 0)) + { + m_currentStep = 1; + m_animationController.SetPoses(m_steps[m_currentStep-1].first, m_steps[m_currentStep].first); + m_animationController.SetDuration(m_steps[m_currentStep].second); + m_animationController.Reset(); + } + else + { + m_currentStep = 0; + m_animationController.SetPoses(m_steps.back().first, m_steps.front().first); + m_animationController.SetDuration(m_resetTime); + m_animationController.Reset(); + } + } + else + { + OnSequenceCompleted.NotifyAll(this); + } + } + } + + void SequenceAnimationController::Tick() + { + if(m_steps.empty()) + return; + m_animationController.Tick(); + } + + void SequenceAnimationController::Restart() + { + m_currentStep = 0; + if(!m_steps.empty()) + { + m_animationController.SetPoses(m_animationController.GetTargetPose(), m_steps[m_currentStep].first); + m_animationController.SetDuration(m_steps[m_currentStep].second); + } + m_animationController.Reset(); + } + + bool SequenceAnimationController::IsFinished() const + { + if(m_loop) + return false; + return m_currentStep >= m_steps.size(); + } + + void SequenceAnimationController::AddAnimationStep(const Math::PoseF &pose, double duration) + { + m_steps.emplace_back(pose, duration); + if(m_steps.size() > 1) + { + m_currentStep = 1; + m_animationController.SetPoses(m_steps[0].first, m_steps[1].first); + m_animationController.SetDuration(m_steps[1].second); + m_animationController.Reset(); + } + } + + void SequenceAnimationController::AddAnimationSteps(std::initializer_list poses, double duration) + { + for(const auto& pose : poses) + { + m_steps.emplace_back(pose, duration); + } + + if(m_steps.size() > 1) + { + m_currentStep = 1; + m_animationController.SetPoses(m_steps[0].first, m_steps[1].first); + m_animationController.SetDuration(m_steps[1].second); + m_animationController.Reset(); + } + } + + void SequenceAnimationController::AddAnimationSteps(std::initializer_list steps) + { + for(const auto& step : steps) + { + m_steps.emplace_back(step); + } + + if(m_steps.size() > 1) + { + m_currentStep = 1; + m_animationController.SetPoses(m_steps[0].first, m_steps[1].first); + m_animationController.SetDuration(m_steps[1].second); + m_animationController.Reset(); + } + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/SequenceAnimationController.hpp b/openVulkanoCpp/Scene/SequenceAnimationController.hpp new file mode 100644 index 0000000..11f7741 --- /dev/null +++ b/openVulkanoCpp/Scene/SequenceAnimationController.hpp @@ -0,0 +1,86 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "Scene/SimpleAnimationController.hpp" +#include "Base/Event.hpp" +#include "Base/ITickable.hpp" +#include "Math/Math.hpp" +#include "Math/Pose.hpp" + +#include +#include +#include + +namespace OpenVulkano::Scene +{ + class SequenceAnimationController : public ITickable + { + public: + using PoseDurationPair = std::pair; + + private: + SimpleAnimationController m_animationController; + std::vector m_steps; + size_t m_currentStep = 0; + bool m_loop = false; + double m_resetTime = 0; + + void SequenceCompletedCallback(SimpleAnimationController *animationController); + + public: + Event OnSequenceCompleted; + + SequenceAnimationController(); + + /** + * Enables or disables looping of the animation sequence. + * + * When looping is enabled, the sequence will restart from the first step after the last step is completed. + * If the start and end poses of the step list are equal or the reset time is 0, the sequence will jump + * directly to the first step. Otherwise, it will animate the transition back to the first step using the + * reset time set by the user. + * + * @param loop A boolean value to enable or disable looping. + */ + void EnableLoop(bool value) { m_loop = value; } + + void SetNode(Node *node) { m_animationController.SetNode(node); } + Node* GetNode() const { return m_animationController.GetNode(); } + + /** + * Sets the time to transition back to the first pose when looping is enabled. + * + * If the reset time is set to 0, the sequence will jump directly to the first step. If the start and end poses + * of the step list are equal, the sequence will also jump directly to the first step regardless of the reset time. + * Otherwise, the sequence will animate the transition back to the first step using the reset time. + * + * @param resetTime The time to transition back to the first pose. + * @see GetAnimationPoseResetTime + */ + void SetAnimationPoseResetTime(double resetTime) { m_resetTime = resetTime; } + + /** + * Gets the current reset time for transitioning back to the first pose. + * + * @return The current reset time. + * @see SetAnimationPoseResetTime + */ + double GetAnimationPoseResetTime() const { return m_resetTime; } + + void Restart(); + bool IsFinished() const; + + void AddAnimationStep(const Math::PoseF &pose, double duration); + void AddAnimationSteps(std::initializer_list poses, double duration); + void AddAnimationSteps(std::initializer_list steps); + const PoseDurationPair& GetStep(int index) const; + const std::vector &GetSteps() const { return m_steps; } + + void Tick() override; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/SimpleAnimationController.hpp b/openVulkanoCpp/Scene/SimpleAnimationController.hpp index a369f4c..08271b6 100644 --- a/openVulkanoCpp/Scene/SimpleAnimationController.hpp +++ b/openVulkanoCpp/Scene/SimpleAnimationController.hpp @@ -32,14 +32,14 @@ namespace OpenVulkano::Scene void Reset() { m_elapsed = 0; } void SwapPoses() { std::swap(m_initialPose, m_targetPose); } - Node* GetNode() { return m_node; } + Node* GetNode() const { return m_node; } void SetNode(Node *node) { m_node = node; } - const Math::PoseF& GetInitialPose() { return m_initialPose; } - const Math::PoseF& GetTargetPose() { return m_targetPose; } + const Math::PoseF& GetInitialPose() const { return m_initialPose; } + const Math::PoseF& GetTargetPose() const { return m_targetPose; } void SetPoses(const Math::PoseF &initialPose, const Math::PoseF &targetPose) { m_initialPose = initialPose; m_targetPose = targetPose; } - double GetDuration() { return m_duration; } + double GetDuration() const { return m_duration; } void SetDuration(double duration) { m_duration = duration; } double GetProgress();