From 4c2e01daaf3ab2b66286c41f2d5534936ffae5b7 Mon Sep 17 00:00:00 2001 From: Doyeong Lee Date: Sat, 18 Apr 2026 22:22:25 +0900 Subject: [PATCH 1/5] Implement Job System Task-Based Job System Integration - Replaced the existing ThreadManager with a JobSystem to manage CPU-bound tasks through a worker thread pool. - Added JobSystem APIs (QueueWork, QueueParallelWork, WaitForWork, WaitForAll) using a counter-based synchronization method for thread management. Thread-Safe Input Handling (InputSnapshot Pattern) - Added an InputSnapshot structure that captures a read-only copy of the keyboard and mouse state at the start of each frame. - Updated all game scripts and logic to read from InputSnapshot instead of InputManager to prevent race conditions when reading inputs during multithreaded updates. Engine Loop Parallelization - Modified GameStateManager::UpdateGameLogic to run ObjectManager, ParticleManager, and SpriteManager updates concurrently as independent jobs. - Applied task dependencies to ensure PhysicsManager updates run only after ObjectManager jobs are completed, avoiding data races between object state changes and physics simulations. - Replaced sequential iterations in internal systems with QueueParallelWork. Object states and 2D/3D physics integrations are now processed in batches across multiple threads. --- .../include/BasicComponents/DynamicSprite.hpp | 6 +- Engine/include/Engine.hpp | 7 +- Engine/include/InputManager.hpp | 78 +++- Engine/include/Interface/ISprite.hpp | 6 + Engine/include/JobSystem.hpp | 99 +++++ Engine/include/ObjectManager.hpp | 4 + Engine/include/ThreadManager.hpp | 41 -- .../source/BasicComponents/DynamicSprite.cpp | 8 +- Engine/source/BasicComponents/ISprite.cpp | 27 ++ Engine/source/Engine.cpp | 13 +- Engine/source/GameStateManager.cpp | 35 +- Engine/source/InputManager.cpp | 394 +++++++++--------- Engine/source/JobSystem.cpp | 267 ++++++++++++ Engine/source/ObjectManager.cpp | 34 +- Engine/source/PhysicsManager.cpp | 45 +- Engine/source/ThreadManager.cpp | 114 ----- Game/source/3DPhysicsDemo.cpp | 10 +- Game/source/BeatEmUpDemo/BEUPlayer.cpp | 20 +- Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp | 12 +- Game/source/PlatformDemo/PPlayer.cpp | 14 +- Game/source/PlatformDemo/PlatformDemo.cpp | 2 +- .../PlatformDemo/PlatformDemoSystem.cpp | 20 +- .../PocketBallDemo/PocketBallSystem.cpp | 10 +- 23 files changed, 840 insertions(+), 426 deletions(-) create mode 100644 Engine/include/JobSystem.hpp delete mode 100644 Engine/include/ThreadManager.hpp create mode 100644 Engine/source/JobSystem.cpp delete mode 100644 Engine/source/ThreadManager.cpp diff --git a/Engine/include/BasicComponents/DynamicSprite.hpp b/Engine/include/BasicComponents/DynamicSprite.hpp index e6bfe52b..79affabc 100644 --- a/Engine/include/BasicComponents/DynamicSprite.hpp +++ b/Engine/include/BasicComponents/DynamicSprite.hpp @@ -27,11 +27,11 @@ class DynamicSprite : public ISprite void UpdateProjection() override; // Add Quad - void AddQuad(glm::vec4 color_); - void AddQuadWithTexture(std::string name_, glm::vec4 color_ = { 1.f,1.f,1.f,1.f }, bool isTexel_ = false); + void CreateQuad(glm::vec4 color_); + void CreateQuadWithTexture(std::string name_, glm::vec4 color_ = { 1.f,1.f,1.f,1.f }, bool isTexel_ = false); // Animation - void LoadAnimation(const std::filesystem::path& spriteInfoFile, std::string name); + void LoadAnimationData(const std::filesystem::path& spriteInfoFile, std::string name); glm::vec2 GetHotSpot(int index); glm::vec2 GetFrameSize() const { return frameSize; }; diff --git a/Engine/include/Engine.hpp b/Engine/include/Engine.hpp index 4e353278..d8d8f781 100644 --- a/Engine/include/Engine.hpp +++ b/Engine/include/Engine.hpp @@ -11,7 +11,7 @@ #include "CameraManager.hpp" #include "SoundManager.hpp" #include "SpriteManager.hpp" -#include "ThreadManager.hpp" +#include "JobSystem.hpp" #include "Logger.hpp" #include "Particle/ParticleManager.hpp" #include "PhysicsManager.hpp" @@ -35,6 +35,8 @@ class Engine static PhysicsManager& GetPhysicsManager() { return Instance().physicsManager; } static Timer& GetTimer() { return Instance().timer; } static Logger& GetLogger() { return *Instance().logger; } + static JobSystem& GetJobSystem() { return Instance().jobSystem; } + static const InputSnapshot& GetInputSnapshot() { return Instance().inputSnapshot; } void Init(const char* title, int windowWidth, int windowHeight, bool fullScreen, WindowMode mode); void Update(); @@ -61,6 +63,7 @@ class Engine SpriteManager* spriteManager; ParticleManager particleManager; PhysicsManager physicsManager; - ThreadManager threadManager; + JobSystem jobSystem; + InputSnapshot inputSnapshot; Logger* logger; }; \ No newline at end of file diff --git a/Engine/include/InputManager.hpp b/Engine/include/InputManager.hpp index f51fa2a9..299d3998 100644 --- a/Engine/include/InputManager.hpp +++ b/Engine/include/InputManager.hpp @@ -8,13 +8,6 @@ #include -//Thread -#include -#include -#include -#include -//Thread - enum class MOUSEBUTTON { LEFT = 1, @@ -160,6 +153,70 @@ enum class KEYBOARDKEYS MODE = SDL_SCANCODE_TO_KEYCODE(SDL_SCANCODE_MODE), }; +// Immutable snapshot of input state captured at the start of each frame. +// All query functions are const — safe to call from any thread. +struct InputSnapshot +{ + std::unordered_map keyStates; + std::unordered_map keyStatePrev; + std::unordered_map mouseStates; + std::unordered_map mouseStatePrev; + glm::vec2 mousePosition = { 0.f, 0.f }; + glm::vec2 mouseWheel = { 0.f, 0.f }; + glm::vec2 relativeMouseState = { 0.f, 0.f }; + + bool IsKeyPressed(KEYBOARDKEYS key) const + { + auto it = keyStates.find(key); + return it != keyStates.end() && it->second; + } + + bool IsKeyPressOnce(KEYBOARDKEYS key) const + { + auto itCur = keyStates.find(key); + auto itPrev = keyStatePrev.find(key); + bool cur = (itCur != keyStates.end()) && itCur->second; + bool prev = (itPrev != keyStatePrev.end()) && itPrev->second; + return cur && !prev; + } + + bool IsKeyReleaseOnce(KEYBOARDKEYS key) const + { + auto itCur = keyStates.find(key); + auto itPrev = keyStatePrev.find(key); + bool cur = (itCur != keyStates.end()) && itCur->second; + bool prev = (itPrev != keyStatePrev.end()) && itPrev->second; + return !cur && prev; + } + + bool IsMouseButtonPressed(MOUSEBUTTON btn) const + { + auto it = mouseStates.find(btn); + return it != mouseStates.end() && it->second; + } + + bool IsMouseButtonPressOnce(MOUSEBUTTON btn) const + { + auto itCur = mouseStates.find(btn); + auto itPrev = mouseStatePrev.find(btn); + bool cur = (itCur != mouseStates.end()) && itCur->second; + bool prev = (itPrev != mouseStatePrev.end()) && itPrev->second; + return cur && !prev; + } + + bool IsMouseButtonReleaseOnce(MOUSEBUTTON btn) const + { + auto itCur = mouseStates.find(btn); + auto itPrev = mouseStatePrev.find(btn); + bool cur = (itCur != mouseStates.end()) && itCur->second; + bool prev = (itPrev != mouseStatePrev.end()) && itPrev->second; + return !cur && prev; + } + + glm::vec2 GetMousePosition() const { return mousePosition; } + glm::vec2 GetMouseWheelMotion() const { return mouseWheel; } + glm::vec2 GetRelativeMouseState() const { return relativeMouseState; } +}; class InputManager { @@ -184,6 +241,13 @@ class InputManager void SetRelativeMouseMode(bool state); bool GetRelativeMouseMode(); glm::vec2 GetRelativeMouseState(); + + // Batch update previous state at frame boundary (call before CreateSnapshot) + void UpdatePreviousState(); + + // Create an immutable snapshot of current input state for thread-safe reading + InputSnapshot CreateSnapshot(); + protected: void KeyDown(KEYBOARDKEYS keycode) { diff --git a/Engine/include/Interface/ISprite.hpp b/Engine/include/Interface/ISprite.hpp index e212960a..1a9b9950 100644 --- a/Engine/include/Interface/ISprite.hpp +++ b/Engine/include/Interface/ISprite.hpp @@ -36,6 +36,9 @@ class ISprite : public IComponent virtual void UpdateProjection() = 0; // Add Mesh + void AddQuad(glm::vec4 color_); + void AddQuadWithTexture(std::string name_, glm::vec4 color_ = { 1.f,1.f,1.f,1.f }, bool isTexel_ = false); + void LoadAnimation(const std::filesystem::path& spriteInfoFile, std::string name); void AddMesh3D(MeshType type, const std::filesystem::path& path, int stacks_, int slices_, glm::vec4 color = { 1.f,1.f,1.f,1.f }, float metallic_ = 0.3f, float roughness_ = 0.3f); // Getter @@ -64,6 +67,9 @@ class ISprite : public IComponent void SetRoughness(float amount, int index = 0) { subMeshes[index]->GetData()->material.roughness = amount; } //For CompFuncQueue + virtual void CreateQuad(glm::vec4 color_) = 0; + virtual void CreateQuadWithTexture(std::string name_, glm::vec4 color_ = { 1.f,1.f,1.f,1.f }, bool isTexel_ = false) = 0; + virtual void LoadAnimationData(const std::filesystem::path& spriteInfoFile, std::string name) = 0; virtual void CreateMesh3D(MeshType type, const std::filesystem::path& path, int stacks_, int slices_, glm::vec4 color = { 1.f,1.f,1.f,1.f }, float metallic_ = 0.3f, float roughness_ = 0.3f) = 0; // BoneInfoMap diff --git a/Engine/include/JobSystem.hpp b/Engine/include/JobSystem.hpp new file mode 100644 index 00000000..b4a5bc31 --- /dev/null +++ b/Engine/include/JobSystem.hpp @@ -0,0 +1,99 @@ +//Author: DOYEONG LEE +//Project: CubeEngine +//File: JobSystem.hpp +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Represents a single unit of work to be executed by a worker thread +struct Job +{ + std::function task; + std::shared_ptr> counter; // Shared completion counter +}; + +// Handle returned by QueueWork / QueueParallelWork to track job completion +class JobHandle +{ +public: + JobHandle() = default; + + bool IsComplete() const + { + return !counter || counter->load(std::memory_order_acquire) <= 0; + } + +private: + std::shared_ptr> counter; + friend class JobSystem; +}; + +// Thread-safe work queue for distributing jobs to worker threads +class JobQueue +{ +public: + void Push(Job job); + std::optional TryPop(); + std::optional WaitAndPop(std::atomic& running); + void NotifyAll(); + +private: + std::deque jobs; + std::mutex queueMutex; + std::condition_variable cv; +}; + +// Multi-threaded job system that replaces the old ThreadManager +class JobSystem +{ +public: + JobSystem() : running(false) {} + ~JobSystem() { Stop(); } + + // Start worker thread pool (0 = auto: hardware_concurrency - 1) + void Start(uint32_t threadCount = 0); + void Stop(); + + // Submit a single job, returns a handle for synchronization + JobHandle QueueWork(std::function task); + + // Submit a parallel-for job that splits 'count' items into batches + JobHandle QueueParallelWork( + uint32_t count, + std::function body, + uint32_t batchSize = 64 + ); + + // Block until the specified job is complete (main thread helps execute jobs) + void WaitForWork(const JobHandle& handle); + + // Block until all specified jobs are complete + void WaitForAll(const std::vector& handles); + + // Process SDL events on the main thread + void ProcessEvents(); + + uint32_t GetWorkerCount() const + { + return static_cast(workerThreads.size()); + } + +private: + // Main loop for each worker thread + void WorkerLoop(uint32_t workerIndex); + + // Try to pop and execute one job from the queue (used by main thread during WaitForWork) + bool ExecuteOneJob(); + + std::vector workerThreads; + JobQueue jobQueue; + std::atomic running; +}; diff --git a/Engine/include/ObjectManager.hpp b/Engine/include/ObjectManager.hpp index efa48f7e..707841d5 100644 --- a/Engine/include/ObjectManager.hpp +++ b/Engine/include/ObjectManager.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "glm/matrix.hpp" @@ -73,6 +74,7 @@ class ObjectManager return; } + std::lock_guard lock(queueMutex); functionQueue.push_back([component, func]() { func(component); @@ -87,6 +89,7 @@ class ObjectManager std::cerr << "nullptr object!" << '\n'; return; } + std::lock_guard lock(queueMutex); functionQueue.push_back([object, func]() { func(object); @@ -130,6 +133,7 @@ class ObjectManager float roughness = 0.3f; std::vector> functionQueue; + std::mutex queueMutex; // Protects functionQueue and objectsToBeDeleted //For ObjectController // Debug Options diff --git a/Engine/include/ThreadManager.hpp b/Engine/include/ThreadManager.hpp deleted file mode 100644 index e8cf56ec..00000000 --- a/Engine/include/ThreadManager.hpp +++ /dev/null @@ -1,41 +0,0 @@ -//Author: DOYEONG LEE -//Project: CubeEngine -//File: ThreadManager.hpp -#pragma once -#include -#include -#include -#include -#include -#include -#include - -class ThreadManager { -public: - ThreadManager() : running(false) {} - ~ThreadManager() { Stop(); } - - void Start(); - void Stop(); - - void QueueGameUpdate(const std::function& updateFunc, float dt); - void WaitForGameUpdates(); - - void ProcessEvents(); - -private: - void GameUpdateLoop(); - void SDLEventLoop(); - - std::thread gameUpdateThread; - std::thread sdlEventThread; - - std::mutex gameUpdateMutex; - std::condition_variable gameUpdateCV; - std::queue, float>> gameUpdateQueue; - - std::mutex sdlEventMutex; - std::queue sdlEventQueue; - - std::atomic running; -}; \ No newline at end of file diff --git a/Engine/source/BasicComponents/DynamicSprite.cpp b/Engine/source/BasicComponents/DynamicSprite.cpp index 0af6ee31..2bd95055 100644 --- a/Engine/source/BasicComponents/DynamicSprite.cpp +++ b/Engine/source/BasicComponents/DynamicSprite.cpp @@ -323,7 +323,7 @@ void DynamicSprite::UpdateProjection() } } -void DynamicSprite::AddQuad(glm::vec4 color_) +void DynamicSprite::CreateQuad(glm::vec4 color_) { SubMesh subMesh; @@ -380,7 +380,7 @@ void DynamicSprite::AddQuad(glm::vec4 color_) AddSpriteToManager(); } -void DynamicSprite::AddQuadWithTexture(std::string name_, glm::vec4 color_, bool isTexel_) +void DynamicSprite::CreateQuadWithTexture(std::string name_, glm::vec4 color_, bool isTexel_) { SubMesh subMesh; @@ -465,7 +465,7 @@ void DynamicSprite::CreateMesh3D(MeshType type, const std::filesystem::path& pat AddSpriteToManager(); } -void DynamicSprite::LoadAnimation(const std::filesystem::path& spriteInfoFile, std::string name) +void DynamicSprite::LoadAnimationData(const std::filesystem::path& spriteInfoFile, std::string name) { hotSpotList.clear(); frameTexel.clear(); @@ -487,7 +487,7 @@ void DynamicSprite::LoadAnimation(const std::filesystem::path& spriteInfoFile, s //texturePtr = Engine::GetTextureManager().Load(text, true); //frameSize = texturePtr->GetSize(); Engine::Instance().GetRenderManager()->LoadTexture(text, name, true); - AddQuadWithTexture(name, glm::vec4(1.f), true); + CreateQuadWithTexture(name, glm::vec4(1.f), true); inFile >> text; while (inFile.eof() == false) diff --git a/Engine/source/BasicComponents/ISprite.cpp b/Engine/source/BasicComponents/ISprite.cpp index 40cc833f..e72e8c63 100644 --- a/Engine/source/BasicComponents/ISprite.cpp +++ b/Engine/source/BasicComponents/ISprite.cpp @@ -9,6 +9,33 @@ #include "VKRenderManager.hpp" #include "DXRenderManager.hpp" +void ISprite::AddQuad(glm::vec4 color_) +{ + Engine::GetObjectManager().QueueComponentFunction(this, + [=](ISprite* sprite) + { + this->CreateQuad(color_); + }); +} + +void ISprite::AddQuadWithTexture(std::string name_, glm::vec4 color_, bool isTexel_) +{ + Engine::GetObjectManager().QueueComponentFunction(this, + [=](ISprite* sprite) + { + this->CreateQuadWithTexture(name_, color_, isTexel_); + }); +} + +void ISprite::LoadAnimation(const std::filesystem::path& spriteInfoFile, std::string name) +{ + Engine::GetObjectManager().QueueComponentFunction(this, + [=](ISprite* sprite) + { + this->LoadAnimationData(spriteInfoFile, name); + }); +} + void ISprite::AddMesh3D(MeshType type, const std::filesystem::path& path, int stacks_, int slices_, glm::vec4 color, float metallic_, float roughness_) { Engine::GetObjectManager().QueueComponentFunction(this, diff --git a/Engine/source/Engine.cpp b/Engine/source/Engine.cpp index 6c59f1dc..2c4a40a1 100644 --- a/Engine/source/Engine.cpp +++ b/Engine/source/Engine.cpp @@ -43,7 +43,7 @@ void Engine::Init(const char* title, int windowWidth, int windowHeight, bool ful spriteManager = new SpriteManager; - threadManager.Start(); + jobSystem.Start(); } void Engine::Update() @@ -60,7 +60,14 @@ void Engine::Update() if (timer.GetFrameRate() == FrameRate::UNLIMIT || deltaTime >= timer.GetFramePerTime()) { Uint64 winFlag = SDL_GetWindowFlags(window.GetWindow()); - threadManager.ProcessEvents(); + // Save previous frame's input state before polling new events + inputManager.UpdatePreviousState(); + + jobSystem.ProcessEvents(); + + // Capture immutable input snapshot for this frame + inputSnapshot = inputManager.CreateSnapshot(); + timer.ResetLastTimeStamp(); frameCount++; @@ -93,7 +100,7 @@ void Engine::End() break; } - threadManager.Stop(); + jobSystem.Stop(); } void Engine::SetFPS(FrameRate fps) diff --git a/Engine/source/GameStateManager.cpp b/Engine/source/GameStateManager.cpp index 05466446..d949014c 100644 --- a/Engine/source/GameStateManager.cpp +++ b/Engine/source/GameStateManager.cpp @@ -172,12 +172,39 @@ void GameStateManager::UpdateGameLogic(float dt) } else { + // Phase 1: Main thread — level logic (uses InputSnapshot, may queue object operations) levelList.at(static_cast(currentLevel))->Update(dt); - Engine::GetObjectManager().Update(dt); - Engine::GetParticleManager().Update(dt); + + // Phase 2: Parallel jobs — independent system updates + auto& js = Engine::GetJobSystem(); + + auto objectsHandle = js.QueueWork([dt]() + { + Engine::GetObjectManager().Update(dt); + }); + + auto particleHandle = js.QueueWork([dt]() + { + Engine::GetParticleManager().Update(dt); + }); + + auto spriteHandle = js.QueueWork([dt]() + { + Engine::GetSpriteManager().Update(dt); + }); + + // Wait for object updates before physics (objects may modify velocities) + js.WaitForAll({ objectsHandle, particleHandle, spriteHandle }); + + // Phase 3: Physics runs after object updates to avoid race conditions + auto physicsHandle = js.QueueWork([dt]() + { + Engine::GetPhysicsManager().Update(dt); + }); + js.WaitForWork(physicsHandle); + + // Phase 4: Main thread — camera (uses InputManager directly for mouse mode) Engine::GetCameraManager().Update(); - Engine::GetPhysicsManager().Update(dt); - Engine::GetSpriteManager().Update(dt); } } diff --git a/Engine/source/InputManager.cpp b/Engine/source/InputManager.cpp index 5b0201a7..04a95fd1 100644 --- a/Engine/source/InputManager.cpp +++ b/Engine/source/InputManager.cpp @@ -1,187 +1,207 @@ -//Author: DOYEONG LEE -//Project: CubeEngine -//File: InputManager.cpp -#include "InputManager.hpp" - -#include "Engine.hpp" - -InputManager::InputManager() -{ - //Engine::GetLogger().LogDebug(LogCategory::Engine, "Input Manager Initialized"); -} - -InputManager::~InputManager() -{ - Engine::GetLogger().LogDebug(LogCategory::Engine, "Input Manager Deleted"); -} - -void InputManager::InputPollEvent(SDL_Event& event) -{ - switch (event.type) - { - case SDL_EVENT_KEY_DOWN: - KeyDown(static_cast(event.key.key)); - Engine::GetLogger().LogEvent(LogCategory::Engine, "Key Down : " + std::to_string(event.key.key)); - break; - case SDL_EVENT_KEY_UP: - KeyUp(static_cast(event.key.key)); - Engine::GetLogger().LogEvent(LogCategory::Engine, "Key Up : " + std::to_string(event.key.key)); - break; - case SDL_EVENT_MOUSE_BUTTON_DOWN: - if (!(ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) || ImGui::IsAnyItemHovered())) - { - MouseButtonDown(static_cast(event.button.button), event.button.x, event.button.y); - Engine::GetLogger().LogEvent(LogCategory::Engine, "Mouse Down : " + std::to_string(event.button.button)); - } - break; - case SDL_EVENT_MOUSE_BUTTON_UP: - if (!(ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) || ImGui::IsAnyItemHovered())) - { - MouseButtonUp(static_cast(event.button.button), event.button.x, event.button.y); - Engine::GetLogger().LogEvent(LogCategory::Engine, "Mouse Up : " + std::to_string(event.button.button)); - } - break; - case SDL_EVENT_MOUSE_WHEEL: - if (!(ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) || ImGui::IsAnyItemHovered())) - { - MouseWheel(event); - Engine::GetLogger().LogEvent(LogCategory::Engine, "Mouse Wheel : " + std::to_string(static_cast(event.wheel.y))); - } - break; - case SDL_EVENT_MOUSE_MOTION: - mouseRelX = event.motion.xrel; - mouseRelY = event.motion.yrel; - break; - default: - break; - } - if (event.type == SDL_EVENT_MOUSE_MOTION) - { - mouseRelX = event.motion.xrel; - mouseRelY = event.motion.yrel; - } -} - -bool InputManager::IsKeyPressed(KEYBOARDKEYS keycode) -{ - auto it = keyStates.find(keycode); - if (it != keyStates.end()) - { - keyStatePrev[keycode] = it->second; - return it->second; - } - return false; -} - -bool InputManager::IsKeyPressOnce(KEYBOARDKEYS keycode) -{ - auto it = keyStates.find(keycode); - if (it != keyStates.end()) - { - bool isPressed = it->second && !keyStatePrev[keycode]; - keyStatePrev[keycode] = it->second; - return isPressed; - } - return false; -} - -bool InputManager::IsKeyReleaseOnce(KEYBOARDKEYS keycode) -{ - auto it = keyStates.find(keycode); - if (it != keyStates.end()) - { - bool isReleased = !it->second && keyStatePrev[keycode]; - keyStatePrev[keycode] = it->second; - return isReleased; - } - return false; -} - -bool InputManager::IsMouseButtonPressed(MOUSEBUTTON button) -{ - auto it = mouseButtonStates.find(button); - if (it != mouseButtonStates.end()) - { - mouseButtonStatePrev[button] = it->second; - return it->second; - } - return false; -} - -bool InputManager::IsMouseButtonPressOnce(MOUSEBUTTON button) -{ - auto it = mouseButtonStates.find(button); - if (it != mouseButtonStates.end()) - { - bool isPressed = it->second && !mouseButtonStatePrev[button]; - mouseButtonStatePrev[button] = it->second; - return isPressed; - } - return false; -} - -bool InputManager::IsMouseButtonReleaseOnce(MOUSEBUTTON button) -{ - auto it = mouseButtonStates.find(button); - if (it != mouseButtonStates.end()) - { - bool isReleased = !it->second && mouseButtonStates[button]; - mouseButtonStatePrev[button] = it->second; - return isReleased; - } - return false; -} - -glm::vec2 InputManager::GetMousePosition() -{ - float mouseX, mouseY; - SDL_GetMouseState(&mouseX, &mouseY); - glm::vec2 pos = { mouseX, mouseY }; - glm::vec2 windowViewSize = Engine::Instance().GetCameraManager().GetViewSize(); - int w = 0; - int h = 0; - SDL_GetWindowSizeInPixels(Engine::Instance().GetWindow().GetWindow(), &w, &h); - float zoom = Engine::Instance().GetCameraManager().GetZoom(); - - pos = { windowViewSize.x / 2.f * (pos.x / (static_cast(w) / 2.f) - 1) / zoom, windowViewSize.y / 2.f * (pos.y / (static_cast(h) / 2.f) - 1) / zoom }; - return pos; -} - -glm::vec2 InputManager::GetMouseWheelMotion() -{ - return mouseWheelMotion; -} - -void InputManager::ResetWheelMotion() -{ - mouseWheelMotion = { 0.f, 0.f }; -} - -void InputManager::SetRelativeMouseMode(bool state) -{ - SDL_Window* window = Engine::Instance().GetWindow().GetWindow(); - if (state == true) - { - SDL_SetWindowRelativeMouseMode(window, true); - } - else - { - SDL_SetWindowRelativeMouseMode(window, false); - } - mouseRelX = 0; - mouseRelY = 0; -} - -bool InputManager::GetRelativeMouseMode() -{ - SDL_Window* window = Engine::Instance().GetWindow().GetWindow(); - return SDL_GetWindowRelativeMouseMode(window); -} - -glm::vec2 InputManager::GetRelativeMouseState() -{ - glm::vec2 temp{ mouseRelX, mouseRelY }; - mouseRelX = 0; - mouseRelY = 0; - return temp; -} \ No newline at end of file +//Author: DOYEONG LEE +//Project: CubeEngine +//File: InputManager.cpp +#include "InputManager.hpp" + +#include "Engine.hpp" + +InputManager::InputManager() +{ + //Engine::GetLogger().LogDebug(LogCategory::Engine, "Input Manager Initialized"); +} + +InputManager::~InputManager() +{ + Engine::GetLogger().LogDebug(LogCategory::Engine, "Input Manager Deleted"); +} + +void InputManager::InputPollEvent(SDL_Event& event) +{ + switch (event.type) + { + case SDL_EVENT_KEY_DOWN: + KeyDown(static_cast(event.key.key)); + Engine::GetLogger().LogEvent(LogCategory::Engine, "Key Down : " + std::to_string(event.key.key)); + break; + case SDL_EVENT_KEY_UP: + KeyUp(static_cast(event.key.key)); + Engine::GetLogger().LogEvent(LogCategory::Engine, "Key Up : " + std::to_string(event.key.key)); + break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + if (!(ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) || ImGui::IsAnyItemHovered())) + { + MouseButtonDown(static_cast(event.button.button), event.button.x, event.button.y); + Engine::GetLogger().LogEvent(LogCategory::Engine, "Mouse Down : " + std::to_string(event.button.button)); + } + break; + case SDL_EVENT_MOUSE_BUTTON_UP: + if (!(ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) || ImGui::IsAnyItemHovered())) + { + MouseButtonUp(static_cast(event.button.button), event.button.x, event.button.y); + Engine::GetLogger().LogEvent(LogCategory::Engine, "Mouse Up : " + std::to_string(event.button.button)); + } + break; + case SDL_EVENT_MOUSE_WHEEL: + if (!(ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) || ImGui::IsAnyItemHovered())) + { + MouseWheel(event); + Engine::GetLogger().LogEvent(LogCategory::Engine, "Mouse Wheel : " + std::to_string(static_cast(event.wheel.y))); + } + break; + case SDL_EVENT_MOUSE_MOTION: + mouseRelX = event.motion.xrel; + mouseRelY = event.motion.yrel; + break; + default: + break; + } + if (event.type == SDL_EVENT_MOUSE_MOTION) + { + mouseRelX = event.motion.xrel; + mouseRelY = event.motion.yrel; + } +} + +bool InputManager::IsKeyPressed(KEYBOARDKEYS keycode) +{ + auto it = keyStates.find(keycode); + if (it != keyStates.end()) + { + keyStatePrev[keycode] = it->second; + return it->second; + } + return false; +} + +bool InputManager::IsKeyPressOnce(KEYBOARDKEYS keycode) +{ + auto it = keyStates.find(keycode); + if (it != keyStates.end()) + { + bool isPressed = it->second && !keyStatePrev[keycode]; + keyStatePrev[keycode] = it->second; + return isPressed; + } + return false; +} + +bool InputManager::IsKeyReleaseOnce(KEYBOARDKEYS keycode) +{ + auto it = keyStates.find(keycode); + if (it != keyStates.end()) + { + bool isReleased = !it->second && keyStatePrev[keycode]; + keyStatePrev[keycode] = it->second; + return isReleased; + } + return false; +} + +bool InputManager::IsMouseButtonPressed(MOUSEBUTTON button) +{ + auto it = mouseButtonStates.find(button); + if (it != mouseButtonStates.end()) + { + mouseButtonStatePrev[button] = it->second; + return it->second; + } + return false; +} + +bool InputManager::IsMouseButtonPressOnce(MOUSEBUTTON button) +{ + auto it = mouseButtonStates.find(button); + if (it != mouseButtonStates.end()) + { + bool isPressed = it->second && !mouseButtonStatePrev[button]; + mouseButtonStatePrev[button] = it->second; + return isPressed; + } + return false; +} + +bool InputManager::IsMouseButtonReleaseOnce(MOUSEBUTTON button) +{ + auto it = mouseButtonStates.find(button); + if (it != mouseButtonStates.end()) + { + bool isReleased = !it->second && mouseButtonStates[button]; + mouseButtonStatePrev[button] = it->second; + return isReleased; + } + return false; +} + +glm::vec2 InputManager::GetMousePosition() +{ + float mouseX, mouseY; + SDL_GetMouseState(&mouseX, &mouseY); + glm::vec2 pos = { mouseX, mouseY }; + glm::vec2 windowViewSize = Engine::Instance().GetCameraManager().GetViewSize(); + int w = 0; + int h = 0; + SDL_GetWindowSizeInPixels(Engine::Instance().GetWindow().GetWindow(), &w, &h); + float zoom = Engine::Instance().GetCameraManager().GetZoom(); + + pos = { windowViewSize.x / 2.f * (pos.x / (static_cast(w) / 2.f) - 1) / zoom, windowViewSize.y / 2.f * (pos.y / (static_cast(h) / 2.f) - 1) / zoom }; + return pos; +} + +glm::vec2 InputManager::GetMouseWheelMotion() +{ + return mouseWheelMotion; +} + +void InputManager::ResetWheelMotion() +{ + mouseWheelMotion = { 0.f, 0.f }; +} + +void InputManager::SetRelativeMouseMode(bool state) +{ + SDL_Window* window = Engine::Instance().GetWindow().GetWindow(); + if (state == true) + { + SDL_SetWindowRelativeMouseMode(window, true); + } + else + { + SDL_SetWindowRelativeMouseMode(window, false); + } + mouseRelX = 0; + mouseRelY = 0; +} + +bool InputManager::GetRelativeMouseMode() +{ + SDL_Window* window = Engine::Instance().GetWindow().GetWindow(); + return SDL_GetWindowRelativeMouseMode(window); +} + +glm::vec2 InputManager::GetRelativeMouseState() +{ + glm::vec2 temp{ mouseRelX, mouseRelY }; + mouseRelX = 0; + mouseRelY = 0; + return temp; +} + +void InputManager::UpdatePreviousState() +{ + // Batch update prev states at frame boundary instead of per-query + keyStatePrev = keyStates; + mouseButtonStatePrev = mouseButtonStates; +} + +InputSnapshot InputManager::CreateSnapshot() +{ + InputSnapshot snap; + snap.keyStates = keyStates; + snap.keyStatePrev = keyStatePrev; + snap.mouseStates = mouseButtonStates; + snap.mouseStatePrev = mouseButtonStatePrev; + snap.mouseWheel = mouseWheelMotion; + snap.relativeMouseState = { mouseRelX, mouseRelY }; + snap.mousePosition = GetMousePosition(); + return snap; +} \ No newline at end of file diff --git a/Engine/source/JobSystem.cpp b/Engine/source/JobSystem.cpp new file mode 100644 index 00000000..471cdaad --- /dev/null +++ b/Engine/source/JobSystem.cpp @@ -0,0 +1,267 @@ +//Author: DOYEONG LEE +//Project: CubeEngine +//File: JobSystem.cpp +#include "Engine.hpp" +#include "imgui.h" +#include "imgui_impl_sdl3.h" +#include "DXRenderManager.hpp" + +#include + +void JobQueue::Push(Job job) +{ + { + std::lock_guard lock(queueMutex); + jobs.push_back(std::move(job)); + } + cv.notify_one(); +} + +std::optional JobQueue::TryPop() +{ + std::lock_guard lock(queueMutex); + if (jobs.empty()) + { + return std::nullopt; + } + + Job job = std::move(jobs.front()); + jobs.pop_front(); + return job; +} + +std::optional JobQueue::WaitAndPop(std::atomic& running) +{ + std::unique_lock lock(queueMutex); + cv.wait(lock, [&] + { + return !running.load(std::memory_order_relaxed) || !jobs.empty(); + }); + + if (!running.load(std::memory_order_relaxed) && jobs.empty()) + { + return std::nullopt; + } + + Job job = std::move(jobs.front()); + jobs.pop_front(); + return job; +} + +void JobQueue::NotifyAll() +{ + cv.notify_all(); +} + +void JobSystem::Start(uint32_t threadCount) +{ + if (running.load()) + { + return; + } + + if (threadCount == 0) + { + threadCount = std::max(1u, std::thread::hardware_concurrency() - 1); + } + + running = true; + workerThreads.reserve(threadCount); + for (uint32_t i = 0; i < threadCount; ++i) + { + workerThreads.emplace_back(&JobSystem::WorkerLoop, this, i); + } + + Engine::GetLogger().LogDebug(LogCategory::Engine, + "Job System Initialized (" + std::to_string(threadCount) + " workers)"); +} + +void JobSystem::Stop() +{ + if (!running.load()) + { + return; + } + + running = false; + // Wake all workers so they can exit their loop + jobQueue.NotifyAll(); + + for (auto& t : workerThreads) + { + if (t.joinable()) + { + t.join(); + } + } + workerThreads.clear(); + + Engine::GetLogger().LogDebug(LogCategory::Engine, "Job System Stop"); +} + +JobHandle JobSystem::QueueWork(std::function task) +{ + auto counter = std::make_shared>(1); + jobQueue.Push(Job{ std::move(task), counter }); + + JobHandle handle; + handle.counter = counter; + return handle; +} + +JobHandle JobSystem::QueueParallelWork( + uint32_t count, + std::function body, + uint32_t batchSize) +{ + if (count == 0) + { + return JobHandle{}; + } + + uint32_t batchCount = (count + batchSize - 1) / batchSize; + auto counter = std::make_shared>(static_cast(batchCount)); + + for (uint32_t b = 0; b < batchCount; ++b) + { + uint32_t begin = b * batchSize; + uint32_t end = std::min(begin + batchSize, count); + jobQueue.Push(Job{ + [body, begin, end]() + { + body(begin, end); + }, + counter + }); + } + + JobHandle handle; + handle.counter = counter; + return handle; +} + +void JobSystem::WaitForWork(const JobHandle& handle) +{ + // Main thread helps execute jobs while waiting for the target job + while (!handle.IsComplete()) + { + if (!ExecuteOneJob()) + { + std::this_thread::yield(); + } + } +} + +void JobSystem::WaitForAll(const std::vector& handles) +{ + // Wait for each handle, helping execute jobs in between + bool allComplete = false; + while (!allComplete) + { + allComplete = true; + for (const auto& h : handles) + { + if (!h.IsComplete()) + { + allComplete = false; + break; + } + } + + if (!allComplete) + { + if (!ExecuteOneJob()) + { + std::this_thread::yield(); + } + } + } +} + +void JobSystem::WorkerLoop(uint32_t /*workerIndex*/) +{ + while (running.load(std::memory_order_relaxed)) + { + auto item = jobQueue.WaitAndPop(running); + if (!item.has_value()) + { + continue; + } + + // Store counter before executing — ensures decrement even if task throws + auto counter = item->counter; + try + { + item->task(); + } + catch (...) + { + Engine::GetLogger().LogError(LogCategory::Engine, "Job threw an exception"); + } + counter->fetch_sub(1, std::memory_order_release); + } +} + +bool JobSystem::ExecuteOneJob() +{ + auto item = jobQueue.TryPop(); + if (!item.has_value()) + { + return false; + } + + auto counter = item->counter; + try + { + item->task(); + } + catch (...) + { + Engine::GetLogger().LogError(LogCategory::Engine, "Main thread caught job exception"); + } + counter->fetch_sub(1, std::memory_order_release); + return true; +} + +// SDL Event Processing (main thread only) +void JobSystem::ProcessEvents() +{ + Engine::GetInputManager().ResetWheelMotion(); + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL3_ProcessEvent(&event); + + if (ImGui::IsAnyItemActive() == false) + { + Engine::GetInputManager().InputPollEvent(event); + } + + switch (event.type) + { + case SDL_EVENT_QUIT: + Engine::GetGameStateManager().SetGameState(State::UNLOAD); + break; + case SDL_EVENT_WINDOW_RESIZED: + Engine::Instance().ResetDeltaTime(); + if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::DX) + { + dynamic_cast(Engine::GetRenderManager())->SetResize(); + } + SDL_FALLTHROUGH; + case SDL_EVENT_WINDOW_MOVED: + case SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EVENT_WINDOW_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + Engine::Instance().ResetDeltaTime(); + break; + default: + break; + } + + if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::GL) + { + Engine::GetWindow().UpdateWindowGL(event); + } + } +} diff --git a/Engine/source/ObjectManager.cpp b/Engine/source/ObjectManager.cpp index 3642dee7..25bfd886 100644 --- a/Engine/source/ObjectManager.cpp +++ b/Engine/source/ObjectManager.cpp @@ -30,8 +30,31 @@ ObjectManager::~ObjectManager() void ObjectManager::Update(float dt) { - std::for_each(objectMap.begin(), objectMap.end(), [&](auto& obj) { obj.second->Update(dt); }); - //DeleteObjectsFromList(); + if (objectMap.empty()) + { + return; + } + + // Cache map values into contiguous vector for parallel iteration + std::vector objects; + objects.reserve(objectMap.size()); + for (auto& [id, obj] : objectMap) + { + objects.push_back(obj.get()); + } + + auto handle = Engine::GetJobSystem().QueueParallelWork( + static_cast(objects.size()), + [&objects, dt](uint32_t begin, uint32_t end) + { + for (uint32_t i = begin; i < end; ++i) + { + objects[i]->Update(dt); + } + }, + 32 // Batch size + ); + Engine::GetJobSystem().WaitForWork(handle); } void ObjectManager::DeleteObjectsFromList() @@ -56,12 +79,9 @@ void ObjectManager::Draw(float dt) void ObjectManager::Destroy(int id) { - //objectsToBeDeleted.push_back(id); - //objectMap.at(id).get()->DestroyAllComponents(); - + // Thread-safe: objects may self-destruct during parallel updates + std::lock_guard lock(queueMutex); objectsToBeDeleted.push_back(id); - //std::for_each(objectsToBeDeleted.begin(), objectsToBeDeleted.end(), [&](int id) { objectMap.at(id).reset(); objectMap.erase(id); }); - //objectsToBeDeleted.clear(); } void ObjectManager::DestroyAllObjects() diff --git a/Engine/source/PhysicsManager.cpp b/Engine/source/PhysicsManager.cpp index c60e7261..f4441217 100644 --- a/Engine/source/PhysicsManager.cpp +++ b/Engine/source/PhysicsManager.cpp @@ -109,15 +109,27 @@ void PhysicsManager::UpdatePhysics2D(float dt) void PhysicsManager::ApplyMovement2D(float dt) { - // Update owner positions using linear velocity - for (Physics2D* body : bodies2D) + if (bodies2D.empty()) { - Object* owner = body->GetOwner(); - glm::vec2 currentVelocity = body->GetVelocity(); - - owner->SetXPosition(owner->GetPosition().x + currentVelocity.x * dt); - owner->SetYPosition(owner->GetPosition().y + currentVelocity.y * dt); + return; } + + // Parallel integration: each body's position update is independent + auto handle = Engine::GetJobSystem().QueueParallelWork( + static_cast(bodies2D.size()), + [this, dt](uint32_t begin, uint32_t end) + { + for (uint32_t i = begin; i < end; ++i) + { + Object* owner = bodies2D[i]->GetOwner(); + glm::vec2 currentVelocity = bodies2D[i]->GetVelocity(); + owner->SetXPosition(owner->GetPosition().x + currentVelocity.x * dt); + owner->SetYPosition(owner->GetPosition().y + currentVelocity.y * dt); + } + }, + 16 + ); + Engine::GetJobSystem().WaitForWork(handle); } @@ -285,11 +297,24 @@ void PhysicsManager::UpdatePhysics3D(float dt) void PhysicsManager::Integrate3D(float dt) { - // Delegate integration to each 3D physics body - for (Physics3D* body : bodies3D) + if (bodies3D.empty()) { - body->UpdatePhysics(dt); + return; } + + // Parallel integration: each body's physics update is independent + auto handle = Engine::GetJobSystem().QueueParallelWork( + static_cast(bodies3D.size()), + [this, dt](uint32_t begin, uint32_t end) + { + for (uint32_t i = begin; i < end; ++i) + { + bodies3D[i]->UpdatePhysics(dt); + } + }, + 16 + ); + Engine::GetJobSystem().WaitForWork(handle); } std::vector PhysicsManager::BroadPhase3D(float dt) diff --git a/Engine/source/ThreadManager.cpp b/Engine/source/ThreadManager.cpp deleted file mode 100644 index 5d7ddf18..00000000 --- a/Engine/source/ThreadManager.cpp +++ /dev/null @@ -1,114 +0,0 @@ -//Author: DOYEONG LEE -//Project: CubeEngine -//File: ThreadManager.cpp -#include "Engine.hpp" -#include "imgui.h" -#include "imgui_impl_sdl3.h" -#include "DXRenderManager.hpp" - -void ThreadManager::Start() -{ - running = true; - gameUpdateThread = std::thread(&ThreadManager::GameUpdateLoop, this); - sdlEventThread = std::thread(&ThreadManager::SDLEventLoop, this); - Engine::GetLogger().LogDebug(LogCategory::Engine, "Thread Manager Initialized"); -} - -void ThreadManager::Stop() -{ - running = false; - gameUpdateCV.notify_one(); - if (gameUpdateThread.joinable()) gameUpdateThread.join(); - if (sdlEventThread.joinable()) sdlEventThread.join(); - Engine::GetLogger().LogDebug(LogCategory::Engine, "Thread Manager Stop"); -} - -void ThreadManager::QueueGameUpdate(const std::function& updateFunc, float dt) -{ - { - std::lock_guard lock(gameUpdateMutex); - gameUpdateQueue.push({ updateFunc, dt }); - } - gameUpdateCV.notify_one(); -} - -void ThreadManager::WaitForGameUpdates() -{ - while (true) - { - std::unique_lock lock(gameUpdateMutex); - if (gameUpdateQueue.empty()) - { - break; - } - lock.unlock(); - std::this_thread::yield(); - } -} - -void ThreadManager::GameUpdateLoop() -{ - while (running) - { - std::unique_lock lock(gameUpdateMutex); - gameUpdateCV.wait(lock, [this] { return !running || !gameUpdateQueue.empty(); }); - - while (!gameUpdateQueue.empty()) { - auto [updateFunc, dt] = gameUpdateQueue.front(); - gameUpdateQueue.pop(); - lock.unlock(); - - updateFunc(dt); - - lock.lock(); - } - } -} - -void ThreadManager::SDLEventLoop() -{ - while (running) - { - std::this_thread::yield(); - } -} - -void ThreadManager::ProcessEvents() -{ - Engine::GetInputManager().ResetWheelMotion(); - SDL_Event event; - while (SDL_PollEvent(&event)) - { - ImGui_ImplSDL3_ProcessEvent(&event); - - if (ImGui::IsAnyItemActive() == false) - { - Engine::GetInputManager().InputPollEvent(event); - } - - switch (event.type) - { - case SDL_EVENT_QUIT: - Engine::GetGameStateManager().SetGameState(State::UNLOAD); - break; - case SDL_EVENT_WINDOW_RESIZED: - Engine::Instance().ResetDeltaTime(); - if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::DX) - dynamic_cast(Engine::GetRenderManager())->SetResize(); - SDL_FALLTHROUGH; - case SDL_EVENT_WINDOW_MOVED: - case SDL_EVENT_WINDOW_MINIMIZED: - case SDL_EVENT_WINDOW_MAXIMIZED: - case SDL_EVENT_WINDOW_RESTORED: - Engine::Instance().ResetDeltaTime(); - break; - default: - break; - } - - if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::GL) - { - Engine::GetWindow().UpdateWindowGL(event); - } - } -} \ No newline at end of file diff --git a/Game/source/3DPhysicsDemo.cpp b/Game/source/3DPhysicsDemo.cpp index 598b7143..a86f7d1e 100644 --- a/Game/source/3DPhysicsDemo.cpp +++ b/Game/source/3DPhysicsDemo.cpp @@ -93,19 +93,19 @@ void PhysicsDemo::Update(float dt) glm::vec3 movement(0.0f); float speed = 200.0f * dt; - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::UP)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::UP)) { movement += Engine::GetCameraManager().GetBackVector() * speed; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::DOWN)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::DOWN)) { movement -= Engine::GetCameraManager().GetBackVector() * speed; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::RIGHT)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::RIGHT)) { movement += Engine::GetCameraManager().GetRightVector() * speed; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LEFT)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::LEFT)) { movement -= Engine::GetCameraManager().GetRightVector() * speed; } @@ -155,7 +155,7 @@ void PhysicsDemo::Update(float dt) } } - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::R)) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::R)) { Engine::GetGameStateManager().SetGameState(State::RESTART); } diff --git a/Game/source/BeatEmUpDemo/BEUPlayer.cpp b/Game/source/BeatEmUpDemo/BEUPlayer.cpp index c3805331..c9a46630 100644 --- a/Game/source/BeatEmUpDemo/BEUPlayer.cpp +++ b/Game/source/BeatEmUpDemo/BEUPlayer.cpp @@ -217,13 +217,13 @@ void BEUPlayer::CollideObject(Object* obj) void BEUPlayer::Control(float dt) { - if (Engine::GetInputManager().IsKeyReleaseOnce(KEYBOARDKEYS::DOWN) + if (Engine::GetInputSnapshot().IsKeyReleaseOnce(KEYBOARDKEYS::DOWN) && IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false && IsStateOn(BEUObjectStates::ATTACK) == false) { SetStateOff(BEUObjectStates::MOVE); GetComponent()->PlayAnimation(0); } - else if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::DOWN) + else if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::DOWN) && IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false && IsStateOn(BEUObjectStates::ATTACK) == false) { if (IsStateOn(BEUObjectStates::MOVE) == false) @@ -240,13 +240,13 @@ void BEUPlayer::Control(float dt) SetZPosition(GetPosition().z - 10.f * dt); } } - if (Engine::GetInputManager().IsKeyReleaseOnce(KEYBOARDKEYS::UP) + if (Engine::GetInputSnapshot().IsKeyReleaseOnce(KEYBOARDKEYS::UP) && IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false && IsStateOn(BEUObjectStates::ATTACK) == false) { GetComponent()->PlayAnimation(0); SetStateOff(BEUObjectStates::MOVE); } - else if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::UP) + else if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::UP) && IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false && IsStateOn(BEUObjectStates::ATTACK) == false) { if (IsStateOn(BEUObjectStates::MOVE) == false) @@ -263,7 +263,7 @@ void BEUPlayer::Control(float dt) SetZPosition(GetPosition().z + 10.f * dt); } } - if (Engine::GetInputManager().IsKeyReleaseOnce(KEYBOARDKEYS::LEFT) && IsStateOn(BEUObjectStates::ATTACK) == false) + if (Engine::GetInputSnapshot().IsKeyReleaseOnce(KEYBOARDKEYS::LEFT) && IsStateOn(BEUObjectStates::ATTACK) == false) { SetStateOff(BEUObjectStates::MOVE); SetStateOff(BEUObjectStates::MOVEFOWARD); @@ -271,7 +271,7 @@ void BEUPlayer::Control(float dt) if (IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false) GetComponent()->PlayAnimation(0); } - else if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LEFT) + else if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::LEFT) && IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false && IsStateOn(BEUObjectStates::ATTACK) == false) { if (IsStateOn(BEUObjectStates::DIRECTION) == true) @@ -290,7 +290,7 @@ void BEUPlayer::Control(float dt) SetStateOff(BEUObjectStates::DIRECTION); SetXPosition(GetPosition().x - 10.f * dt); } - if (Engine::GetInputManager().IsKeyReleaseOnce(KEYBOARDKEYS::RIGHT) && IsStateOn(BEUObjectStates::ATTACK) == false) + if (Engine::GetInputSnapshot().IsKeyReleaseOnce(KEYBOARDKEYS::RIGHT) && IsStateOn(BEUObjectStates::ATTACK) == false) { SetStateOff(BEUObjectStates::MOVE); SetStateOff(BEUObjectStates::MOVEFOWARD); @@ -298,7 +298,7 @@ void BEUPlayer::Control(float dt) if (IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false) GetComponent()->PlayAnimation(0); } - else if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::RIGHT) + else if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::RIGHT) && IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false && IsStateOn(BEUObjectStates::ATTACK) == false) { if (IsStateOn(BEUObjectStates::DIRECTION) == false) @@ -323,7 +323,7 @@ void BEUPlayer::Control(float dt) Engine::GetCameraManager().SetCameraPosition({ position.x , Engine::GetCameraManager().GetCameraPosition().y, Engine::GetCameraManager().GetCameraPosition().z }); }*/ } - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::X) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::X) && IsStateOn(BEUObjectStates::FALLING) == false && IsStateOn(BEUObjectStates::JUMPING) == false && IsStateOn(BEUObjectStates::ATTACK) == false) { Object::SetYPosition(Object::GetPosition().y + 1.f); @@ -340,7 +340,7 @@ void BEUPlayer::Control(float dt) } } } - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::Z) && IsStateOn(BEUObjectStates::ATTACK) == false) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::Z) && IsStateOn(BEUObjectStates::ATTACK) == false) { if (canAttack == true) { diff --git a/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp b/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp index 8299e8c0..20a5e3e3 100644 --- a/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp +++ b/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp @@ -115,27 +115,27 @@ void BeatEmUpDemo::Init() void BeatEmUpDemo::Update(float dt) { - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::R)) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::R)) { Engine::GetGameStateManager().SetGameState(State::RESTART); } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::W)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::W)) { Engine::GetCameraManager().MoveCameraPos(CameraMoveDir::FOWARD, 10.f * dt); } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::S)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::S)) { Engine::GetCameraManager().MoveCameraPos(CameraMoveDir::BACKWARD, 10.f * dt); } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::A)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::A)) { Engine::GetCameraManager().MoveCameraPos(CameraMoveDir::LEFT, 10.f * dt); } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::D)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::D)) { Engine::GetCameraManager().MoveCameraPos(CameraMoveDir::RIGHT, 10.f * dt); } - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::Q)) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::Q)) { if (rand() % 2 == 1) { diff --git a/Game/source/PlatformDemo/PPlayer.cpp b/Game/source/PlatformDemo/PPlayer.cpp index 37ae4bdd..90bdf0a7 100644 --- a/Game/source/PlatformDemo/PPlayer.cpp +++ b/Game/source/PlatformDemo/PPlayer.cpp @@ -114,13 +114,13 @@ void PPlayer::CollideObject(Object* obj) void PPlayer::Control(float /*dt*/) { - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::DOWN)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::DOWN)) { } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::UP)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::UP)) { } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LEFT)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::LEFT)) { if (IsStateOn(PlayerStates::DIRECTION) == true) { @@ -129,7 +129,7 @@ void PPlayer::Control(float /*dt*/) SetStateOff(PlayerStates::DIRECTION); GetComponent()->AddForceX(-300.f); } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::RIGHT)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::RIGHT)) { if (IsStateOn(PlayerStates::DIRECTION) == false) { @@ -138,13 +138,13 @@ void PPlayer::Control(float /*dt*/) SetStateOn(PlayerStates::DIRECTION); GetComponent()->AddForceX(300.f); } - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::X) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::X) && IsStateOn(PlayerStates::FALLING) == false && IsStateOn(PlayerStates::JUMPING) == false) { Object::SetYPosition(Object::GetPosition().y + 1.f); GetComponent()->SetVelocityY(400.f); } - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::Z)) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::Z)) { if (canAttack == true) { @@ -198,4 +198,4 @@ void PPlayer::Jumping() } } -} \ No newline at end of file +} diff --git a/Game/source/PlatformDemo/PlatformDemo.cpp b/Game/source/PlatformDemo/PlatformDemo.cpp index 43060794..79aa3565 100644 --- a/Game/source/PlatformDemo/PlatformDemo.cpp +++ b/Game/source/PlatformDemo/PlatformDemo.cpp @@ -46,7 +46,7 @@ void PlatformDemo::Init() void PlatformDemo::Update(float dt) { - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::R)) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::R)) { Engine::GetGameStateManager().SetGameState(State::RESTART); } diff --git a/Game/source/PlatformDemo/PlatformDemoSystem.cpp b/Game/source/PlatformDemo/PlatformDemoSystem.cpp index 509d89f2..60901a29 100644 --- a/Game/source/PlatformDemo/PlatformDemoSystem.cpp +++ b/Game/source/PlatformDemo/PlatformDemoSystem.cpp @@ -435,7 +435,7 @@ void PDemoMapEditorDemo::ObjectCreator() target->name = newName; ImGui::InputFloat2("Position", targetP); - glm::vec2 mPos = Engine::GetInputManager().GetMousePosition(); + glm::vec2 mPos = Engine::GetInputSnapshot().GetMousePosition(); glm::vec2 newPosition = { mPos.x - std::fmod(mPos.x, gridSize.x), mPos.y - std::fmod(mPos.y, gridSize.y) }; target->pos = newPosition; @@ -449,7 +449,7 @@ void PDemoMapEditorDemo::ObjectCreator() for (auto& obj : objects) { if (!(obj->GetPosition().x + obj->GetSize().x / 2.f < mPos.x || mPos.y < obj->GetPosition().x - obj->GetSize().x / 2.f - || obj->GetPosition().y + obj->GetSize().y / 2.f < mPos.y || mPos.y < obj->GetPosition().y - obj->GetSize().y / 2.f) && Engine::GetInputManager().IsMouseButtonPressOnce(MOUSEBUTTON::RIGHT)) + || obj->GetPosition().y + obj->GetSize().y / 2.f < mPos.y || mPos.y < obj->GetPosition().y - obj->GetSize().y / 2.f) && Engine::GetInputSnapshot().IsMouseButtonPressOnce(MOUSEBUTTON::RIGHT)) { delete obj; objects.erase(objects.begin() + id); @@ -457,7 +457,7 @@ void PDemoMapEditorDemo::ObjectCreator() } id++; } - if (Engine::GetInputManager().IsMouseButtonPressOnce(MOUSEBUTTON::MIDDLE)) + if (Engine::GetInputSnapshot().IsMouseButtonPressOnce(MOUSEBUTTON::MIDDLE)) { switch (objectNum) { @@ -518,7 +518,7 @@ void PDemoMapEditorDemo::BackgroundCreator() } ImGui::InputFloat2("Position", targetP); - glm::vec2 mPos = Engine::GetInputManager().GetMousePosition(); + glm::vec2 mPos = Engine::GetInputSnapshot().GetMousePosition(); glm::vec2 newPosition = { mPos.x - std::fmod(mPos.x, gridSize.x), -(mPos.y - std::fmod(mPos.y, gridSize.y)) }; target->pos = newPosition; @@ -539,7 +539,7 @@ void PDemoMapEditorDemo::BackgroundCreator() for (int i = 0; i < group.second.size(); i++) { if (!(group.second.at(i).position.x + group.second.at(i).size.x < mPos.x || mPos.y < group.second.at(i).position.x - group.second.at(i).size.x - || group.second.at(i).position.y + group.second.at(i).size.y < mPos.y || mPos.y < group.second.at(i).position.y - group.second.at(i).size.y) && Engine::GetInputManager().IsMouseButtonPressOnce(MOUSEBUTTON::RIGHT)) + || group.second.at(i).position.y + group.second.at(i).size.y < mPos.y || mPos.y < group.second.at(i).position.y - group.second.at(i).size.y) && Engine::GetInputSnapshot().IsMouseButtonPressOnce(MOUSEBUTTON::RIGHT)) { delete group.second.at(i).sprite; group.second.erase(group.second.begin() + i); @@ -547,7 +547,7 @@ void PDemoMapEditorDemo::BackgroundCreator() } } } - if (Engine::GetInputManager().IsMouseButtonPressOnce(MOUSEBUTTON::MIDDLE)) + if (Engine::GetInputSnapshot().IsMouseButtonPressOnce(MOUSEBUTTON::MIDDLE)) { bgm->AddSaveBackgroundList(target->spriteName, "none", target->backgroundType, target->pos, target->size, 0.f, target->speed, { 0.f,0.f }, target->depth, false, target->isAnimation); @@ -567,20 +567,20 @@ void PDemoMapEditorDemo::WallCreator() glm::vec2 midPoint = { (target->startPos.x + target->endPos.x) / 2.f, (target->startPos.y + target->endPos.y) / 2.f }; if (isWallSetting == false) { - glm::vec2 mPos = Engine::GetInputManager().GetMousePosition(); + glm::vec2 mPos = Engine::GetInputSnapshot().GetMousePosition(); target->startPos = { mPos.x - std::fmod(mPos.x, gridSize.x), mPos.y - std::fmod(mPos.y, gridSize.y) }; target->rect->UpdateModel({ target->startPos.x, -target->startPos.y, 0.f }, { 4.f,4.f,0.f }, 0.f); } else { - glm::vec2 mPos = Engine::GetInputManager().GetMousePosition(); + glm::vec2 mPos = Engine::GetInputSnapshot().GetMousePosition(); target->endPos = { mPos.x - std::fmod(mPos.x, gridSize.x), mPos.y - std::fmod(mPos.y, gridSize.y) }; target->rect->UpdateModel({ midPoint.x, -midPoint.y, 0.f }, { abs(target->endPos.x - target->startPos.x) , abs(target->endPos.y - target->startPos.y),0.f }, 0.f); } target->rect->UpdateProjection(); target->rect->UpdateView(); - if (Engine::GetInputManager().IsMouseButtonPressOnce(MOUSEBUTTON::LEFT)) + if (Engine::GetInputSnapshot().IsMouseButtonPressOnce(MOUSEBUTTON::LEFT)) { if (isWallSetting == true) { @@ -600,7 +600,7 @@ void PDemoMapEditorDemo::WallCreator() isWallSetting = true; } } - else if (Engine::GetInputManager().IsMouseButtonPressOnce(MOUSEBUTTON::RIGHT)) + else if (Engine::GetInputSnapshot().IsMouseButtonPressOnce(MOUSEBUTTON::RIGHT)) { if (isWallSetting == true) { diff --git a/Game/source/PocketBallDemo/PocketBallSystem.cpp b/Game/source/PocketBallDemo/PocketBallSystem.cpp index 92fe48ab..b0dab760 100644 --- a/Game/source/PocketBallDemo/PocketBallSystem.cpp +++ b/Game/source/PocketBallDemo/PocketBallSystem.cpp @@ -108,7 +108,7 @@ void PocketBallSystem::Control(float dt) { if (isShot == false) { - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::DOWN)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::DOWN)) { if (distanceMax.x >= 0.f) { @@ -120,22 +120,22 @@ void PocketBallSystem::Control(float dt) cursorPosition = { playerPosition.x + ((distanceMax.x) * cos(angle)) - ((distanceMax.y) * sin(angle)) * dt, playerPosition.y + ((distanceMax.x) * sin(angle)) + ((distanceMax.y) * cos(angle)) * dt }; } } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::UP)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::UP)) { distanceMax += 150.f * dt; cursorPosition = { playerPosition.x + ((distanceMax.x) * cos(angle)) - ((distanceMax.y) * sin(angle)) * dt, playerPosition.y + ((distanceMax.x) * sin(angle)) + ((distanceMax.y) * cos(angle)) * dt }; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LEFT)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::LEFT)) { angle -= 1.5f * dt; cursorPosition = { playerPosition.x + ((distanceMax.x) * cos(angle)) - ((distanceMax.y) * sin(angle)) * dt, playerPosition.y + ((distanceMax.x) * sin(angle)) + ((distanceMax.y) * cos(angle)) * dt }; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::RIGHT)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::RIGHT)) { angle += 1.5f * dt; cursorPosition = { playerPosition.x + ((distanceMax.x) * cos(angle)) - ((distanceMax.y) * sin(angle)) * dt, playerPosition.y + ((distanceMax.x) * sin(angle)) + ((distanceMax.y) * cos(angle)) * dt }; } - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::SPACE)) + if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::SPACE)) { Engine::GetObjectManager().FindObjectWithName("White")->GetComponent()->AddForce({ power * -cos(shotAngle) * 500.f, power * -sin(shotAngle) * 500.f }); isShot = true; From 3d9b6f5d15fb904e79aaf5efe61d9d1fa0a833ce Mon Sep 17 00:00:00 2001 From: Doyeong Lee Date: Fri, 24 Apr 2026 03:48:50 +0900 Subject: [PATCH 2/5] Dynamic BVH, GJK/EPA Integration, and Memory Management Memory & Performance Optimization - Eliminated heap allocation overhead in high-frequency collision loops by adopting thread_local static vectors for temporary calculation buffers. - Replaced expensive pass-by-value parameter passing with const references across all physics mathematical functions to prevent unnecessary memory copying. Broad Phase Spatial Partitioning (Dynamic BVH) - Implemented a custom Dynamic Bounding Volume Hierarchy (BVH) tree using object pooling to efficiently manage 2D and 3D spatial data without dynamic allocations. - Applied Fat AABBs and incremental tree updates using the Surface Area Heuristic (SAH) for 3D and Perimeter Heuristic for 2D, drastically reducing $O(N^2)$ broad-phase bottlenecks down to $O(N \log N)$. Continuous Collision Detection (CCD) Pipeline Upgrade - Optimized the FindClosestCollision CCD loop by querying the newly implemented BVH tree with swept AABBs, eliminating the massive overhead of iterating through all objects in the scene. Narrow Phase Collision Revamp (GJK/EPA Integration) - Replaced the legacy Separating Axis Theorem (SAT) logic with the Gilbert-Johnson-Keerthi (GJK) algorithm for discrete intersection testing, utilizing the Expanding Polytope Algorithm (EPA) for precise penetration depth, normal, and contact point extraction using barycentric coordinates. - Introduced a unified GjkShape proxy structure to handle multiple collider types (Box, Sphere) seamlessly within a single collision pipeline, eliminating the need for virtual function overhead during geometry queries. - Transitioned Continuous Collision Detection (CCD) from SAT-based directional sweeps to a unified SweptGJK routine, applying sub-stepping and binary search techniques to determine accurate Time of Impact (TOI) and prevent tunneling. Physics Solver Stability & Sleep State Optimization - Addressed micro-bouncing and jittering during resting contacts by introducing a velocity threshold in the linear impulse solver (CalculateLinearVelocity), neutralizing restitution for low-velocity impacts. - Refined the wake-up trigger logic during position resolution to prevent rigid bodies from unnecessarily transitioning out of the sleep state upon insignificant micro-collisions. Geometry Translation & Legacy Cleanup - Fixed a broad-phase bug in ComputeAABB where rotational transformations of bounding vertices were ignored, ensuring accurately fitted bounding boxes for rotating polygons. - Removed deprecated SAT helper functions, axis projection loops, and cached separating axis variables to streamline the Physics3D class architecture. --- .../include/BasicComponents/DynamicSprite.hpp | 2 +- Engine/include/BasicComponents/Light.hpp | 2 +- Engine/include/BasicComponents/Physics2D.hpp | 13 +- Engine/include/BasicComponents/Physics3D.hpp | 93 +- .../include/BasicComponents/StaticSprite.hpp | 2 +- Engine/include/Object.hpp | 6 +- Engine/include/PhysicsManager.hpp | 149 +- Engine/source/BasicComponents/Physics2D.cpp | 203 ++- Engine/source/BasicComponents/Physics3D.cpp | 1568 +++++++++++++---- Engine/source/PhysicsManager.cpp | 988 +++++++++-- Game/source/3DPhysicsDemo.cpp | 27 +- 11 files changed, 2470 insertions(+), 583 deletions(-) diff --git a/Engine/include/BasicComponents/DynamicSprite.hpp b/Engine/include/BasicComponents/DynamicSprite.hpp index 79affabc..33db438d 100644 --- a/Engine/include/BasicComponents/DynamicSprite.hpp +++ b/Engine/include/BasicComponents/DynamicSprite.hpp @@ -10,7 +10,7 @@ class Animation; class DynamicSprite : public ISprite { public: - DynamicSprite() : ISprite() { Init(); spriteType = SpriteType::DYNAMIC; }; + DynamicSprite() : ISprite() { spriteType = SpriteType::DYNAMIC; }; ~DynamicSprite() override; void Init() override; diff --git a/Engine/include/BasicComponents/Light.hpp b/Engine/include/BasicComponents/Light.hpp index b849de90..d859297d 100644 --- a/Engine/include/BasicComponents/Light.hpp +++ b/Engine/include/BasicComponents/Light.hpp @@ -20,7 +20,7 @@ class DynamicSprite; class Light : public IComponent { public: - Light() : IComponent(ComponentTypes::LIGHT) { Init(); }; + Light() : IComponent(ComponentTypes::LIGHT) {}; ~Light() override; void Init() override; diff --git a/Engine/include/BasicComponents/Physics2D.hpp b/Engine/include/BasicComponents/Physics2D.hpp index 07056cec..3151ce7d 100644 --- a/Engine/include/BasicComponents/Physics2D.hpp +++ b/Engine/include/BasicComponents/Physics2D.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "Interface/IComponent.hpp" #include "CollisionMode.hpp" @@ -41,7 +42,7 @@ struct Circle class Physics2D : public IComponent { public: - Physics2D() : IComponent(ComponentTypes::PHYSICS2D) { Init(); }; + Physics2D() : IComponent(ComponentTypes::PHYSICS2D) {}; ~Physics2D() override; void Init() override ; @@ -108,6 +109,9 @@ class Physics2D : public IComponent void AddCollidePolygon(glm::vec2 position); void AddCollidePolygonAABB(glm::vec2 min, glm::vec2 max); void AddCollidePolygonAABB(glm::vec2 size); + + // Remove the destroyed body from the cache to prevent dangling pointers + void RemoveFromCollisionCache(Physics2D* otherBody){ separatingAxisCache.erase(otherBody); } private: // Mathematical helper methods for collision detection glm::vec2 FindSATCenter(const std::vector& points); @@ -118,8 +122,8 @@ class Physics2D : public IComponent glm::vec2 RotatePoint(const glm::vec2 point, const glm::vec2 size, float angle); // SAT (Separating Axis Theorem) helper functions - bool IsSeparatingAxis(const glm::vec2 axis, const std::vector points1, const std::vector points2, float* axisDepth, float* min1, float* max1, float* min2, float* max2); - bool IsSeparatingAxis(const glm::vec2 axis, const std::vector pointsPoly, const glm::vec2 pointCir, const float radius, float* axisDepth, float* min1, float* max1, float* min2, float* max2); + bool IsSeparatingAxis(const glm::vec2& axis, const std::vector& points1, const std::vector& points2, float* axisDepth, float* min1, float* max1, float* min2, float* max2); + bool IsSeparatingAxis(const glm::vec2& axis, const std::vector& pointsPoly, const glm::vec2& pointCircle, const float radius, float* axisDepth, float* min1, float* max1, float* min2, float* max2); // Calculates and applies impulses due to collision void CalculateLinearVelocity(Physics2D& body, Physics2D& body2, glm::vec2 normal, float* axisDepth, glm::vec2 contactPoint); @@ -167,6 +171,9 @@ class Physics2D : public IComponent bool isGhostCollision = false; bool isGravityOn = false; bool enableRotationalPhysics = false; + + // Cache for Temporal Coherence (Separating Axis Theorem) + std::unordered_map separatingAxisCache; #ifdef _DEBUG void AddPoint(glm::vec2 pos); std::vector points; diff --git a/Engine/include/BasicComponents/Physics3D.hpp b/Engine/include/BasicComponents/Physics3D.hpp index e95cf3df..6a2dffc9 100644 --- a/Engine/include/BasicComponents/Physics3D.hpp +++ b/Engine/include/BasicComponents/Physics3D.hpp @@ -8,6 +8,7 @@ #pragma warning(disable : 4201) #include #pragma warning(default : 4201) +#include #include "Interface/IComponent.hpp" #include "Object.hpp" @@ -26,7 +27,6 @@ struct CollisionResult glm::vec3 collisionNormal = { 0.f, 0.f, 0.f }; }; - enum class BodyType3D { RIGID = 0, @@ -44,12 +44,35 @@ struct Sphere float radius = 0; }; +struct SupportPoint +{ + glm::vec3 minkowskiDifference; + glm::vec3 pointOnA; + glm::vec3 pointOnB; +}; + +struct EpaFace +{ + glm::vec3 normal; + float distance; + int vertexIndices[3]; +}; + +struct GjkShape +{ + ColliderType3D type; + const std::vector* vertices = nullptr; + glm::vec3 center = { 0.0f, 0.0f, 0.0f }; + float radius = 0.0f; + glm::vec3 offset = { 0.0f, 0.0f, 0.0f }; +}; + #include "CollisionMode.hpp" class Physics3D : public IComponent { public: - Physics3D() : IComponent(ComponentTypes::PHYSICS3D) { Init(); }; + Physics3D() : IComponent(ComponentTypes::PHYSICS3D) {}; ~Physics3D() override; void Init() override ; @@ -122,7 +145,7 @@ class Physics3D : public IComponent bool GetEnableRotationalPhysics() const { return enableRotationalPhysics; } void SetEnableRotationalPhysics(bool v); - //2d->3d + // Methods for handling collisions mapped from two-dimensional space to three-dimensional space std::vector GetCollidePolyhedron() { return collidePolyhedron; } float GetSphereRadius() const { return sphere.radius; } @@ -135,12 +158,24 @@ class Physics3D : public IComponent void AddCollidePolyhedronAABB(glm::vec3 min, glm::vec3 max); void AddCollidePolyhedronAABB(glm::vec3 size); void AddCollideSphere(float r); - //2d->3d + // End of methods for mapping two-dimensional space to three-dimensional space // Made public so PhysicsManager can drive the CCD loop directly. CollisionResult FindClosestCollision(float dt); void CalculateLinearVelocity(Physics3D& body, Physics3D& body2, glm::vec3 normal, float* axisDepth, glm::vec3 contactPoint, float impulseScale = 1.0f); + void RemoveFromCollisionCache(Physics3D* otherBody) + { + separatingAxisCache.erase(otherBody); + } + + + //======== Legacy: SAT ========// + //bool CollisionPPSAT(Object* obj, Object* obj2, CollisionMode mode = static_cast(0)); + //bool CollisionSSSAT(Object* obj, Object* obj2, CollisionMode mode = static_cast(0)); + //bool CollisionPSSAT(Object* poly, Object* sph, CollisionMode mode = static_cast(0)); + //======== Legacy: SAT ========// + private: // Linear Physical Properties glm::vec3 velocity = { 0.0f, 0.0f, 0.0f }; @@ -172,29 +207,41 @@ class Physics3D : public IComponent BodyType3D bodyType = BodyType3D::RIGID; CollisionDetectionMode collisionMode = CollisionDetectionMode::DISCRETE; - //2d->3d - // Discrete Collision Helpers (SAT) - glm::vec3 FindSATCenter(const std::vector& points); - glm::vec3 RotatePoint(const glm::vec3& point, const glm::vec3& position, const glm::quat& rotation); - bool IsSeparatingAxis(const glm::vec3 axis, const std::vector points1, const std::vector points2, float* axisDepth, float* min1, float* max1, float* min2, float* max2); - - glm::vec3 FindClosestPointOnSegment(const glm::vec3& sphereCenter, std::vector& vertices); - bool IsSeparatingAxis(const glm::vec3 axis, const std::vector pointsPoly, const glm::vec3 pointSphere, const float radius, float* axisDepth, float* min1, float* max1, float* min2, float* max2); - - // Continuous Collision Helpers (CCD) - void ProjectPolygon(const std::vector& vertices, const glm::vec3& axis, float& min, float& max); - bool StaticSATIntersection(Physics3D* body1, Physics3D* body2, - const std::vector& rotatedPoly1, const std::vector& rotatedPoly2, - const glm::mat4& rotationMatrix1, const glm::mat4& rotationMatrix2, - glm::vec3& outNormal, float& outDepth); - bool SweptSATOBB(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult); + glm::vec3 ComputePolygonCenter(const std::vector& points); + // Continuous collision detection mode for preventing tunneling at high speeds bool SweptSpheres(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult); - bool SweptSphereVsOBB(Physics3D* boxBody, float dt, CollisionResult& outResult); - //CollisionDetectionMode : Continuous + + // Gilbert-Johnson-Keerthi distance algorithm and Expanding Polytope Algorithm for convex collision resolution + // universal support function that handles all shape types + glm::vec3 GetShapeSupportPoint(const GjkShape& shape, glm::vec3 searchDirection); + // updated gjk and epa signatures using GjkShape + SupportPoint GetSupport(const GjkShape& shapeA, const GjkShape& shapeB, glm::vec3 searchDirection); + bool CheckCollisionGJK(const GjkShape& shapeA, const GjkShape& shapeB, std::vector& outSimplex); + bool HandleSimplex(std::vector& currentSimplex, glm::vec3& currentDirection); + void CalculatePenetrationEPA(const GjkShape& shapeA, const GjkShape& shapeB, std::vector& currentSimplex, glm::vec3& outNormal, float& outDepth, glm::vec3& outContactPoint); + // Helper methods for Continuous Collision Detection to solve time of impact + bool SweptGJK(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult); std::vector collidePolyhedron; Sphere sphere; - //2d->3d + // Cache previous separating axes to exploit temporal coherence and accelerate the algorithm in subsequent frames + std::unordered_map separatingAxisCache; + + //======== Legacy: SAT ========// + //glm::vec3 RotatePoint(const glm::vec3& point, const glm::vec3& position, const glm::quat& rotation); + //bool IsSeparatingAxis(const glm::vec3 axis, const std::vector points1, const std::vector points2, float* axisDepth, float* min1, float* max1, float* min2, float* max2); + //bool IsSeparatingAxis(const glm::vec3 axis, const std::vector pointsPoly, const glm::vec3 pointSphere, const float radius, float* axisDepth, float* min1, float* max1, float* min2, float* max2); + + //glm::vec3 FindClosestPointOnSegment(const glm::vec3& sphereCenter, std::vector& vertices); + + //void ProjectPolygon(const std::vector& vertices, const glm::vec3& axis, float& min, float& max); + //bool StaticSATIntersection(Physics3D* body1, Physics3D* body2, + // const std::vector& rotatedPoly1, const std::vector& rotatedPoly2, + // const glm::mat4& rotationMatrix1, const glm::mat4& rotationMatrix2, + // glm::vec3& outNormal, float& outDepth); + //bool SweptSATOBB(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult); + //bool SweptSphereVsOBB(Physics3D* boxBody, float dt, CollisionResult& outResult); + //======== Legacy: SAT ========// }; \ No newline at end of file diff --git a/Engine/include/BasicComponents/StaticSprite.hpp b/Engine/include/BasicComponents/StaticSprite.hpp index 09d076ce..7dd61758 100644 --- a/Engine/include/BasicComponents/StaticSprite.hpp +++ b/Engine/include/BasicComponents/StaticSprite.hpp @@ -17,7 +17,7 @@ class StaticSprite : public ISprite uint32_t primitiveIndexOffset; }; - StaticSprite() : ISprite() { Init(); spriteType = SpriteType::STATIC; }; + StaticSprite() : ISprite() { spriteType = SpriteType::STATIC; }; ~StaticSprite() override; void Init() override; diff --git a/Engine/include/Object.hpp b/Engine/include/Object.hpp index 5e32d0f5..7d5e1c12 100644 --- a/Engine/include/Object.hpp +++ b/Engine/include/Object.hpp @@ -70,9 +70,11 @@ class Object return; } ComponentTypes* componentType = new ComponentTypes(); - dynamic_cast(componentType)->SetOwner(this); + IComponent* component = dynamic_cast(componentType); + component->SetOwner(this); + component->Init(); this->componentList.push_back(std::move(componentType)); - LogComponentAction(dynamic_cast(componentType)->GetType(), true); + LogComponentAction(component->GetType(), true); } template ComponentTypes* GetComponent() diff --git a/Engine/include/PhysicsManager.hpp b/Engine/include/PhysicsManager.hpp index c1cd1ffa..4fd07739 100644 --- a/Engine/include/PhysicsManager.hpp +++ b/Engine/include/PhysicsManager.hpp @@ -3,9 +3,14 @@ //File: PhysicsManager.hpp #pragma once +#ifndef NOMINMAX +#define NOMINMAX +#endif + #include #include #include +#include #include "BasicComponents/Physics2D.hpp" #include "BasicComponents/Physics3D.hpp" @@ -25,6 +30,111 @@ struct CollisionPair3D Physics3D* bodyB = nullptr; }; +struct AABB +{ + glm::vec3 minExtent = { 0.0f, 0.0f, 0.0f }; + glm::vec3 maxExtent = { 0.0f, 0.0f, 0.0f }; + + // Check if this Axis-Aligned Bounding Box overlaps with another Axis-Aligned Bounding Box by comparing their minimum and maximum extents along all three axes + bool Intersects(const AABB& other) const + { + if (maxExtent.x < other.minExtent.x || minExtent.x > other.maxExtent.x) return false; + if (maxExtent.y < other.minExtent.y || minExtent.y > other.maxExtent.y) return false; + if (maxExtent.z < other.minExtent.z || minExtent.z > other.maxExtent.z) return false; + return true; + } + + // Check if this Axis-Aligned Bounding Box fully contains another Axis-Aligned Bounding Box. This is used for the Fat Axis-Aligned Bounding Box check to avoid frequent tree updates. + bool Contains(const AABB& other) const + { + bool isMinInside = minExtent.x <= other.minExtent.x && minExtent.y <= other.minExtent.y && minExtent.z <= other.minExtent.z; + bool isMaxInside = maxExtent.x >= other.maxExtent.x && maxExtent.y >= other.maxExtent.y && maxExtent.z >= other.maxExtent.z; + return isMinInside && isMaxInside; + } + + // Merge two Axis-Aligned Bounding Boxes into this one by taking the minimum of their minimum extents and the maximum of their maximum extents + void Merge(const AABB& a, const AABB& b) + { + minExtent = glm::min(a.minExtent, b.minExtent); + maxExtent = glm::max(a.maxExtent, b.maxExtent); + } + + // Surface Area Heuristic cost calculation, which estimates the probability of a ray or bounding volume intersecting this node based on its surface area + float GetSurfaceArea() const + { + glm::vec3 extents = maxExtent - minExtent; + return 2.0f * (extents.x * extents.y + extents.y * extents.z + extents.z * extents.x); + } +}; + +struct BVHNode +{ + AABB boundingBox; + Physics3D* physicsBody = nullptr; + + // Tree topology + int parentIndex = -1; + int leftChildIndex = -1; + int rightChildIndex = -1; + + // Used for maintaining the free list in the object pool + int nextFreeNodeIndex = -1; + + bool IsLeaf() const + { + return rightChildIndex == -1; + } +}; + +struct AABB2D +{ + glm::vec2 minExtent = { 0.0f, 0.0f }; + glm::vec2 maxExtent = { 0.0f, 0.0f }; + + bool Intersects(const AABB2D& other) const + { + if (maxExtent.x < other.minExtent.x || minExtent.x > other.maxExtent.x) return false; + if (maxExtent.y < other.minExtent.y || minExtent.y > other.maxExtent.y) return false; + return true; + } + + bool Contains(const AABB2D& other) const + { + bool isMinInside = minExtent.x <= other.minExtent.x && minExtent.y <= other.minExtent.y; + bool isMaxInside = maxExtent.x >= other.maxExtent.x && maxExtent.y >= other.maxExtent.y; + return isMinInside && isMaxInside; + } + + void Merge(const AABB2D& a, const AABB2D& b) + { + minExtent = glm::min(a.minExtent, b.minExtent); + maxExtent = glm::max(a.maxExtent, b.maxExtent); + } + + // Surface Area Heuristic cost calculation for two-dimensional space uses perimeter instead of surface area to estimate the intersection probability + float GetPerimeter() const + { + glm::vec2 extents = maxExtent - minExtent; + return 2.0f * (extents.x + extents.y); + } +}; + +struct BVHNode2D +{ + AABB2D boundingBox; + Physics2D* physicsBody = nullptr; + + int parentIndex = -1; + int leftChildIndex = -1; + int rightChildIndex = -1; + int nextFreeNodeIndex = -1; + + bool IsLeaf() const + { + return rightChildIndex == -1; + } +}; + class PhysicsManager { public: @@ -42,9 +152,13 @@ class PhysicsManager void SetCollisionMode(ObjectType typeA, ObjectType typeB, CollisionMode mode); CollisionMode GetCollisionMode(ObjectType typeA, ObjectType typeB); + + // Query the Bounding Volume Hierarchy tree to find objects in the path of a moving body. This is used for Continuous Collision Detection. + std::vector GetPotentialColliders(Physics3D* queryBody, float dt); + private: void UpdatePhysics2D(float dt); - std::vector BroadPhase2D(); + std::vector BroadPhase2D(float dt); void ApplyMovement2D(float dt); void NarrowPhase2D(std::vector& pairs, float dt); @@ -55,6 +169,22 @@ class PhysicsManager void SolveContinuous(Physics3D* body, float dt); + // Dynamic BVH Core Functions + int AllocateNode(); + void FreeNode(int nodeIndex); + AABB ComputeAABB(Physics3D* body); + void InsertLeaf(int leafNodeIndex); + void RemoveLeaf(int leafNodeIndex); + void QueryTree(int nodeIndex, Physics3D* queryBody, const AABB& queryAABB, std::vector& outPairs); + + // Dynamic BVH Core Functions for 2D + int AllocateNode2D(); + void FreeNode2D(int nodeIndex); + AABB2D ComputeAABB2D(Physics2D* body); + void InsertLeaf2D(int leafNodeIndex); + void RemoveLeaf2D(int leafNodeIndex); + void QueryTree2D(int nodeIndex, Physics2D* queryBody, const AABB2D& queryAABB, std::vector& outPairs); + std::vector bodies2D; std::vector bodies3D; @@ -62,4 +192,21 @@ class PhysicsManager static constexpr int MAX_CCD_ITERATIONS = 4; static constexpr float SKIN_WIDTH = 0.005f; + + // Tree State + int rootNodeIndex = -1; + int freeListIndex = 0; + std::vector bvhNodes; + + // Map to quickly find a body's leaf node index + std::unordered_map bodyNodeMap; + + // Tree State for 2D + int rootNodeIndex2D = -1; + int freeListIndex2D = 0; + std::vector bvhNodes2D; + std::unordered_map bodyNodeMap2D; + + const glm::vec2 FAT_BOUNDS_MARGIN_2D = { 0.2f, 0.2f }; + const glm::vec3 FAT_BOUNDS_MARGIN = { 0.2f, 0.2f, 0.2f }; }; diff --git a/Engine/source/BasicComponents/Physics2D.cpp b/Engine/source/BasicComponents/Physics2D.cpp index 0bcbc63f..3fb9f449 100644 --- a/Engine/source/BasicComponents/Physics2D.cpp +++ b/Engine/source/BasicComponents/Physics2D.cpp @@ -162,69 +162,101 @@ bool Physics2D::CheckCollision(Object* obj) bool Physics2D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) { - // Polygon vs Polygon collision using Separating Axis Theorem (SAT) - if (obj->GetComponent()->GetCollidePolygon().empty() == false && - obj2->GetComponent()->GetCollidePolygon().empty() == false) - { - std::vector rotatedPoints1; - std::vector rotatedPoints2; - float depth = INFINITY; - glm::vec2 normal = { 0.f, 0.f }; + auto* physics1 = obj->GetComponent(); + auto* physics2 = obj2->GetComponent(); - float min1 = INFINITY; - float min2 = INFINITY; - float max1 = -INFINITY; - float max2 = -INFINITY; + if (physics1->GetCollidePolygon().empty() == false && physics2->GetCollidePolygon().empty() == false) + { + const auto& poly1 = physics1->GetCollidePolygon(); + const auto& poly2 = physics2->GetCollidePolygon(); - // Transform local vertices to world space based on object transform - for (const glm::vec2 point : collidePolygon) + if (poly1.empty() || poly2.empty()) { - rotatedPoints1.push_back(RotatePoint(glm::vec2(obj->GetPosition()), point, DegreesToRadians(obj->GetRotate()))); + return false; } - for (const glm::vec2 point : obj2->GetComponent()->GetCollidePolygon()) + // Use thread_local to avoid memory allocation overhead on every check + static thread_local std::vector rotatedPoints1; + static thread_local std::vector rotatedPoints2; + static thread_local std::vector collisionAxes; + + rotatedPoints1.clear(); + rotatedPoints2.clear(); + collisionAxes.clear(); + + // 2D Rotation and Translation matrices + float angle1 = glm::radians(obj->GetRotate()); + float angle2 = glm::radians(obj2->GetRotate()); + + glm::mat2 rotMat1(cos(angle1), sin(angle1), -sin(angle1), cos(angle1)); + glm::mat2 rotMat2(cos(angle2), sin(angle2), -sin(angle2), cos(angle2)); + + glm::vec2 pos1 = obj->GetPosition(); + glm::vec2 pos2 = obj2->GetPosition(); + + for (const auto& point : poly1) { - rotatedPoints2.push_back(RotatePoint(glm::vec2(obj2->GetPosition()), point, DegreesToRadians(obj2->GetRotate()))); + rotatedPoints1.emplace_back((rotMat1 * point) + pos1); + } + for (const auto& point : poly2) + { + rotatedPoints2.emplace_back((rotMat2 * point) + pos2); } - // Test separating axes for the first polygon's edges - for (size_t i = 0; i < rotatedPoints1.size(); ++i) + // Temporal Coherence: Check previously saved separating axis first + auto cacheIterator = physics1->separatingAxisCache.find(physics2); + if (cacheIterator != physics1->separatingAxisCache.end()) { - float axisDepth = 0.f; - glm::vec2 edge = rotatedPoints1[(i + 1) % rotatedPoints1.size()] - rotatedPoints1[i]; - glm::vec2 axis = glm::vec2(-edge.y, edge.x); // Perpendicular vector (Normal) - axis = normalize(axis); + glm::vec2 cachedAxis = cacheIterator->second; + float dummyDepth, dummyMin1, dummyMax1, dummyMin2, dummyMax2; - if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) + if (IsSeparatingAxis(cachedAxis, rotatedPoints1, rotatedPoints2, &dummyDepth, &dummyMin1, &dummyMax1, &dummyMin2, &dummyMax2)) { - return false; // Gap found, no collision - } - if (axisDepth < depth) - { - depth = axisDepth; - normal = ((max1 - min2) < (max2 - min1)) ? axis : -axis; + return false; } } - // Test separating axes for the second polygon's edges + // Collect face normals as potential separating axes + for (size_t i = 0; i < rotatedPoints1.size(); ++i) + { + glm::vec2 edge = rotatedPoints1[(i + 1) % rotatedPoints1.size()] - rotatedPoints1[i]; + glm::vec2 axisNormal = glm::normalize(glm::vec2(-edge.y, edge.x)); + collisionAxes.push_back(axisNormal); + } for (size_t i = 0; i < rotatedPoints2.size(); ++i) { - float axisDepth = 0.f; glm::vec2 edge = rotatedPoints2[(i + 1) % rotatedPoints2.size()] - rotatedPoints2[i]; - glm::vec2 axis = glm::vec2(-edge.y, edge.x); - axis = normalize(axis); + glm::vec2 axisNormal = glm::normalize(glm::vec2(-edge.y, edge.x)); + collisionAxes.push_back(axisNormal); + } - if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) + float depth = FLT_MAX; + glm::vec2 normal(0.f); + + // Test for separation along each axis + for (const auto& axis : collisionAxes) + { + float min1, max1, min2, max2; + float currentDepth; + + if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, ¤tDepth, &min1, &max1, &min2, &max2)) { + physics1->separatingAxisCache[physics2] = axis; + physics2->separatingAxisCache[physics1] = -axis; return false; } - if (axisDepth < depth) + + if (currentDepth < depth) { - depth = axisDepth; + depth = currentDepth; normal = ((max1 - min2) < (max2 - min1)) ? axis : -axis; } } + // Collision confirmed. Clear cache to recalculate accurately next time. + physics1->separatingAxisCache.erase(physics2); + physics2->separatingAxisCache.erase(physics1); + // Penetration resolution and collision response if (mode == CollisionMode::COLLIDE && obj->GetComponent()->GetIsGhostCollision() == false && @@ -389,9 +421,11 @@ bool Physics2D::CollisionPC(Object* poly, Object* cir, CollisionMode mode) // Polygon vs Circle collision check glm::vec2 circleCenter = cir->GetPosition(); float circleRadius = cir->GetComponent()->GetCircleCollideRadius(); - std::vector rotatedPoints; + // Use thread_local static vectors to minimize memory allocations during high-frequency collision checks. + static thread_local std::vector rotatedPoints; + rotatedPoints.clear(); - glm::vec2 normal = { 0.f,0.f }; + glm::vec2 normal = { 0.f, 0.f }; float depth = INFINITY; float min1 = INFINITY; @@ -487,8 +521,12 @@ bool Physics2D::CollisionPPWithoutPhysics(Object* obj, Object* obj2) if (obj->GetComponent()->GetCollidePolygon().empty() == false && obj2->GetComponent()->GetCollidePolygon().empty() == false) { - std::vector rotatedPoints1; - std::vector rotatedPoints2; + // Utilize thread_local static vectors to prevent costly heap allocations in parallel loops. + static thread_local std::vector rotatedPoints1; + static thread_local std::vector rotatedPoints2; + rotatedPoints1.clear(); + rotatedPoints2.clear(); + float depth = INFINITY; glm::vec2 normal = { 0.f, 0.f }; @@ -710,69 +748,62 @@ glm::vec2 Physics2D::RotatePoint(const glm::vec2 point, const glm::vec2 size, fl return glm::vec2(x, y); } -bool Physics2D::IsSeparatingAxis(const glm::vec2 axis, const std::vector points1, const std::vector points2, float* axisDepth, float* min1, float* max1, float* min2, float* max2) +bool Physics2D::IsSeparatingAxis(const glm::vec2& axis, const std::vector& points1, const std::vector& points2, float* axisDepth, float* min1, float* max1, float* min2, float* max2) { - // Project all points of both polygons onto the given axis - float minPoint1 = INFINITY; - float minPoint2 = INFINITY; - float maxPoint1 = -INFINITY; - float maxPoint2 = -INFINITY; + // Project both polygons onto an axis to check for overlap + float currentMin1 = FLT_MAX; + float currentMax1 = -FLT_MAX; + float currentMin2 = FLT_MAX; + float currentMax2 = -FLT_MAX; - for (const glm::vec2 point : points1) + for (const glm::vec2& vertex : points1) { - float projection = glm::dot(point, axis); - minPoint1 = std::min(minPoint1, projection); - maxPoint1 = std::max(maxPoint1, projection); + float projection = glm::dot(axis, vertex); + currentMin1 = std::min(currentMin1, projection); + currentMax1 = std::max(currentMax1, projection); } - for (const glm::vec2 point : points2) + for (const glm::vec2& vertex : points2) { - float projection = glm::dot(point, axis); - minPoint2 = std::min(minPoint2, projection); - maxPoint2 = std::max(maxPoint2, projection); + float projection = glm::dot(axis, vertex); + currentMin2 = std::min(currentMin2, projection); + currentMax2 = std::max(currentMax2, projection); } - // Check if the two projected ranges overlap - *axisDepth = std::min(maxPoint2 - minPoint1, maxPoint1 - minPoint2); - *min1 = minPoint1; - *max1 = maxPoint1; - *min2 = minPoint2; - *max2 = maxPoint2; + // Determine the amount of overlap along the axis + *axisDepth = std::min(currentMax2 - currentMin1, currentMax1 - currentMin2); + *min1 = currentMin1; + *max1 = currentMax1; + *min2 = currentMin2; + *max2 = currentMax2; - // Return true if there is a gap (separation) - return !(maxPoint1 >= minPoint2 && maxPoint2 >= minPoint1); + return !(currentMax1 >= currentMin2 && currentMax2 >= currentMin1); } -bool Physics2D::IsSeparatingAxis(const glm::vec2 axis, const std::vector pointsPoly, const glm::vec2 pointCir, const float radius, float* axisDepth, float* min1, float* max1, float* min2, float* max2) +bool Physics2D::IsSeparatingAxis(const glm::vec2& axis, const std::vector& pointsPoly, const glm::vec2& pointCircle, const float radius, float* axisDepth, float* min1, float* max1, float* min2, float* max2) { - // SAT projection for polygon vs circle - float minPoint1 = INFINITY; - float maxPoint1 = -INFINITY; + // Project polygon and circle onto an axis + float currentMin1 = FLT_MAX; + float currentMax1 = -FLT_MAX; - for (const glm::vec2 point : pointsPoly) + for (const glm::vec2& vertex : pointsPoly) { - float projection = glm::dot(point, axis); - minPoint1 = std::min(minPoint1, projection); - maxPoint1 = std::max(maxPoint1, projection); + float projection = glm::dot(vertex, axis); + currentMin1 = std::min(currentMin1, projection); + currentMax1 = std::max(currentMax1, projection); } - // For circle, the projection range is center +/- radius - glm::vec2 direction = normalize(axis); - glm::vec2 directionAndRadius = direction * radius; - - float p1 = glm::dot(pointCir + directionAndRadius, axis); - float p2 = glm::dot(pointCir - directionAndRadius, axis); - - float minPoint2 = std::min(p1, p2); - float maxPoint2 = std::max(p1, p2); + float circleProjection = glm::dot(pointCircle, axis); + float currentMin2 = circleProjection - radius; + float currentMax2 = circleProjection + radius; - *axisDepth = std::min(maxPoint2 - minPoint1, maxPoint1 - minPoint2); - *min1 = minPoint1; - *max1 = maxPoint1; - *min2 = minPoint2; - *max2 = maxPoint2; + *axisDepth = std::min(currentMax2 - currentMin1, currentMax1 - currentMin2); + *min1 = currentMin1; + *max1 = currentMax1; + *min2 = currentMin2; + *max2 = currentMax2; - return !(maxPoint1 >= minPoint2 && maxPoint2 >= minPoint1); + return !(currentMax1 >= currentMin2 && currentMax2 >= currentMin1); } void Physics2D::CalculateLinearVelocity(Physics2D& body, Physics2D& body2, glm::vec2 normal, float* /*axisDepth*/, glm::vec2 contactPoint) diff --git a/Engine/source/BasicComponents/Physics3D.cpp b/Engine/source/BasicComponents/Physics3D.cpp index 0cfb02b5..a5b8e75c 100644 --- a/Engine/source/BasicComponents/Physics3D.cpp +++ b/Engine/source/BasicComponents/Physics3D.cpp @@ -119,7 +119,7 @@ void Physics3D::UpdatePhysics(float dt) Gravity(dt); } - // Apply Newton's second law: F = ma -> a = F/m + // Apply Newton's second law of motion where Force equals mass times acceleration to calculate the current acceleration vector acceleration = force / mass; velocity += acceleration * dt; @@ -138,7 +138,7 @@ void Physics3D::UpdatePhysics(float dt) } } - // Reset accumulated force after integration + // Reset the accumulated force back to zero after integration so forces do not carry over to the next frame force = glm::vec3(0.0f); // Clamp linear velocity to the defined maximum limits @@ -157,11 +157,11 @@ void Physics3D::UpdatePhysics(float dt) if (enableRotationalPhysics && inverseInertia > 0.0f) { - // Calculate angular acceleration from torque + // Calculate angular acceleration from torque using the inverse moment of inertia tensor equivalent glm::vec3 angularAcceleration = torque * inverseInertia; angularVelocity += angularAcceleration * dt; - // Apply rotational damping + // Apply rotational damping to simulate air resistance and reduce endless spinning angularVelocity *= (1.f - angularDamping * dt); // Clamp extremely low angular velocities to zero @@ -172,7 +172,7 @@ void Physics3D::UpdatePhysics(float dt) torque = glm::vec3(0.0f); - // Perform quaternion integration to update orientation + // Perform quaternion integration to update orientation based on the angular velocity vector scaled by half the delta time glm::quat spin = 0.5f * glm::quat(0.f, angularVelocity.x, angularVelocity.y, angularVelocity.z) * orientation; orientation.w += spin.w * dt; orientation.x += spin.x * dt; @@ -180,12 +180,12 @@ void Physics3D::UpdatePhysics(float dt) orientation.z += spin.z * dt; orientation = glm::normalize(orientation); - // Map the orientation back to Euler angles for the visual object + // Map the quaternion orientation back to Euler angles to synchronize with the visual object transform glm::vec3 eulerArgs = glm::degrees(glm::eulerAngles(orientation)); GetOwner()->SetRotate(-eulerArgs); } - // Thresholds for deciding if the object should go to sleep + // Set thresholds for kinetic energy below which the object is considered stationary and should go to sleep to save processing power const float linearSleepThreshold = 0.01f; const float angularSleepThreshold = 0.0001f; @@ -230,7 +230,7 @@ void Physics3D::UpdatePhysics(float dt) float moveTime = collision.timeOfImpact > skinWidth ? collision.timeOfImpact - skinWidth : 0.0f; GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * dt * moveTime); - // Calculate point of contact and resolve velocity impulse + // Calculate the exact point of contact by retracting slightly along the normal vector and resolve the linear velocity impulse glm::vec3 contactPoint = GetOwner()->GetPosition() - collision.collisionNormal * 0.1f; CalculateLinearVelocity(*this, *collision.otherObject->GetComponent(), collision.collisionNormal, nullptr, contactPoint); @@ -248,7 +248,7 @@ void Physics3D::UpdatePhysics(float dt) } else { - // Simple discrete movement + // Simple discrete Euler integration movement without continuous collision checks GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * dt); } } @@ -325,16 +325,1080 @@ bool Physics3D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) if (physics1->GetCollidePolyhedron().empty() == false && physics2->GetCollidePolyhedron().empty() == false) { - const auto& poly1 = physics1->collidePolyhedron; - const auto& poly2 = physics2->collidePolyhedron; + const auto& poly1 = physics1->GetCollidePolyhedron(); + const auto& poly2 = physics2->GetCollidePolyhedron(); if (poly1.empty() || poly2.empty()) { return false; } + // thread_local prevents data corruption when multiple worker threads access these buffers simultaneously + static thread_local std::vector rotatedPoly1; + static thread_local std::vector rotatedPoly2; + + // reset buffers without deallocating memory for performance + rotatedPoly1.clear(); + rotatedPoly2.clear(); + + // fetch orientations based on whether rotational physics is enabled + glm::quat orient1 = physics1->GetEnableRotationalPhysics() ? physics1->GetOrientation() : glm::quat(-glm::radians(obj->GetRotate3D())); + glm::quat orient2 = physics2->GetEnableRotationalPhysics() ? physics2->GetOrientation() : glm::quat(-glm::radians(obj2->GetRotate3D())); + + glm::mat4 rotationMatrix1 = glm::mat4_cast(orient1); + glm::mat4 rotationMatrix2 = glm::mat4_cast(orient2); + + // calculate world space transformation matrices + glm::mat4 transform1 = glm::translate(glm::mat4(1.0f), obj->GetPosition()) * rotationMatrix1; + glm::mat4 transform2 = glm::translate(glm::mat4(1.0f), obj2->GetPosition()) * rotationMatrix2; + + // transform all local vertices to world space coordinates + for (const auto& point : poly1) + { + rotatedPoly1.emplace_back(glm::vec3(transform1 * glm::vec4(point, 1.0f))); + } + + for (const auto& point : poly2) + { + rotatedPoly2.emplace_back(glm::vec3(transform2 * glm::vec4(point, 1.0f))); + } + + GjkShape shapeA; + shapeA.type = ColliderType3D::BOX; + shapeA.vertices = &rotatedPoly1; + + GjkShape shapeB; + shapeB.type = ColliderType3D::BOX; + shapeB.vertices = &rotatedPoly2; + // Execute the Gilbert-Johnson-Keerthi distance algorithm to detect if the two convex hulls intersect by building a simplex inside their Minkowski Difference + std::vector finalSimplex; + bool hasCollision = CheckCollisionGJK(shapeA, shapeB, finalSimplex); + + if (hasCollision == false) + { + return false; + } + + // Expand the polytope using the Expanding Polytope Algorithm to find the exact collision normal, penetration depth, and true contact point on the surface + glm::vec3 contactNormal(0.0f); + float contactDepth = 0.0f; + glm::vec3 actualContactPoint(0.0f); + CalculatePenetrationEPA(shapeA, shapeB, finalSimplex, contactNormal, contactDepth, actualContactPoint); + + if (mode == CollisionMode::COLLIDE && !physics1->GetIsGhostCollision() && !physics2->GetIsGhostCollision()) + { + const float skinSlop = 0.005f; + const float resolutionRate = 0.2f; + float actualPenetration = std::max(contactDepth - skinSlop, 0.0f); + glm::vec3 moveDelta = contactNormal * (actualPenetration * resolutionRate); + + // calculate relative velocity to ignore micro resting collisions + glm::vec3 relativeVelocity = physics1->GetVelocity() - physics2->GetVelocity(); + + // only wake objects if the impact has noticeable speed or deep penetration + bool isSignificantImpact = (actualPenetration > 0.001f) || (glm::length2(relativeVelocity) > 0.01f); + + if (physics1->GetBodyType() == BodyType3D::RIGID && physics2->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveDelta) > 0.0f) + { + obj->SetPosition(obj->GetPosition() - moveDelta * 0.5f); + obj2->SetPosition(obj2->GetPosition() + moveDelta * 0.5f); + } + + if (isSignificantImpact) + { + physics1->Awake(); + physics2->Awake(); + } + } + else if (physics1->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveDelta) > 0.0f) + { + obj->SetPosition(obj->GetPosition() - moveDelta); + } + + if (isSignificantImpact) + { + physics1->Awake(); + } + } + else if (physics2->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveDelta) > 0.0f) + { + obj2->SetPosition(obj2->GetPosition() + moveDelta); + } + + if (isSignificantImpact) + { + physics2->Awake(); + } + } + CalculateLinearVelocity(*physics1, *physics2, contactNormal, &contactDepth, actualContactPoint); + } + return true; + } + return false; +} + +bool Physics3D::CollisionSS(Object* obj, Object* obj2, CollisionMode mode) +{ + // Simple sphere-to-sphere distance check + glm::vec3 center1 = obj->GetPosition(); + glm::vec3 center2 = obj2->GetPosition(); + float radius1 = obj->GetComponent()->sphere.radius / 2.f; + float radius2 = obj2->GetComponent()->sphere.radius / 2.f; + + float distanceSquared = glm::length2(center2 - center1); + float radiusSum = radius1 + radius2; + + if (distanceSquared <= radiusSum * radiusSum) + { + float distance = std::sqrt(distanceSquared); + glm::vec3 normal = (center2 - center1) / distance; + float depth = radiusSum - distance; + + if (mode == CollisionMode::COLLIDE && obj->GetComponent()->GetIsGhostCollision() == false && obj2->GetComponent()->GetIsGhostCollision() == false) + { + const float slop = 0.005f; + float penetrationAmt = std::max(depth - slop, 0.0f); + + // Separate spheres based on their mass/rigid type + if (obj->GetComponent()->GetBodyType() == BodyType3D::RIGID && obj2->GetComponent()->GetBodyType() == BodyType3D::RIGID) + { + if (penetrationAmt > 0.0f) + { + obj->SetPosition(obj->GetPosition() - normal * penetrationAmt * 0.5f); + obj2->SetPosition(obj2->GetPosition() + normal * penetrationAmt * 0.5f); + } + obj->GetComponent()->Awake(); + obj2->GetComponent()->Awake(); + } + else if (obj->GetComponent()->GetBodyType() == BodyType3D::RIGID && obj2->GetComponent()->GetBodyType() == BodyType3D::BLOCK) + { + if (penetrationAmt > 0.0f) + { + obj->SetPosition(obj->GetPosition() - normal * penetrationAmt); + } + obj->GetComponent()->Awake(); + } + else if (obj->GetComponent()->GetBodyType() == BodyType3D::BLOCK && obj2->GetComponent()->GetBodyType() == BodyType3D::RIGID) + { + if (penetrationAmt > 0.0f) + { + obj2->SetPosition(obj2->GetPosition() + normal * penetrationAmt); + } + obj2->GetComponent()->Awake(); + } + // Resolve collision impulse + glm::vec3 cp = center1 - normal * radius1; + CalculateLinearVelocity(*obj->GetComponent(), *obj2->GetComponent(), normal, &depth, cp); + } + return true; + } + return false; +} + +bool Physics3D::CollisionPS(Object* poly, Object* sph, CollisionMode mode) +{ + auto* physicsPoly = poly->GetComponent(); + auto* physicsSph = sph->GetComponent(); + + if (physicsPoly->GetCollidePolyhedron().empty()) + { + return false; + } + + const auto& polyVertices = physicsPoly->GetCollidePolyhedron(); + + static thread_local std::vector rotatedPoly; + rotatedPoly.clear(); + + glm::quat polyOrient = physicsPoly->GetEnableRotationalPhysics() ? physicsPoly->GetOrientation() : glm::quat(-glm::radians(poly->GetRotate3D())); + glm::mat4 polyMatrix = glm::translate(glm::mat4(1.0f), poly->GetPosition()) * glm::mat4_cast(polyOrient); + + for (const auto& point : polyVertices) + { + rotatedPoly.emplace_back(glm::vec3(polyMatrix * glm::vec4(point, 1.0f))); + } + + // setup shape proxy for polygon + GjkShape shapePoly; + shapePoly.type = ColliderType3D::BOX; + shapePoly.vertices = &rotatedPoly; + + // setup shape proxy for sphere + GjkShape shapeSphere; + shapeSphere.type = ColliderType3D::SPHERE; + shapeSphere.center = sph->GetPosition(); + shapeSphere.radius = physicsSph->GetSphereRadius() / 2.0f; + + std::vector finalSimplex; + bool hasCollision = CheckCollisionGJK(shapePoly, shapeSphere, finalSimplex); + + if (hasCollision == false) + { + return false; + } + + glm::vec3 contactNormal(0.0f); + float contactDepth = 0.0f; + glm::vec3 actualContactPoint(0.0f); + + CalculatePenetrationEPA(shapePoly, shapeSphere, finalSimplex, contactNormal, contactDepth, actualContactPoint); + + if (mode == CollisionMode::COLLIDE && !physicsPoly->GetIsGhostCollision() && !physicsSph->GetIsGhostCollision()) + { + const float skinSlop = 0.005f; + const float resolutionRate = 0.2f; + float actualPenetration = std::max(contactDepth - skinSlop, 0.0f); + glm::vec3 moveDelta = contactNormal * (actualPenetration * resolutionRate); + + // calculate relative velocity to ignore micro resting collisions + glm::vec3 relativeVelocity = physicsPoly->GetVelocity() - physicsSph->GetVelocity(); + bool isSignificantImpact = (actualPenetration > 0.001f) || (glm::length2(relativeVelocity) > 0.01f); + + if (physicsPoly->GetBodyType() == BodyType3D::RIGID && physicsSph->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveDelta) > 0.0f) + { + poly->SetPosition(poly->GetPosition() - moveDelta * 0.5f); + sph->SetPosition(sph->GetPosition() + moveDelta * 0.5f); + } + + if (isSignificantImpact) + { + physicsPoly->Awake(); + physicsSph->Awake(); + } + } + else if (physicsPoly->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveDelta) > 0.0f) + { + poly->SetPosition(poly->GetPosition() - moveDelta); + } + + if (isSignificantImpact) + { + physicsPoly->Awake(); + } + } + else if (physicsSph->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveDelta) > 0.0f) + { + sph->SetPosition(sph->GetPosition() + moveDelta); + } + + if (isSignificantImpact) + { + physicsSph->Awake(); + } + } + CalculateLinearVelocity(*physicsPoly, *physicsSph, contactNormal, &contactDepth, actualContactPoint); + } + return true; +} + +void Physics3D::AddCollidePolyhedron(glm::vec3 position) +{ + colliderType = ColliderType3D::BOX; + collidePolyhedron.push_back(position); +} + +void Physics3D::AddCollidePolyhedronAABB(glm::vec3 min, glm::vec3 max) +{ + // Define an axis-aligned bounding box from min/max corners + colliderType = ColliderType3D::BOX; + collidePolyhedron.clear(); + collidePolyhedron.push_back(glm::vec3(min.x, min.y, min.z)); + collidePolyhedron.push_back(glm::vec3(min.x, max.y, min.z)); + collidePolyhedron.push_back(glm::vec3(max.x, max.y, min.z)); + collidePolyhedron.push_back(glm::vec3(max.x, min.y, min.z)); + collidePolyhedron.push_back(glm::vec3(min.x, min.y, max.z)); + collidePolyhedron.push_back(glm::vec3(min.x, max.y, max.z)); + collidePolyhedron.push_back(glm::vec3(max.x, max.y, max.z)); + collidePolyhedron.push_back(glm::vec3(max.x, min.y, max.z)); +} + +void Physics3D::AddCollidePolyhedronAABB(glm::vec3 size) +{ + AddCollidePolyhedronAABB(-size / 2.f, size / 2.f); +} + +void Physics3D::AddCollideSphere(float r) +{ + colliderType = ColliderType3D::SPHERE; + collidePolyhedron.clear(); + sphere.radius = r; +} + +glm::vec3 Physics3D::ComputePolygonCenter(const std::vector& pts) +{ + // Calculate the arithmetic mean of a set of points + glm::vec3 center(0.0f); + if (pts.empty()) + { + return center; + } + for (const auto& p : pts) + { + center += p; + } + return center / static_cast(pts.size()); +} + +void Physics3D::CalculateLinearVelocity(Physics3D& body, Physics3D& body2, glm::vec3 normal, float* /*axisDepth*/, glm::vec3 contactPoint, float impulseScale) +{ + // Calculate the lever arm vectors from the center of mass of each body to the point of contact. + // These vectors are used to compute the torque generated by the collision impulse. + glm::vec3 ra = contactPoint - body.GetOwner()->GetPosition(); + glm::vec3 rb = contactPoint - body2.GetOwner()->GetPosition(); + + // Calculate the total velocity at the contact point for both bodies. + // This includes both the linear velocity and the linear velocity induced by the angular rotation at the contact point. + glm::vec3 vaContact = body.GetVelocity() + glm::cross(body.GetAngularVelocity(), ra); + glm::vec3 vbContact = body2.GetVelocity() + glm::cross(body2.GetAngularVelocity(), rb); + + // Determine the relative velocity between the two bodies along the collision normal. + // This value indicates how fast the bodies are moving towards or away from each other. + glm::vec3 relativeVelocity = vbContact - vaContact; + float velAlongNormal = glm::dot(relativeVelocity, normal); + + // Do not resolve the collision if the relative velocity is positive, which means the objects are already separating. + if (velAlongNormal > 0.0f) + { + return; + } + + float res = std::min(body.GetRestitution(), body2.GetRestitution()); + + // Apply a velocity threshold to prevent infinite micro bouncing caused by constant gravitational acceleration. + if (std::abs(velAlongNormal) < 0.2f) + { + res = 0.0f; + } + + // Compute the rotational effect on the impulse denominator. + // This represents how much the bodies will rotate in response to an impulse applied at the contact point, based on their inertia tensors. + glm::vec3 raCrossN = glm::cross(ra, normal); + glm::vec3 rbCrossN = glm::cross(rb, normal); + + float invMassSum = (body.GetBodyType() == BodyType3D::RIGID ? (1.f / body.mass) : 0.0f) + (body2.GetBodyType() == BodyType3D::RIGID ? (1.f / body2.mass) : 0.0f); + + float denominator = invMassSum; + + if (body.GetBodyType() == BodyType3D::RIGID && body.GetEnableRotationalPhysics()) + { + glm::vec3 angEffectA = glm::cross(raCrossN * body.GetInverseInertia(), ra); + denominator += glm::dot(angEffectA, normal); + } + + if (body2.GetBodyType() == BodyType3D::RIGID && body2.GetEnableRotationalPhysics()) + { + glm::vec3 angEffectB = glm::cross(rbCrossN * body2.GetInverseInertia(), rb); + denominator += glm::dot(angEffectB, normal); + } + + if (denominator <= 0.0f) + { + return; + } + + // Calculate the scalar magnitude of the impulse j. + // The formula incorporates the coefficient of restitution to simulate bounciness and divides by the combined mass and inertia denominator. + float j = -(1.f + res) * velAlongNormal / denominator; + j *= impulseScale; + glm::vec3 impulse = normal * j; + + // Apply the computed impulse vector to the linear and angular velocities of both rigid bodies. + if (body.GetBodyType() == BodyType3D::RIGID) + { + body.SetVelocity(body.GetVelocity() - impulse * (1.f / body.mass)); + + if (body.GetEnableRotationalPhysics()) + { + body.SetAngularVelocity(body.GetAngularVelocity() - glm::cross(ra, impulse) * body.GetInverseInertia()); + } + } + + if (body2.GetBodyType() == BodyType3D::RIGID) + { + body2.SetVelocity(body2.GetVelocity() + impulse * (1.f / body2.mass)); + + if (body2.GetEnableRotationalPhysics()) + { + body2.SetAngularVelocity(body2.GetAngularVelocity() + glm::cross(rb, impulse) * body2.GetInverseInertia()); + } + } +} + +bool Physics3D::SweptSpheres(Physics3D* b1, Physics3D* b2, float dt, CollisionResult& res) +{ + // Continuous collision detection between two moving spheres by modeling their movement as a quadratic equation over time. + Object* o1 = b1->GetOwner(), * o2 = b2->GetOwner(); + glm::vec3 p1 = o1->GetPosition(), p2 = o2->GetPosition(); + float r1 = b1->sphere.radius / 2.0f, r2 = b2->sphere.radius / 2.0f, rSum = r1 + r2; + glm::vec3 vRel = (b2->GetVelocity() - b1->GetVelocity()) * dt; + glm::vec3 diff = p2 - p1; + + // Solve the quadratic equation for the time of intersection. + // The equation models the distance between the two spheres over time: |(Position1 + Time * Velocity1) - (Position2 + Time * Velocity2)|^2 = (Radius1 + Radius2)^2. + float a = glm::dot(vRel, vRel); + float b = 2.0f * glm::dot(diff, vRel); + float c = glm::dot(diff, diff) - rSum * rSum; + + if (c < 0.0f) + { + res.hasCollided = true; res.timeOfImpact = 0.0f; res.otherObject = o2; + res.collisionNormal = glm::normalize(p1 - p2); return true; + } + if (a < 0.00001f) + { + return false; + } + + float det = b * b - 4 * a * c; + if (det < 0) + { + return false; + } + + float t = (-b - sqrt(det)) / (2.0f * a); + if (t >= 0.0f && t <= 1.0f) + { + res.hasCollided = true; res.timeOfImpact = t; res.otherObject = o2; + res.collisionNormal = glm::normalize((p1 + b1->GetVelocity() * dt * t) - (p2 + b2->GetVelocity() * dt * t)); + return true; + } + return false; +} + + + +CollisionResult Physics3D::FindClosestCollision(float dt) +{ + // Search all scene objects to find the earliest continuous collision time of impact in this frame. + CollisionResult result; + result.timeOfImpact = 1.1f; + Object* ownerObject = GetOwner(); + + // Query the Bounding Volume Hierarchy tree to retrieve only the objects that are near the swept path of this body. + // This is a massive performance optimization over checking every object in the scene. + std::vector potentialColliders = Engine::GetPhysicsManager().GetPotentialColliders(this, dt); + + // Iterate through the filtered list of potential colliders obtained from the spatial partition. + for (Physics3D* otherBody : potentialColliders) + { + Object* otherObject = otherBody->GetOwner(); + + CollisionResult currentResult; + ColliderType3D myType = GetColliderType(); + ColliderType3D otherType = otherBody->GetColliderType(); + bool isHit = false; + + // Dispatch continuous collision detection routines based on the shape types of the two bodies to calculate exact time of impact. + if (myType == ColliderType3D::SPHERE && otherType == ColliderType3D::SPHERE) + { + isHit = SweptSpheres(this, otherBody, dt, currentResult); + } + else + { + // Box-Box, Sphere-Box, Box-Sphere + isHit = SweptGJK(this, otherBody, dt, currentResult); + + // invert normal if the calling body is sphere hitting a box to maintain standard reaction + if (isHit && myType == ColliderType3D::SPHERE && otherType == ColliderType3D::BOX) + { + currentResult.collisionNormal *= -1.0f; + } + } + + // Track only the earliest impact time + if (isHit) + { + if (currentResult.timeOfImpact < result.timeOfImpact) + { + result = currentResult; + } + } + } + + return result; +} + +glm::vec3 Physics3D::GetShapeSupportPoint(const GjkShape& shape, glm::vec3 searchDirection) +{ + // Calculate the support point of a shape in a given direction. + // The support point is the vertex of the convex shape that is furthest in the specified search direction. + if (shape.type == ColliderType3D::BOX) + { + float maxDistance = -FLT_MAX; + glm::vec3 farthestPoint(0.0f); + + // Iterate through all vertices of the polyhedron to find the one with the maximum projection along the search direction. + for (const auto& vertex : *(shape.vertices)) + { + // Apply dynamic offset for continuous collision detection sweeping. + glm::vec3 shiftedVertex = vertex + shape.offset; + float currentDistance = glm::dot(shiftedVertex, searchDirection); + + if (currentDistance > maxDistance) + { + maxDistance = currentDistance; + farthestPoint = shiftedVertex; + } + } + return farthestPoint; + } + else if (shape.type == ColliderType3D::SPHERE) + { + // For a sphere, the support point is simply the center plus the radius in the normalized search direction. + glm::vec3 normalizedDirection = glm::normalize(searchDirection); + // Apply dynamic offset to the sphere center for sweep calculations. + return (shape.center + shape.offset) + normalizedDirection * shape.radius; + } + + return glm::vec3(0.0f); +} + +SupportPoint Physics3D::GetSupport(const GjkShape& shapeA, const GjkShape& shapeB, glm::vec3 searchDirection) +{ + // The support function for the Minkowski Difference of two convex shapes A and B. + // The Minkowski Difference is defined as the set of all points (a - b) where a is in A and b is in B. + // A support point of the Minkowski Difference in direction D is calculated as Support(A, D) - Support(B, -D). + SupportPoint newSupport; + + newSupport.pointOnA = GetShapeSupportPoint(shapeA, searchDirection); + newSupport.pointOnB = GetShapeSupportPoint(shapeB, -searchDirection); + newSupport.minkowskiDifference = newSupport.pointOnA - newSupport.pointOnB; + + return newSupport; +} + +// update checkCollisionGJK signature and center calculations +bool Physics3D::CheckCollisionGJK(const GjkShape& shapeA, const GjkShape& shapeB, std::vector& outSimplex) +{ + // The Gilbert-Johnson-Keerthi (GJK) algorithm determines if two convex shapes intersect by checking if their Minkowski Difference contains the origin. + // We iteratively build a simplex (a point, line, triangle, or tetrahedron) within the Minkowski Difference to enclose the origin. + + // Apply sweep offsets to shape centers to find an initial search direction towards each other. + glm::vec3 centerA = (shapeA.type == ColliderType3D::BOX) ? ComputePolygonCenter(*(shapeA.vertices)) + shapeA.offset : shapeA.center + shapeA.offset; + glm::vec3 centerB = (shapeB.type == ColliderType3D::BOX) ? ComputePolygonCenter(*(shapeB.vertices)) + shapeB.offset : shapeB.center + shapeB.offset; + + glm::vec3 searchDirection = centerA - centerB; + + // Use a default direction if the centers are nearly identical to avoid mathematical errors. + if (glm::length2(searchDirection) < 0.0001f) + { + searchDirection = glm::vec3(1.0f, 0.0f, 0.0f); + } + + // Initialize the simplex with the first support point in the initial search direction. + SupportPoint firstPoint = GetSupport(shapeA, shapeB, searchDirection); + outSimplex.push_back(firstPoint); + + // The next search direction is always towards the origin from the last point added. + searchDirection = -firstPoint.minkowskiDifference; + + // Limit iterations to prevent infinite loops in cases of numerical instability. + for (int i = 0; i < 64; ++i) + { + SupportPoint nextPoint = GetSupport(shapeA, shapeB, searchDirection); + + // If the new support point does not pass the origin in the search direction, the Minkowski Difference cannot contain the origin. + if (glm::dot(nextPoint.minkowskiDifference, searchDirection) < 0.0f) + { + return false; + } + + outSimplex.push_back(nextPoint); + + // Update the simplex and search direction. If HandleSimplex returns true, the simplex now encloses the origin. + if (HandleSimplex(outSimplex, searchDirection)) + { + return true; + } + } + + return false; +} + +bool Physics3D::HandleSimplex(std::vector& currentSimplex, glm::vec3& currentDirection) +{ + // Logic for a line segment simplex (two points). + if (currentSimplex.size() == 2) + { + // Calculate the vector of the line and the vector from the latest point to the origin. + glm::vec3 lineAB = currentSimplex[0].minkowskiDifference - currentSimplex[1].minkowskiDifference; + glm::vec3 toOrigin = -currentSimplex[1].minkowskiDifference; + + // Use triple product to find a direction perpendicular to the line and pointing towards the origin. + currentDirection = glm::cross(glm::cross(lineAB, toOrigin), lineAB); + + // If the origin lies exactly on the line, choose an arbitrary perpendicular direction. + if (glm::length2(currentDirection) < 0.0001f) + { + currentDirection = glm::cross(lineAB, glm::vec3(1.0f, 0.0f, 0.0f)); + } + return false; + } + + // Logic for a triangle simplex (three points). + if (currentSimplex.size() == 3) + { + // Calculate edges from the latest point C added to the simplex. + glm::vec3 edgeAC = currentSimplex[0].minkowskiDifference - currentSimplex[2].minkowskiDifference; + glm::vec3 edgeBC = currentSimplex[1].minkowskiDifference - currentSimplex[2].minkowskiDifference; + glm::vec3 toOrigin = -currentSimplex[2].minkowskiDifference; + + // Calculate the normal of the triangle face. + glm::vec3 faceNormal = glm::cross(edgeBC, edgeAC); + + // Check if the origin is outside the edge AC. + if (glm::dot(glm::cross(faceNormal, edgeAC), toOrigin) > 0.0f) + { + // The origin is closer to edge AC, so remove point B and move towards the origin. + currentSimplex.erase(currentSimplex.begin() + 1); + currentDirection = glm::cross(glm::cross(edgeAC, toOrigin), edgeAC); + } + else + { + // Check if the origin is outside the edge BC. + if (glm::dot(glm::cross(edgeBC, faceNormal), toOrigin) > 0.0f) + { + // The origin is closer to edge BC, so remove point A and move towards the origin. + currentSimplex.erase(currentSimplex.begin()); + currentDirection = glm::cross(glm::cross(edgeBC, toOrigin), edgeBC); + } + else + { + // The origin is above or below the triangle face. + if (glm::dot(faceNormal, toOrigin) > 0.0f) + { + currentDirection = faceNormal; + } + else + { + // Winding order needs adjustment to keep the normal pointing towards the origin. + std::swap(currentSimplex[0], currentSimplex[1]); + currentDirection = -faceNormal; + } + } + } + return false; + } + + // Logic for a tetrahedron simplex (four points). + if (currentSimplex.size() == 4) + { + // Calculate edges from the latest point D added to the simplex. + glm::vec3 edgeDA = currentSimplex[0].minkowskiDifference - currentSimplex[3].minkowskiDifference; + glm::vec3 edgeDB = currentSimplex[1].minkowskiDifference - currentSimplex[3].minkowskiDifference; + glm::vec3 edgeDC = currentSimplex[2].minkowskiDifference - currentSimplex[3].minkowskiDifference; + glm::vec3 toOrigin = -currentSimplex[3].minkowskiDifference; + + // Calculate normals for the three faces connected to point D. + glm::vec3 normalABC = glm::cross(edgeDB, edgeDA); + glm::vec3 normalACD = glm::cross(edgeDA, edgeDC); + glm::vec3 normalADB = glm::cross(edgeDC, edgeDB); + + // If the origin is in front of face ABC, remove point C and continue. + if (glm::dot(normalABC, toOrigin) > 0.0f) + { + currentSimplex.erase(currentSimplex.begin() + 2); + currentDirection = normalABC; + return false; + } + + // If the origin is in front of face ACD, remove point B and continue. + if (glm::dot(normalACD, toOrigin) > 0.0f) + { + currentSimplex.erase(currentSimplex.begin() + 1); + currentDirection = normalACD; + return false; + } + + // If the origin is in front of face ADB, remove point A and continue. + if (glm::dot(normalADB, toOrigin) > 0.0f) + { + currentSimplex.erase(currentSimplex.begin()); + currentDirection = normalADB; + return false; + } + + // If the origin is not in front of any face, it is inside the tetrahedron. + return true; + } + + return false; +} + +void Physics3D::CalculatePenetrationEPA(const GjkShape& shapeA, const GjkShape& shapeB, std::vector& currentSimplex, glm::vec3& outNormal, float& outDepth, glm::vec3& outContactPoint) +{ + // The Expanding Polytope Algorithm (EPA) is used to find the minimum penetration depth and collision normal after GJK detects an intersection. + // It iteratively expands the simplex (the polytope) until the closest face to the origin is reached on the boundary of the Minkowski Difference. + std::vector polytope = currentSimplex; + + // Define the initial four faces of the tetrahedron using indices of the simplex points. + // The faces must be defined with consistent winding order to ensure normals point outwards. + std::vector faceIndices = + { + 0, 1, 2, + 0, 3, 1, + 0, 2, 3, + 1, 3, 2 + }; + + // Calculate the outward-pointing normal of a triangle face using the cross product of its edges. + auto getFaceNormal = [&](int a, int b, int c) + { + glm::vec3 edgeAB = polytope[b].minkowskiDifference - polytope[a].minkowskiDifference; + glm::vec3 edgeAC = polytope[c].minkowskiDifference - polytope[a].minkowskiDifference; + return glm::normalize(glm::cross(edgeAB, edgeAC)); + }; + + // Helper function to extract the exact world space contact point using barycentric coordinates. + // It projects the origin onto the closest face and interpolates the original shape points. + auto extractContactPoint = [&](int indexA, int indexB, int indexC, glm::vec3 normal, float depth) + { + glm::vec3 point0 = polytope[indexA].minkowskiDifference; + glm::vec3 point1 = polytope[indexB].minkowskiDifference; + glm::vec3 point2 = polytope[indexC].minkowskiDifference; + + glm::vec3 projectedOrigin = normal * depth; + + // Calculate vectors for barycentric coordinate calculation. + glm::vec3 edge0 = point1 - point0; + glm::vec3 edge1 = point2 - point0; + glm::vec3 edge2 = projectedOrigin - point0; + + float dot00 = glm::dot(edge0, edge0); + float dot01 = glm::dot(edge0, edge1); + float dot11 = glm::dot(edge1, edge1); + float dot20 = glm::dot(edge2, edge0); + float dot21 = glm::dot(edge2, edge1); + + float denominator = dot00 * dot11 - dot01 * dot01; + float u, v, w; + + // Handle degenerate triangles to prevent division by zero errors. + if (std::abs(denominator) < 0.0001f) + { + u = v = w = 1.0f / 3.0f; + } + else + { + v = (dot11 * dot20 - dot01 * dot21) / denominator; + w = (dot00 * dot21 - dot01 * dot20) / denominator; + u = 1.0f - v - w; + } + + // Interpolate points from the original shapes A and B to find the true contact position. + glm::vec3 contactOnA = u * polytope[indexA].pointOnA + v * polytope[indexB].pointOnA + w * polytope[indexC].pointOnA; + glm::vec3 contactOnB = u * polytope[indexA].pointOnB + v * polytope[indexB].pointOnB + w * polytope[indexC].pointOnB; + + // The final contact point is the average of the two interpolated points. + return (contactOnA + contactOnB) * 0.5f; + }; + + // Ensure all initial faces are pointing outwards from the origin to maintain a convex hull. + for (size_t i = 0; i < faceIndices.size(); i += 3) + { + glm::vec3 normal = getFaceNormal(faceIndices[i], faceIndices[i + 1], faceIndices[i + 2]); + if (glm::dot(normal, polytope[faceIndices[i]].minkowskiDifference) < 0.0f) + { + // Reverse the winding order to flip the normal direction. + std::swap(faceIndices[i + 1], faceIndices[i + 2]); + } + } + + const int maxIterations = 32; + const float tolerance = 0.001f; + + for (int iteration = 0; iteration < maxIterations; ++iteration) + { + float minDistance = FLT_MAX; + int closestFaceIndex = 0; + glm::vec3 closestNormal(0.0f); + + // Find the closest face of the current polytope to the origin. + for (size_t i = 0; i < faceIndices.size(); i += 3) + { + glm::vec3 normal = getFaceNormal(faceIndices[i], faceIndices[i + 1], faceIndices[i + 2]); + // The distance from the origin to the face is the dot product of the normal and any point on the face. + float distance = glm::dot(normal, polytope[faceIndices[i]].minkowskiDifference); + + if (distance < minDistance) + { + minDistance = distance; + closestFaceIndex = static_cast(i); + closestNormal = normal; + } + } + + // Acquire a new support point in the direction of the closest face to further expand the polytope. + SupportPoint newSupport = GetSupport(shapeA, shapeB, closestNormal); + float supportDistance = glm::dot(closestNormal, newSupport.minkowskiDifference); + + // If the distance to the new support point is not significantly further than the distance to the closest face, + // we have reached the boundary of the Minkowski Difference and found the true minimum penetration. + if (supportDistance - minDistance < tolerance) + { + outNormal = closestNormal; + outDepth = supportDistance; + outContactPoint = extractContactPoint(faceIndices[closestFaceIndex], faceIndices[closestFaceIndex + 1], faceIndices[closestFaceIndex + 2], closestNormal, supportDistance); + return; + } + + // The uniqueEdges list will store the boundary edges of the "hole" created by removing visible faces. + std::vector> uniqueEdges; + + // Find all faces that are visible from the new support point. + for (size_t i = 0; i < faceIndices.size(); i += 3) + { + glm::vec3 normal = getFaceNormal(faceIndices[i], faceIndices[i + 1], faceIndices[i + 2]); + glm::vec3 toNewPoint = newSupport.minkowskiDifference - polytope[faceIndices[i]].minkowskiDifference; + + // If the dot product is positive, the face is pointing towards the new point (it is visible). + if (glm::dot(normal, toNewPoint) > 0.0f) + { + // Add the edges of the visible face to the unique edges list. + // If an edge is already in the list, it is shared between two visible faces and should be removed (not a boundary edge). + auto addEdge = [&](int a, int b) + { + bool isDuplicate = false; + for (auto it = uniqueEdges.begin(); it != uniqueEdges.end(); ++it) + { + if ((it->first == a && it->second == b) || (it->first == b && it->second == a)) + { + uniqueEdges.erase(it); + isDuplicate = true; + break; + } + } + if (isDuplicate == false) + { + uniqueEdges.push_back({ a, b }); + } + }; + + addEdge(faceIndices[i], faceIndices[i + 1]); + addEdge(faceIndices[i + 1], faceIndices[i + 2]); + addEdge(faceIndices[i + 2], faceIndices[i]); + + // Mark the face for removal by setting its first index to -1. + faceIndices[i] = -1; + } + } + + // Rebuild the face list by excluding those marked for removal. + std::vector nextFaces; + for (size_t i = 0; i < faceIndices.size(); i += 3) + { + if (faceIndices[i] != -1) + { + nextFaces.push_back(faceIndices[i]); + nextFaces.push_back(faceIndices[i + 1]); + nextFaces.push_back(faceIndices[i + 2]); + } + } + + // Add the new support point to the polytope and create new faces connecting the boundary edges to the new point. + int newPointIndex = static_cast(polytope.size()); + polytope.push_back(newSupport); + + for (const auto& edge : uniqueEdges) + { + nextFaces.push_back(edge.first); + nextFaces.push_back(edge.second); + nextFaces.push_back(newPointIndex); + } + + faceIndices = nextFaces; + } + + // fallback when maximum iterations are reached to ensure a valid return value + outNormal = getFaceNormal(faceIndices[0], faceIndices[1], faceIndices[2]); + outDepth = glm::dot(outNormal, polytope[faceIndices[0]].minkowskiDifference); + outContactPoint = extractContactPoint(faceIndices[0], faceIndices[1], faceIndices[2], outNormal, outDepth); +} + +bool Physics3D::SweptGJK(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult) +{ + // The Swept GJK algorithm performs continuous collision detection by checking if two moving shapes intersect at any point during a time step. + // This prevents "tunneling," where objects moving at high speeds pass through each other between discrete physics updates. + + // Calculate the relative velocity between the two bodies to simplify the problem to one moving body and one stationary body. + glm::vec3 velocity1 = body1->GetVelocity(); + glm::vec3 velocity2 = body2->GetVelocity(); + glm::vec3 relativeVelocity = velocity1 - velocity2; + + float speed = glm::length(relativeVelocity); + + // Skip the sweep calculation if the objects are relatively stationary to save processing time. + if (speed < 0.001f) + { + return false; + } + + // Prepare world space vertices for both shapes to ensure accurate collision detection. + static thread_local std::vector worldVertices1; + static thread_local std::vector worldVertices2; + worldVertices1.clear(); + worldVertices2.clear(); + + GjkShape shape1; + shape1.type = body1->GetColliderType(); + + // Transform the first shape into world space. + if (shape1.type == ColliderType3D::BOX) + { + glm::quat orient1 = body1->GetEnableRotationalPhysics() ? body1->GetOrientation() : glm::quat(-glm::radians(body1->GetOwner()->GetRotate3D())); + glm::mat4 matrix1 = glm::translate(glm::mat4(1.0f), body1->GetOwner()->GetPosition()) * glm::mat4_cast(orient1); + + for (const auto& point : body1->GetCollidePolyhedron()) + { + worldVertices1.emplace_back(glm::vec3(matrix1 * glm::vec4(point, 1.0f))); + } + shape1.vertices = &worldVertices1; + } + else if (shape1.type == ColliderType3D::SPHERE) + { + shape1.center = body1->GetOwner()->GetPosition(); + shape1.radius = body1->GetSphereRadius() / 2.0f; + } + + GjkShape shape2; + shape2.type = body2->GetColliderType(); + + // Transform the second shape into world space. + if (shape2.type == ColliderType3D::BOX) + { + glm::quat orient2 = body2->GetEnableRotationalPhysics() ? body2->GetOrientation() : glm::quat(-glm::radians(body2->GetOwner()->GetRotate3D())); + glm::mat4 matrix2 = glm::translate(glm::mat4(1.0f), body2->GetOwner()->GetPosition()) * glm::mat4_cast(orient2); + + for (const auto& point : body2->GetCollidePolyhedron()) + { + worldVertices2.emplace_back(glm::vec3(matrix2 * glm::vec4(point, 1.0f))); + } + shape2.vertices = &worldVertices2; + } + else if (shape2.type == ColliderType3D::SPHERE) + { + shape2.center = body2->GetOwner()->GetPosition(); + shape2.radius = body2->GetSphereRadius() / 2.0f; + } + + // To prevent tunneling, divide the movement into several safe steps based on a maximum distance per step. + const float safeDistanceStep = 0.5f; + float totalDistance = speed * dt; + + int numSteps = static_cast(std::ceil(totalDistance / safeDistanceStep)); + numSteps = std::max(1, std::min(numSteps, 32)); + + float timeStep = dt / static_cast(numSteps); + float previousSafeTime = 0.0f; + + // Check each sub-step for a potential collision. + for (int i = 1; i <= numSteps; ++i) + { + float currentTime = i * timeStep; + + // Apply temporary offsets based on velocity to simulate movement at the current time step. + shape1.offset = velocity1 * currentTime; + shape2.offset = velocity2 * currentTime; + + std::vector simplex; + + // Execute a discrete GJK check at the current time step. + if (CheckCollisionGJK(shape1, shape2, simplex)) + { + float lowTime = previousSafeTime; + float highTime = currentTime; + std::vector exactSimplex; + + // Once a collision is detected in a time block, use binary search to find the precise time of impact (TOI). + for (int b = 0; b < 8; ++b) + { + float midTime = (lowTime + highTime) * 0.5f; + + shape1.offset = velocity1 * midTime; + shape2.offset = velocity2 * midTime; + + std::vector midSimplex; + if (CheckCollisionGJK(shape1, shape2, midSimplex)) + { + // The collision occurs before or at midTime. + highTime = midTime; + exactSimplex = midSimplex; + } + else + { + // The collision occurs after midTime. + lowTime = midTime; + } + } + + // Finalize shape positions at the precise impact time found by binary search. + shape1.offset = velocity1 * highTime; + shape2.offset = velocity2 * highTime; + + glm::vec3 contactNormal(0.0f); + float contactDepth = 0.0f; + glm::vec3 contactPoint(0.0f); + + // Use EPA to determine the contact normal and penetration at the exact moment of impact. + CalculatePenetrationEPA(shape1, shape2, exactSimplex, contactNormal, contactDepth, contactPoint); + + // Fill the collision result structure with the impact data. + outResult.hasCollided = true; + outResult.timeOfImpact = highTime / dt; + outResult.collisionNormal = contactNormal; + outResult.otherObject = body2->GetOwner(); + + return true; + } + + previousSafeTime = currentTime; + } + + return false; +} + + +//======== Legacy: SAT ========// + +/* +bool Physics3D::CollisionPPSAT(Object* obj, Object* obj2, CollisionMode mode) +{ + auto* physics1 = obj->GetComponent(); + auto* physics2 = obj2->GetComponent(); + + if (physics1->GetCollidePolyhedron().empty() == false && physics2->GetCollidePolyhedron().empty() == false) + { + const auto& poly1 = physics1->GetCollidePolyhedron(); // ¸â¹ö Á÷Á¢ Á¢±Ù(collidePolyhedron) ´ë½Å Getter »ç¿ë ±ÇÀå + const auto& poly2 = physics2->GetCollidePolyhedron(); + + if (poly1.empty() || poly2.empty()) + { + return false; + } + + // Use thread_local static variables to prevent heap allocation on every collision check + // .clear() sets size to 0 but retains capacity for memory reuse + static thread_local std::vector rotatedPoly1; + static thread_local std::vector rotatedPoly2; + static thread_local std::vector collisionAxes; + + rotatedPoly1.clear(); + rotatedPoly2.clear(); + collisionAxes.clear(); + // Transform local polyhedron vertices to world space - std::vector rotatedPoly1, rotatedPoly2; glm::quat orient1 = physics1->GetEnableRotationalPhysics() ? physics1->GetOrientation() : glm::quat(-glm::radians(obj->GetRotate3D())); glm::quat orient2 = physics2->GetEnableRotationalPhysics() ? physics2->GetOrientation() : glm::quat(-glm::radians(obj2->GetRotate3D())); @@ -348,19 +1412,34 @@ bool Physics3D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) { rotatedPoly1.emplace_back(glm::vec3(transform1 * glm::vec4(point, 1.0f))); } + for (const auto& point : poly2) { rotatedPoly2.emplace_back(glm::vec3(transform2 * glm::vec4(point, 1.0f))); } + // Temporal Coherence: Check the cached separating axis first + auto cacheIterator = physics1->separatingAxisCache.find(physics2); + + if (cacheIterator != physics1->separatingAxisCache.end()) + { + glm::vec3 cachedAxis = cacheIterator->second; + float dummyDepth, dummyMin1, dummyMax1, dummyMin2, dummyMax2; + + // If the old axis still separates them, skip all other axis calculations + if (IsSeparatingAxis(cachedAxis, rotatedPoly1, rotatedPoly2, &dummyDepth, &dummyMin1, &dummyMax1, &dummyMin2, &dummyMax2)) + { + return false; // Early Exit: Massive performance boost + } + } + // Collect all potential separating axes (face normals and edge cross products) - std::vector axes; - axes.push_back(glm::normalize(glm::vec3(rotationMatrix1[0]))); - axes.push_back(glm::normalize(glm::vec3(rotationMatrix1[1]))); - axes.push_back(glm::normalize(glm::vec3(rotationMatrix1[2]))); - axes.push_back(glm::normalize(glm::vec3(rotationMatrix2[0]))); - axes.push_back(glm::normalize(glm::vec3(rotationMatrix2[1]))); - axes.push_back(glm::normalize(glm::vec3(rotationMatrix2[2]))); + collisionAxes.push_back(glm::normalize(glm::vec3(rotationMatrix1[0]))); + collisionAxes.push_back(glm::normalize(glm::vec3(rotationMatrix1[1]))); + collisionAxes.push_back(glm::normalize(glm::vec3(rotationMatrix1[2]))); + collisionAxes.push_back(glm::normalize(glm::vec3(rotationMatrix2[0]))); + collisionAxes.push_back(glm::normalize(glm::vec3(rotationMatrix2[1]))); + collisionAxes.push_back(glm::normalize(glm::vec3(rotationMatrix2[2]))); for (size_t i = 0; i < rotatedPoly1.size(); ++i) { @@ -368,10 +1447,11 @@ bool Physics3D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) { glm::vec3 edge1 = rotatedPoly1[(i + 1) % rotatedPoly1.size()] - rotatedPoly1[i]; glm::vec3 edge2 = rotatedPoly2[(j + 1) % rotatedPoly2.size()] - rotatedPoly2[j]; - glm::vec3 axis = glm::cross(edge1, edge2); - if (glm::length(axis) > 0.0001f) + glm::vec3 crossAxis = glm::cross(edge1, edge2); + + if (glm::length(crossAxis) > 0.0001f) { - axes.push_back(glm::normalize(axis)); + collisionAxes.push_back(glm::normalize(crossAxis)); } } } @@ -380,21 +1460,31 @@ bool Physics3D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) glm::vec3 collisionNormal(0.f); // Test for separation along each axis (Separating Axis Theorem) - for (const auto& axis : axes) + for (const auto& axis : collisionAxes) { float min1, max1, min2, max2; - float depth; - if (IsSeparatingAxis(axis, rotatedPoly1, rotatedPoly2, &depth, &min1, &max1, &min2, &max2)) + float currentDepth; + + if (IsSeparatingAxis(axis, rotatedPoly1, rotatedPoly2, ¤tDepth, &min1, &max1, &min2, &max2)) { + // Save the axis that separated the objects to the cache for the next frame + physics1->separatingAxisCache[physics2] = axis; + physics2->separatingAxisCache[physics1] = -axis; // Reverse direction for the other body + return false; // Gap found, no collision } - if (depth < minDepth) + + if (currentDepth < minDepth) { - minDepth = depth; + minDepth = currentDepth; collisionNormal = ((max1 - min2) < (max2 - min1)) ? axis : -axis; } } + // Actual collision occurred. Remove from cache to recalculate accurately next time. + physics1->separatingAxisCache.erase(physics2); + physics2->separatingAxisCache.erase(physics1); + // Resolve penetration and calculate contact physics if (mode == CollisionMode::COLLIDE && !physics1->GetIsGhostCollision() && !physics2->GetIsGhostCollision()) { @@ -524,7 +1614,7 @@ bool Physics3D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) return false; } -bool Physics3D::CollisionSS(Object* obj, Object* obj2, CollisionMode mode) +bool Physics3D::CollisionSSSAT(Object* obj, Object* obj2, CollisionMode mode) { // Simple sphere-to-sphere distance check glm::vec3 center1 = obj->GetPosition(); @@ -583,7 +1673,7 @@ bool Physics3D::CollisionSS(Object* obj, Object* obj2, CollisionMode mode) return false; } -bool Physics3D::CollisionPS(Object* poly, Object* sph, CollisionMode mode) +bool Physics3D::CollisionPSSAT(Object* poly, Object* sph, CollisionMode mode) { // Polygon vs Sphere: Find the closest point on the polygon to the sphere center Physics3D* polyPhysics = poly->GetComponent(); @@ -643,80 +1733,32 @@ bool Physics3D::CollisionPS(Object* poly, Object* sph, CollisionMode mode) } polyPhysics->Awake(); sphPhysics->Awake(); - } - else if (polyPhysics->GetBodyType() == BodyType3D::RIGID) - { - if (glm::length2(moveVec) > 0.0f) - { - poly->SetPosition(poly->GetPosition() - moveVec); - } - polyPhysics->Awake(); - } - else if (sphPhysics->GetBodyType() == BodyType3D::RIGID) - { - if (glm::length2(moveVec) > 0.0f) - { - sph->SetPosition(sph->GetPosition() + moveVec); - } - sphPhysics->Awake(); - } - - // Apply impulse physics - glm::vec3 cp = polyPos + (polyOrient * closestPointInLocal); - CalculateLinearVelocity(*polyPhysics, *sphPhysics, normal, &depth, cp); - } - return true; - } - - return false; -} - -void Physics3D::AddCollidePolyhedron(glm::vec3 position) -{ - colliderType = ColliderType3D::BOX; - collidePolyhedron.push_back(position); -} - -void Physics3D::AddCollidePolyhedronAABB(glm::vec3 min, glm::vec3 max) -{ - // Define an axis-aligned bounding box from min/max corners - colliderType = ColliderType3D::BOX; - collidePolyhedron.clear(); - collidePolyhedron.push_back(glm::vec3(min.x, min.y, min.z)); - collidePolyhedron.push_back(glm::vec3(min.x, max.y, min.z)); - collidePolyhedron.push_back(glm::vec3(max.x, max.y, min.z)); - collidePolyhedron.push_back(glm::vec3(max.x, min.y, min.z)); - collidePolyhedron.push_back(glm::vec3(min.x, min.y, max.z)); - collidePolyhedron.push_back(glm::vec3(min.x, max.y, max.z)); - collidePolyhedron.push_back(glm::vec3(max.x, max.y, max.z)); - collidePolyhedron.push_back(glm::vec3(max.x, min.y, max.z)); -} - -void Physics3D::AddCollidePolyhedronAABB(glm::vec3 size) -{ - AddCollidePolyhedronAABB(-size / 2.f, size / 2.f); -} - -void Physics3D::AddCollideSphere(float r) -{ - colliderType = ColliderType3D::SPHERE; - collidePolyhedron.clear(); - sphere.radius = r; -} + } + else if (polyPhysics->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveVec) > 0.0f) + { + poly->SetPosition(poly->GetPosition() - moveVec); + } + polyPhysics->Awake(); + } + else if (sphPhysics->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveVec) > 0.0f) + { + sph->SetPosition(sph->GetPosition() + moveVec); + } + sphPhysics->Awake(); + } -glm::vec3 Physics3D::FindSATCenter(const std::vector& pts) -{ - // Calculate the arithmetic mean of a set of points - glm::vec3 center(0.0f); - if (pts.empty()) - { - return center; - } - for (const auto& p : pts) - { - center += p; + // Apply impulse physics + glm::vec3 cp = polyPos + (polyOrient * closestPointInLocal); + CalculateLinearVelocity(*polyPhysics, *sphPhysics, normal, &depth, cp); + } + return true; } - return center / static_cast(pts.size()); + + return false; } glm::vec3 Physics3D::RotatePoint(const glm::vec3& pt, const glm::vec3& pos, const glm::quat& rot) @@ -725,124 +1767,62 @@ glm::vec3 Physics3D::RotatePoint(const glm::vec3& pt, const glm::vec3& pos, cons return (rot * pt) + pos; } -bool Physics3D::IsSeparatingAxis(const glm::vec3 axis, const std::vector pts1, const std::vector pts2, float* depth, float* mi1, float* ma1, float* mi2, float* ma2) +bool Physics3D::IsSeparatingAxis(const glm::vec3 axis, const std::vector points1, const std::vector points2, float* axisDepth, float* min1, float* max1, float* min2, float* max2) { // Project both polygons onto an axis to check for overlap - float min1 = FLT_MAX; - float max1 = -FLT_MAX; - float min2 = FLT_MAX; - float max2 = -FLT_MAX; + float currentMin1 = FLT_MAX; + float currentMax1 = -FLT_MAX; + float currentMin2 = FLT_MAX; + float currentMax2 = -FLT_MAX; - for (const glm::vec3& v : pts1) + for (const glm::vec3& vertex : points1) { - float p = glm::dot(axis, v); - min1 = std::min(min1, p); - max1 = std::max(max1, p); + float projection = glm::dot(axis, vertex); + currentMin1 = std::min(currentMin1, projection); + currentMax1 = std::max(currentMax1, projection); } - for (const glm::vec3& v : pts2) + + for (const glm::vec3& vertex : points2) { - float p = glm::dot(axis, v); - min2 = std::min(min2, p); - max2 = std::max(max2, p); + float projection = glm::dot(axis, vertex); + currentMin2 = std::min(currentMin2, projection); + currentMax2 = std::max(currentMax2, projection); } // Determine the amount of overlap along the axis - *depth = std::min(max2 - min1, max1 - min2); - *mi1 = min1; *ma1 = max1; *mi2 = min2; *ma2 = max2; + *axisDepth = std::min(currentMax2 - currentMin1, currentMax1 - currentMin2); + *min1 = currentMin1; + *max1 = currentMax1; + *min2 = currentMin2; + *max2 = currentMax2; - return !(max1 >= min2 && max2 >= min1); + return !(currentMax1 >= currentMin2 && currentMax2 >= currentMin1); } -bool Physics3D::IsSeparatingAxis(const glm::vec3 axis, const std::vector ptsPoly, const glm::vec3 ptSphere, const float r, float* depth, float* mi1, float* ma1, float* mi2, float* ma2) +bool Physics3D::IsSeparatingAxis(const glm::vec3 axis, const std::vector pointsPoly, const glm::vec3 pointSphere, const float radius, float* axisDepth, float* min1, float* max1, float* min2, float* max2) { // Project polygon and sphere (represented as center +/- radius) onto an axis - float min1 = FLT_MAX; - float max1 = -FLT_MAX; - - for (const glm::vec3& p : ptsPoly) - { - float proj = glm::dot(p, axis); - min1 = std::min(min1, proj); - max1 = std::max(max1, proj); - } - - float sphereProj = glm::dot(ptSphere, axis); - float min2 = sphereProj - r; - float max2 = sphereProj + r; - - *depth = std::min(max2 - min1, max1 - min2); - *mi1 = min1; *ma1 = max1; *mi2 = min2; *ma2 = max2; - - return !(max1 >= min2 && max2 >= min1); -} - -void Physics3D::CalculateLinearVelocity(Physics3D& body, Physics3D& body2, glm::vec3 normal, float* /*axisDepth*/, glm::vec3 contactPoint, float impulseScale) -{ - // Calculate lever arms from center of mass to contact point - glm::vec3 ra = contactPoint - body.GetOwner()->GetPosition(); - glm::vec3 rb = contactPoint - body2.GetOwner()->GetPosition(); - - // Calculate contact velocities including rotational components - glm::vec3 vaContact = body.GetVelocity() + glm::cross(body.GetAngularVelocity(), ra); - glm::vec3 vbContact = body2.GetVelocity() + glm::cross(body2.GetAngularVelocity(), rb); - - // Get the relative velocity along the normal - glm::vec3 relativeVelocity = vbContact - vaContact; - float velAlongNormal = glm::dot(relativeVelocity, normal); - - // Do not resolve if objects are already separating - if (velAlongNormal > 0.0f) - { - return; - } - - float res = std::min(body.GetRestitution(), body2.GetRestitution()); - - // Compute rotational effect on the impulse denominator - glm::vec3 raCrossN = glm::cross(ra, normal); - glm::vec3 rbCrossN = glm::cross(rb, normal); + float currentMin1 = FLT_MAX; + float currentMax1 = -FLT_MAX; - float invMassSum = (body.GetBodyType() == BodyType3D::RIGID ? (1.f / body.mass) : 0.0f) + (body2.GetBodyType() == BodyType3D::RIGID ? (1.f / body2.mass) : 0.0f); - - float denominator = invMassSum; - if (body.GetBodyType() == BodyType3D::RIGID && body.GetEnableRotationalPhysics()) - { - glm::vec3 angEffectA = glm::cross(raCrossN * body.GetInverseInertia(), ra); - denominator += glm::dot(angEffectA, normal); - } - if (body2.GetBodyType() == BodyType3D::RIGID && body2.GetEnableRotationalPhysics()) + for (const glm::vec3& vertex : pointsPoly) { - glm::vec3 angEffectB = glm::cross(rbCrossN * body2.GetInverseInertia(), rb); - denominator += glm::dot(angEffectB, normal); + float projection = glm::dot(vertex, axis); + currentMin1 = std::min(currentMin1, projection); + currentMax1 = std::max(currentMax1, projection); } - if (denominator <= 0.0f) - { - return; - } + float sphereProjection = glm::dot(pointSphere, axis); + float currentMin2 = sphereProjection - radius; + float currentMax2 = sphereProjection + radius; - // Calculate the impulse magnitude j - float j = -(1.f + res) * velAlongNormal / denominator; - j *= impulseScale; - glm::vec3 impulse = normal * j; + *axisDepth = std::min(currentMax2 - currentMin1, currentMax1 - currentMin2); + *min1 = currentMin1; + *max1 = currentMax1; + *min2 = currentMin2; + *max2 = currentMax2; - // Apply the computed impulse to both bodies - if (body.GetBodyType() == BodyType3D::RIGID) - { - body.SetVelocity(body.GetVelocity() - impulse * (1.f / body.mass)); - if (body.GetEnableRotationalPhysics()) - { - body.SetAngularVelocity(body.GetAngularVelocity() - glm::cross(ra, impulse) * body.GetInverseInertia()); - } - } - if (body2.GetBodyType() == BodyType3D::RIGID) - { - body2.SetVelocity(body2.GetVelocity() + impulse * (1.f / body2.mass)); - if (body2.GetEnableRotationalPhysics()) - { - body2.SetAngularVelocity(body2.GetAngularVelocity() + glm::cross(rb, impulse) * body2.GetInverseInertia()); - } - } + return !(currentMax1 >= currentMin2 && currentMax2 >= currentMin1); } glm::vec3 Physics3D::FindClosestPointOnSegment(const glm::vec3& cpSphere, std::vector& verts) @@ -902,6 +1882,56 @@ void Physics3D::ProjectPolygon(const std::vector& verts, const glm::v } } +bool Physics3D::StaticSATIntersection(Physics3D*, Physics3D*, const std::vector& rp1, const std::vector& rp2, const glm::mat4& rm1, const glm::mat4& rm2, glm::vec3& oNorm, float& oDepth) +{ + // Basic SAT test for static intersection (non-swept) + std::vector axes; + glm::vec3 a1[3] = { glm::normalize(glm::vec3(rm1[0])), glm::normalize(glm::vec3(rm1[1])), glm::normalize(glm::vec3(rm1[2])) }; + glm::vec3 a2[3] = { glm::normalize(glm::vec3(rm2[0])), glm::normalize(glm::vec3(rm2[1])), glm::normalize(glm::vec3(rm2[2])) }; + + for (int i = 0; i < 3; ++i) + { + axes.push_back(a1[i]); + axes.push_back(a2[i]); + } + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + glm::vec3 c = glm::cross(a1[i], a2[j]); + if (glm::length2(c) > 0.0001f) + { + axes.push_back(glm::normalize(c)); + } + } + } + + float mDepth = FLT_MAX; + glm::vec3 cNorm(0.0f); + for (const auto& a : axes) + { + float min1, max1, min2, max2; + ProjectPolygon(rp1, a, min1, max1); + ProjectPolygon(rp2, a, min2, max2); + + if (max1 < min2 || max2 < min1) + { + return false; + } + + float d = std::min(max2 - min1, max1 - min2); + if (d < mDepth) + { + mDepth = d; + cNorm = (max1 - min2) < (max2 - min1) ? a : -a; + } + } + oNorm = glm::normalize(cNorm); + oDepth = mDepth; + return true; +} + + bool Physics3D::SweptSATOBB(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& res) { // High-performance swept collision detection using SAT @@ -1027,95 +2057,6 @@ bool Physics3D::SweptSATOBB(Physics3D* body1, Physics3D* body2, float dt, Collis return false; } -bool Physics3D::StaticSATIntersection(Physics3D* /*b1*/, Physics3D* /*b2*/, const std::vector& rp1, const std::vector& rp2, const glm::mat4& rm1, const glm::mat4& rm2, glm::vec3& oNorm, float& oDepth) -{ - // Basic SAT test for static intersection (non-swept) - std::vector axes; - glm::vec3 a1[3] = { glm::normalize(glm::vec3(rm1[0])), glm::normalize(glm::vec3(rm1[1])), glm::normalize(glm::vec3(rm1[2])) }; - glm::vec3 a2[3] = { glm::normalize(glm::vec3(rm2[0])), glm::normalize(glm::vec3(rm2[1])), glm::normalize(glm::vec3(rm2[2])) }; - - for (int i = 0; i < 3; ++i) - { - axes.push_back(a1[i]); - axes.push_back(a2[i]); - } - for (int i = 0; i < 3; ++i) - { - for (int j = 0; j < 3; ++j) - { - glm::vec3 c = glm::cross(a1[i], a2[j]); - if (glm::length2(c) > 0.0001f) - { - axes.push_back(glm::normalize(c)); - } - } - } - - float mDepth = FLT_MAX; - glm::vec3 cNorm(0.0f); - for (const auto& a : axes) - { - float min1, max1, min2, max2; - ProjectPolygon(rp1, a, min1, max1); - ProjectPolygon(rp2, a, min2, max2); - - if (max1 < min2 || max2 < min1) - { - return false; - } - - float d = std::min(max2 - min1, max1 - min2); - if (d < mDepth) - { - mDepth = d; - cNorm = (max1 - min2) < (max2 - min1) ? a : -a; - } - } - oNorm = glm::normalize(cNorm); - oDepth = mDepth; - return true; -} - -bool Physics3D::SweptSpheres(Physics3D* b1, Physics3D* b2, float dt, CollisionResult& res) -{ - // Continuous collision detection between two moving spheres - Object* o1 = b1->GetOwner(), * o2 = b2->GetOwner(); - glm::vec3 p1 = o1->GetPosition(), p2 = o2->GetPosition(); - float r1 = b1->sphere.radius / 2.0f, r2 = b2->sphere.radius / 2.0f, rSum = r1 + r2; - glm::vec3 vRel = (b2->GetVelocity() - b1->GetVelocity()) * dt; - glm::vec3 diff = p2 - p1; - - // Solve quadratic equation for moving distance: |(P+tV) - (P')|^2 = (r+r')^2 - float a = glm::dot(vRel, vRel); - float b = 2.0f * glm::dot(diff, vRel); - float c = glm::dot(diff, diff) - rSum * rSum; - - if (c < 0.0f) - { - res.hasCollided = true; res.timeOfImpact = 0.0f; res.otherObject = o2; - res.collisionNormal = glm::normalize(p1 - p2); return true; - } - if (a < 0.00001f) - { - return false; - } - - float det = b * b - 4 * a * c; - if (det < 0) - { - return false; - } - - float t = (-b - sqrt(det)) / (2.0f * a); - if (t >= 0.0f && t <= 1.0f) - { - res.hasCollided = true; res.timeOfImpact = t; res.otherObject = o2; - res.collisionNormal = glm::normalize((p1 + b1->GetVelocity() * dt * t) - (p2 + b2->GetVelocity() * dt * t)); - return true; - } - return false; -} - bool Physics3D::SweptSphereVsOBB(Physics3D* b2, float dt, CollisionResult& res) { // Test moving sphere against a static/rotating Oriented Bounding Box @@ -1177,53 +2118,4 @@ bool Physics3D::SweptSphereVsOBB(Physics3D* b2, float dt, CollisionResult& res) } return false; } - -CollisionResult Physics3D::FindClosestCollision(float dt) -{ - // Search all scene objects to find the earliest collision in this frame - CollisionResult res; - res.timeOfImpact = 1.1f; - Object* s = GetOwner(); - - for (auto& p : Engine::GetObjectManager().GetObjectMap()) - { - Object* o = p.second.get(); - if (s == o || !o->HasComponent()) - { - continue; - } - - Physics3D* b = o->GetComponent(); - CollisionResult cur; - ColliderType3D t1 = GetColliderType(); - ColliderType3D t2 = b->GetColliderType(); - bool hit = false; - - if (t1 == ColliderType3D::SPHERE && t2 == ColliderType3D::SPHERE) - { - hit = SweptSpheres(this, b, dt, cur); - } - else if (t1 == ColliderType3D::BOX && t2 == ColliderType3D::BOX) - { - hit = SweptSATOBB(this, b, dt, cur); - } - else if (t1 == ColliderType3D::SPHERE && t2 == ColliderType3D::BOX) - { - hit = SweptSphereVsOBB(b, dt, cur); - } - else if (t1 == ColliderType3D::BOX && t2 == ColliderType3D::SPHERE) - { - if (b->SweptSphereVsOBB(this, dt, cur)) - { - cur.collisionNormal *= -1.0f; hit = true; - } - } - - // Track only the earliest impact - if (hit && cur.timeOfImpact < res.timeOfImpact) - { - res = cur; - } - } - return res; -} \ No newline at end of file +*/ \ No newline at end of file diff --git a/Engine/source/PhysicsManager.cpp b/Engine/source/PhysicsManager.cpp index f4441217..50e977a1 100644 --- a/Engine/source/PhysicsManager.cpp +++ b/Engine/source/PhysicsManager.cpp @@ -41,39 +41,138 @@ CollisionMode PhysicsManager::GetCollisionMode(ObjectType typeA, ObjectType type void PhysicsManager::AddBody2D(Physics2D* body) { - // Add the body if it's not already in the list + //// Add the body if it's not already in the list + //if (std::find(bodies2D.begin(), bodies2D.end(), body) == bodies2D.end()) + //{ + // bodies2D.push_back(body); + //} + if (std::find(bodies2D.begin(), bodies2D.end(), body) == bodies2D.end()) { bodies2D.push_back(body); + + if (bvhNodes2D.empty()) + { + int initialCapacity = 256; + bvhNodes2D.resize(initialCapacity); + for (int i = 0; i < initialCapacity - 1; ++i) + { + bvhNodes2D[i].nextFreeNodeIndex = i + 1; + } + bvhNodes2D[initialCapacity - 1].nextFreeNodeIndex = -1; + freeListIndex2D = 0; + } + + int leafIndex = AllocateNode2D(); + bvhNodes2D[leafIndex].physicsBody = body; + + AABB2D tightAABB = ComputeAABB2D(body); + bvhNodes2D[leafIndex].boundingBox.minExtent = tightAABB.minExtent - FAT_BOUNDS_MARGIN_2D; + bvhNodes2D[leafIndex].boundingBox.maxExtent = tightAABB.maxExtent + FAT_BOUNDS_MARGIN_2D; + + InsertLeaf2D(leafIndex); + bodyNodeMap2D[body] = leafIndex; } } void PhysicsManager::RemoveBody2D(Physics2D* body) { - // Search and erase the specified 2D body + //// Search and erase the specified 2D body + //auto iterator = std::find(bodies2D.begin(), bodies2D.end(), body); + //if (iterator != bodies2D.end()) + //{ + // bodies2D.erase(iterator); + //} + auto iterator = std::find(bodies2D.begin(), bodies2D.end(), body); if (iterator != bodies2D.end()) { bodies2D.erase(iterator); + + // Prevent dangling pointers in Narrow Phase cache + for (Physics2D* remainingBody : bodies2D) + { + remainingBody->RemoveFromCollisionCache(body); + } + + if (bodyNodeMap2D.find(body) != bodyNodeMap2D.end()) + { + int leafIndex = bodyNodeMap2D[body]; + RemoveLeaf2D(leafIndex); + FreeNode2D(leafIndex); + bodyNodeMap2D.erase(body); + } } } void PhysicsManager::AddBody3D(Physics3D* body) { - // Add the body if it's not already in the list + //// Add the body if it's not already in the list + //if (std::find(bodies3D.begin(), bodies3D.end(), body) == bodies3D.end()) + //{ + // bodies3D.push_back(body); + //} + if (std::find(bodies3D.begin(), bodies3D.end(), body) == bodies3D.end()) { bodies3D.push_back(body); + + // Initialize node pool if empty + if (bvhNodes.empty()) + { + int initialCapacity = 256; + bvhNodes.resize(initialCapacity); + for (int i = 0; i < initialCapacity - 1; ++i) + { + bvhNodes[i].nextFreeNodeIndex = i + 1; + } + bvhNodes[initialCapacity - 1].nextFreeNodeIndex = -1; + freeListIndex = 0; + } + + // Allocate a leaf node for the new body + int leafIndex = AllocateNode(); + bvhNodes[leafIndex].physicsBody = body; + + // Calculate initial AABB and add Margin (Fat AABB) + AABB tightAABB = ComputeAABB(body); + glm::vec3 margin(0.2f, 0.2f, 0.2f); // Fat margin to prevent frequent updates + bvhNodes[leafIndex].boundingBox.minExtent = tightAABB.minExtent - FAT_BOUNDS_MARGIN; + bvhNodes[leafIndex].boundingBox.maxExtent = tightAABB.maxExtent + FAT_BOUNDS_MARGIN; + + InsertLeaf(leafIndex); + bodyNodeMap[body] = leafIndex; } } void PhysicsManager::RemoveBody3D(Physics3D* body) { - // Search and erase the specified 3D body + //// Search and erase the specified 3D body + //auto iterator = std::find(bodies3D.begin(), bodies3D.end(), body); + //if (iterator != bodies3D.end()) + //{ + // bodies3D.erase(iterator); + //} + auto iterator = std::find(bodies3D.begin(), bodies3D.end(), body); if (iterator != bodies3D.end()) { bodies3D.erase(iterator); + + // Clean up cached pointers in all other bodies to prevent dangling pointers + for (Physics3D* remainingBody : bodies3D) + { + remainingBody->RemoveFromCollisionCache(body); + } + + // Remove from the dynamic tree and free the node + if (bodyNodeMap.find(body) != bodyNodeMap.end()) + { + int leafIndex = bodyNodeMap[body]; + RemoveLeaf(leafIndex); + FreeNode(leafIndex); + bodyNodeMap.erase(body); + } } } @@ -84,16 +183,16 @@ void PhysicsManager::UpdatePhysics2D(float dt) return; } - // Step 1: Move objects based on their current velocity + // Move objects based on their current velocity. This is the first phase of the physics simulation. ApplyMovement2D(dt); - // Step 2: Broad Phase - Quickly filter out objects that are far apart - auto collisionPairs = BroadPhase2D(); + // Broad Phase - Quickly filter out objects that are far apart using spatial partitioning methods. + auto collisionPairs = BroadPhase2D(dt); - // Step 3: Narrow Phase - Perform detailed collision checks on potential pairs + // Narrow Phase - Perform detailed collision checks on potential pairs to get exact collision resolution. NarrowPhase2D(collisionPairs, dt); - // Step 4: Notify objects about potential collisions + // Notify objects about potential collisions so they can handle gameplay logic. for (auto& pair : collisionPairs) { Object* objectA = pair.bodyA->GetOwner(); @@ -134,74 +233,64 @@ void PhysicsManager::ApplyMovement2D(float dt) -std::vector PhysicsManager::BroadPhase2D() +std::vector PhysicsManager::BroadPhase2D(float dt) { - std::vector pairs; + std::vector collisionPairs; - // Helper lambda to calculate the Axis-Aligned Bounding Box (AABB) - auto getAabb2D = [](Physics2D* body, glm::vec2& outMin, glm::vec2& outMax) + // Check if the 2D tree root exists + if (rootNodeIndex2D == -1) { - Object* owner = body->GetOwner(); - glm::vec3 currentPosition = owner->GetPosition(); + return collisionPairs; + } - if (body->GetCollideType() == CollideType::CIRCLE) + // Update the Dynamic BVH tree incrementally + for (Physics2D* body : bodies2D) + { + // Skip if the body is not registered in the tree map + if (bodyNodeMap2D.find(body) == bodyNodeMap2D.end()) { - // For circles, AABB is center +/- radius - float radius = body->GetCircleCollideRadius(); - outMin = { currentPosition.x - radius, currentPosition.y - radius }; - outMax = { currentPosition.x + radius, currentPosition.y + radius }; + continue; } - else + + int leafIndex = bodyNodeMap2D[body]; + AABB2D currentTightAabb = ComputeAABB2D(body); + + // Check if the current tight AABB has moved outside the cached Fat AABB + if (bvhNodes2D[leafIndex].boundingBox.Contains(currentTightAabb) == false) { - // For polygons, calculate the bounding box based on rotated vertices - outMin = { INFINITY, INFINITY }; - outMax = { -INFINITY, -INFINITY }; + // Remove the old leaf and prepare for re-insertion + RemoveLeaf2D(leafIndex); - float angleDegrees = owner->GetRotate(); - float angleRadians = angleDegrees * 3.14159265f / 180.f; - float cosAngle = std::cos(angleRadians); - float sinAngle = std::sin(angleRadians); + // Predict future position using velocity to reduce tree update frequency + glm::vec2 velocityMultiplier = body->GetVelocity() * 2.0f * dt; - for (const glm::vec2& point : body->GetCollidePolygon()) - { - // Local to world rotation math - float rotatedX = point.x * cosAngle - point.y * sinAngle; - float rotatedY = point.x * sinAngle + point.y * cosAngle; - glm::vec2 worldPosition = { currentPosition.x + rotatedX, currentPosition.y + rotatedY }; - - outMin.x = std::min(outMin.x, worldPosition.x); - outMin.y = std::min(outMin.y, worldPosition.y); - outMax.x = std::max(outMax.x, worldPosition.x); - outMax.y = std::max(outMax.y, worldPosition.y); - } + AABB2D newFatAabb; + newFatAabb.minExtent = currentTightAabb.minExtent - FAT_BOUNDS_MARGIN_2D + glm::min(glm::vec2(0.0f), velocityMultiplier); + newFatAabb.maxExtent = currentTightAabb.maxExtent + FAT_BOUNDS_MARGIN_2D + glm::max(glm::vec2(0.0f), velocityMultiplier); + + bvhNodes2D[leafIndex].boundingBox = newFatAabb; + + // Re-insert leaf into the optimal position in the tree + InsertLeaf2D(leafIndex); } - }; + } - // Double-loop check for AABB overlaps - for (size_t i = 0; i < bodies2D.size(); ++i) + // Query the updated tree to find potential collision pairs + for (Physics2D* body : bodies2D) { - glm::vec2 minA, maxA; - getAabb2D(bodies2D[i], minA, maxA); - - for (size_t j = i + 1; j < bodies2D.size(); ++j) + if (bodyNodeMap2D.find(body) == bodyNodeMap2D.end()) { - glm::vec2 minB, maxB; - getAabb2D(bodies2D[j], minB, maxB); + continue; + } - // AABB Overlap test - if (maxA.x < minB.x || maxB.x < minA.x) - { - continue; - } - if (maxA.y < minB.y || maxB.y < minA.y) - { - continue; - } + int leafIndex = bodyNodeMap2D[body]; + const AABB2D& queryAabb = bvhNodes2D[leafIndex].boundingBox; - pairs.push_back({ bodies2D[i], bodies2D[j] }); - } + // Traverse tree to find overlapping AABBs O(N log N) + QueryTree2D(rootNodeIndex2D, body, queryAabb, collisionPairs); } - return pairs; + + return collisionPairs; } @@ -273,13 +362,13 @@ void PhysicsManager::UpdatePhysics3D(float dt) return; } - // Step 1: Linear/Angular integration + // Linear and angular integration. This updates positions and velocities before collision checks. Integrate3D(dt); - // Step 2: 3D Broad Phase + // 3D Broad Phase using Dynamic Bounding Volume Hierarchies to find potential colliding pairs. auto collisionPairs = BroadPhase3D(dt); - // Step 3: 3D Narrow Phase + // 3D Narrow Phase utilizing accurate algorithms to determine exact overlap and calculate contact details. NarrowPhase3D(collisionPairs, dt); for (auto& pair : collisionPairs) @@ -319,72 +408,136 @@ void PhysicsManager::Integrate3D(float dt) std::vector PhysicsManager::BroadPhase3D(float dt) { - std::vector pairs; + //std::vector pairs; + + //// Helper lambda to calculate 3D AABB with support for Continuous Collision Detection (CCD) + //auto getAabb3D = [dt](Physics3D* body, glm::vec3& outMin, glm::vec3& outMax) + //{ + // Object* currentOwner = body->GetOwner(); + // glm::vec3 position = currentOwner->GetPosition(); + // glm::vec3 velocity = body->GetVelocity(); + + // glm::vec3 halfExtent(0.5f); + // const auto& polyhedron = body->GetCollidePolyhedron(); + + // // Determine half-extents for Box or Sphere + // if (body->GetColliderType() == ColliderType3D::BOX && polyhedron.size() >= 7) + // { + // halfExtent = (polyhedron[6] - polyhedron[0]) * 0.5f; + // } + // else if (body->GetColliderType() == ColliderType3D::SPHERE) + // { + // float radius = body->GetSphereRadius() / 2.0f; + // if (radius > 0.0f) + // { + // halfExtent = glm::vec3(radius); + // } + // } + + // outMin = position - halfExtent; + // outMax = position + halfExtent; + + // // If using CCD, expand the AABB to include the entire movement path + // if (body->GetCollisionDetectionMode() == CollisionDetectionMode::CONTINUOUS) + // { + // glm::vec3 displacement = velocity * dt; + // outMin = glm::min(outMin, outMin + displacement); + // outMax = glm::max(outMax, outMax + displacement); + // } + //}; + + //// Check for overlaps in 3D space (XYZ) + //for (size_t i = 0; i < bodies3D.size(); ++i) + //{ + // glm::vec3 minA, maxA; + // getAabb3D(bodies3D[i], minA, maxA); + + // for (size_t j = i + 1; j < bodies3D.size(); ++j) + // { + // glm::vec3 minB, maxB; + // getAabb3D(bodies3D[j], minB, maxB); + + // if (maxA.x < minB.x || maxB.x < minA.x) + // { + // continue; + // } + // if (maxA.y < minB.y || maxB.y < minA.y) + // { + // continue; + // } + // if (maxA.z < minB.z || maxB.z < minA.z) + // { + // continue; + // } + + // pairs.push_back({ bodies3D[i], bodies3D[j] }); + // } + //} + //return pairs; + + std::vector collisionPairs; + + if (rootNodeIndex == -1) + { + return collisionPairs; + } - // Helper lambda to calculate 3D AABB with support for Continuous Collision Detection (CCD) - auto getAabb3D = [dt](Physics3D* body, glm::vec3& outMin, glm::vec3& outMax) + // Incrementally update the Dynamic Bounding Volume Hierarchy tree to reflect any movement since the last frame. This spatial partitioning approach minimizes costly tree rebuilds. + for (Physics3D* body : bodies3D) { - Object* currentOwner = body->GetOwner(); - glm::vec3 position = currentOwner->GetPosition(); - glm::vec3 velocity = body->GetVelocity(); + if (bodyNodeMap.find(body) == bodyNodeMap.end()) + { + continue; + } - glm::vec3 halfExtent(0.5f); - const auto& polyhedron = body->GetCollidePolyhedron(); + int leafIndex = bodyNodeMap[body]; + AABB currentTightAABB = ComputeAABB(body); - // Determine half-extents for Box or Sphere - if (body->GetColliderType() == ColliderType3D::BOX && polyhedron.size() >= 7) + // Account for Continuous Collision Detection displacement by expanding the tight bounding box to cover the entire swept path for the current frame. + if (body->GetCollisionDetectionMode() == CollisionDetectionMode::CONTINUOUS) { - halfExtent = (polyhedron[6] - polyhedron[0]) * 0.5f; + glm::vec3 displacement = body->GetVelocity() * dt; + currentTightAABB.minExtent = glm::min(currentTightAABB.minExtent, currentTightAABB.minExtent + displacement); + currentTightAABB.maxExtent = glm::max(currentTightAABB.maxExtent, currentTightAABB.maxExtent + displacement); } - else if (body->GetColliderType() == ColliderType3D::SPHERE) + + // If the current tight bounding box has moved completely outside the cached fat bounding box, the object's position in the tree must be updated. + if (bvhNodes[leafIndex].boundingBox.Contains(currentTightAABB) == false) { - float radius = body->GetSphereRadius() / 2.0f; - if (radius > 0.0f) - { - halfExtent = glm::vec3(radius); - } - } + // Remove the old leaf from the tree + RemoveLeaf(leafIndex); - outMin = position - halfExtent; - outMax = position + halfExtent; + // Create a new fat bounding box that predicts the movement direction using the current velocity to reduce the frequency of tree updates. + glm::vec3 margin(0.2f, 0.2f, 0.2f); + glm::vec3 velocityMultiplier = body->GetVelocity() * 2.0f * dt; // Predict future position - // If using CCD, expand the AABB to include the entire movement path - if (body->GetCollisionDetectionMode() == CollisionDetectionMode::CONTINUOUS) - { - glm::vec3 displacement = velocity * dt; - outMin = glm::min(outMin, outMin + displacement); - outMax = glm::max(outMax, outMax + displacement); + AABB newFatAABB; + newFatAABB.minExtent = currentTightAABB.minExtent - margin + glm::min(glm::vec3(0.0f), velocityMultiplier); + newFatAABB.maxExtent = currentTightAABB.maxExtent + margin + glm::max(glm::vec3(0.0f), velocityMultiplier); + + bvhNodes[leafIndex].boundingBox = newFatAABB; + + // Re-insert the leaf node into the tree at the optimal new location based on the Surface Area Heuristic cost. + InsertLeaf(leafIndex); } - }; + } - // Check for overlaps in 3D space (XYZ) - for (size_t i = 0; i < bodies3D.size(); ++i) + // Query the updated Bounding Volume Hierarchy tree to find all potential collision pairs with logarithmic time complexity. + for (Physics3D* body : bodies3D) { - glm::vec3 minA, maxA; - getAabb3D(bodies3D[i], minA, maxA); - - for (size_t j = i + 1; j < bodies3D.size(); ++j) + if (bodyNodeMap.find(body) == bodyNodeMap.end()) { - glm::vec3 minB, maxB; - getAabb3D(bodies3D[j], minB, maxB); + continue; + } - if (maxA.x < minB.x || maxB.x < minA.x) - { - continue; - } - if (maxA.y < minB.y || maxB.y < minA.y) - { - continue; - } - if (maxA.z < minB.z || maxB.z < minA.z) - { - continue; - } + // We use the Fat AABB for querying to ensure we don't miss high-speed objects + int leafIndex = bodyNodeMap[body]; + const AABB& queryAABB = bvhNodes[leafIndex].boundingBox; - pairs.push_back({ bodies3D[i], bodies3D[j] }); - } + QueryTree(rootNodeIndex, body, queryAABB, collisionPairs); } - return pairs; + + return collisionPairs; } void PhysicsManager::NarrowPhase3D(std::vector& pairs, float dt) @@ -409,7 +562,7 @@ void PhysicsManager::NarrowPhase3D(std::vector& pairs, float dt ColliderType3D typeA = bodyA->GetColliderType(); ColliderType3D typeB = bodyB->GetColliderType(); - // Resolve continuous collision sub-steps if enabled + // Resolve continuous collision sub-steps if continuous detection mode is enabled for the body to prevent tunneling through geometry. if (bodyA->GetCollisionDetectionMode() == CollisionDetectionMode::CONTINUOUS) { SolveContinuous(bodyA, dt); @@ -419,7 +572,7 @@ void PhysicsManager::NarrowPhase3D(std::vector& pairs, float dt SolveContinuous(bodyB, dt); } - // Discrete collision dispatch + // Dispatch standard discrete collision logic based on the geometric shape type combination of the two colliding bodies. if (typeA == ColliderType3D::BOX && typeB == ColliderType3D::BOX) { bodyA->CollisionPP(bodyA->GetOwner(), bodyB->GetOwner(), currentMode); @@ -444,27 +597,27 @@ void PhysicsManager::SolveContinuous(Physics3D* body, float dt) float remainingTime = dt; int iterationCount = 0; - // User-defined or engine constants for safety + // User-defined or engine constants for safety to prevent infinite loops during continuous collision resolution. const int maxIterations = 5; const float skinWidth = 0.005f; - // Sub-stepping loop for CCD resolution + // Sub-stepping loop for continuous collision resolution. The remaining frame time is consumed incrementally as collisions are handled. while (remainingTime > 0.0f && iterationCount < maxIterations) { CollisionResult collisionResult = body->FindClosestCollision(remainingTime); - // If no hit occurred within this frame slice + // If no impact occurred within this frame slice, the object can safely move the full remaining distance. if (collisionResult.hasCollided == false || collisionResult.timeOfImpact > 1.0f) { body->GetOwner()->SetPosition(body->GetOwner()->GetPosition() + body->GetVelocity() * remainingTime); break; } - // Move the object precisely to the point of impact + // Move the object precisely to the point of impact, subtracting a small skin width to prevent it from getting mathematically stuck inside the other object. float moveFraction = (collisionResult.timeOfImpact > skinWidth) ? (collisionResult.timeOfImpact - skinWidth) : 0.0f; body->GetOwner()->SetPosition(body->GetOwner()->GetPosition() + body->GetVelocity() * remainingTime * moveFraction); - // Resolve velocity impulse at the contact point + // Resolve the velocity impulse at the contact point using linear momentum exchange and rotational effects. Physics3D* otherBody = collisionResult.otherObject->GetComponent(); if (otherBody != nullptr) { @@ -472,8 +625,597 @@ void PhysicsManager::SolveContinuous(Physics3D* body, float dt) body->CalculateLinearVelocity(*body, *otherBody, collisionResult.collisionNormal, nullptr, contactPoint); } - // Subtract the consumed time and loop again to handle potential secondary collisions + // Subtract the consumed time and loop again to handle potential secondary collisions that might occur during the rest of the frame. remainingTime -= remainingTime * moveFraction; iterationCount++; } -} \ No newline at end of file +} + +std::vector PhysicsManager::GetPotentialColliders(Physics3D* queryBody, float dt) +{ + std::vector potentialColliders; + + // If the tree is empty, there is nothing to collide with + if (rootNodeIndex == -1) + { + return potentialColliders; + } + + // Calculate the swept AABB for the moving body's full trajectory this frame + AABB sweptAABB = ComputeAABB(queryBody); + glm::vec3 displacement = queryBody->GetVelocity() * dt; + + sweptAABB.minExtent = glm::min(sweptAABB.minExtent, sweptAABB.minExtent + displacement); + sweptAABB.maxExtent = glm::max(sweptAABB.maxExtent, sweptAABB.maxExtent + displacement); + + // Reuse the existing QueryTree logic to find overlapping Fat AABBs + std::vector outPairs; + QueryTree(rootNodeIndex, queryBody, sweptAABB, outPairs); + + // Extract only the other bodies from the resulting pairs + for (const CollisionPair3D& pair : outPairs) + { + // Because of how QueryTree is written, pair.bodyB is the found object + potentialColliders.push_back(pair.bodyB); + } + + return potentialColliders; +} + +int PhysicsManager::AllocateNode() +{ + // Expand the pool if the free list is empty + if (freeListIndex == -1) + { + int oldCapacity = static_cast(bvhNodes.size()); + int newCapacity = oldCapacity * 2; + bvhNodes.resize(newCapacity); + + for (int i = oldCapacity; i < newCapacity - 1; ++i) + { + bvhNodes[i].nextFreeNodeIndex = i + 1; + } + bvhNodes[newCapacity - 1].nextFreeNodeIndex = -1; + freeListIndex = oldCapacity; + } + + // Pop a node from the free list + int allocatedIndex = freeListIndex; + freeListIndex = bvhNodes[allocatedIndex].nextFreeNodeIndex; + + // Reset node state + bvhNodes[allocatedIndex].parentIndex = -1; + bvhNodes[allocatedIndex].leftChildIndex = -1; + bvhNodes[allocatedIndex].rightChildIndex = -1; + bvhNodes[allocatedIndex].physicsBody = nullptr; + + return allocatedIndex; +} + +void PhysicsManager::FreeNode(int nodeIndex) +{ + // Push the node back to the free list + bvhNodes[nodeIndex].nextFreeNodeIndex = freeListIndex; + freeListIndex = nodeIndex; +} + +AABB PhysicsManager::ComputeAABB(Physics3D* body) +{ + AABB resultAabb; + Object* currentOwner = body->GetOwner(); + glm::vec3 position = currentOwner->GetPosition(); + + if (body->GetColliderType() == ColliderType3D::BOX) + { + const auto& polyhedron = body->GetCollidePolyhedron(); + + if (polyhedron.empty()) + { + resultAabb.minExtent = position - glm::vec3(0.5f); + resultAabb.maxExtent = position + glm::vec3(0.5f); + return resultAabb; + } + + glm::quat orient = body->GetEnableRotationalPhysics() ? body->GetOrientation() : glm::quat(-glm::radians(currentOwner->GetRotate3D())); + glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * glm::mat4_cast(orient); + + glm::vec3 minExtent(FLT_MAX); + glm::vec3 maxExtent(-FLT_MAX); + + for (const auto& vertex : polyhedron) + { + glm::vec3 worldVertex = glm::vec3(transform * glm::vec4(vertex, 1.0f)); + minExtent = glm::min(minExtent, worldVertex); + maxExtent = glm::max(maxExtent, worldVertex); + } + + resultAabb.minExtent = minExtent; + resultAabb.maxExtent = maxExtent; + } + else if (body->GetColliderType() == ColliderType3D::SPHERE) + { + float radius = body->GetSphereRadius() / 2.0f; + + if (radius > 0.0f) + { + resultAabb.minExtent = position - glm::vec3(radius); + resultAabb.maxExtent = position + glm::vec3(radius); + } + else + { + resultAabb.minExtent = position - glm::vec3(0.5f); + resultAabb.maxExtent = position + glm::vec3(0.5f); + } + } + else + { + resultAabb.minExtent = position - glm::vec3(0.5f); + resultAabb.maxExtent = position + glm::vec3(0.5f); + } + + return resultAabb; +} + +void PhysicsManager::InsertLeaf(int leafNodeIndex) +{ + if (rootNodeIndex == -1) + { + rootNodeIndex = leafNodeIndex; + return; + } + + // Find the best sibling for the new leaf using Surface Area Heuristic cost calculations. + AABB leafAABB = bvhNodes[leafNodeIndex].boundingBox; + int currentIndex = rootNodeIndex; + + while (bvhNodes[currentIndex].IsLeaf() == false) + { + int leftChild = bvhNodes[currentIndex].leftChildIndex; + int rightChild = bvhNodes[currentIndex].rightChildIndex; + + float area = bvhNodes[currentIndex].boundingBox.GetSurfaceArea(); + + AABB combinedAABB; + combinedAABB.Merge(bvhNodes[currentIndex].boundingBox, leafAABB); + float combinedArea = combinedAABB.GetSurfaceArea(); + + // Cost of creating a new parent for this node and the new leaf + float costCreation = 2.0f * combinedArea; + + // Minimum cost of pushing the leaf further down the tree + float costInheritance = 2.0f * (combinedArea - area); + + // Cost of descending into left child + float costLeft; + AABB leftCombinedAABB; + leftCombinedAABB.Merge(leafAABB, bvhNodes[leftChild].boundingBox); + if (bvhNodes[leftChild].IsLeaf()) + { + costLeft = leftCombinedAABB.GetSurfaceArea() + costInheritance; + } + else + { + float oldArea = bvhNodes[leftChild].boundingBox.GetSurfaceArea(); + float newArea = leftCombinedAABB.GetSurfaceArea(); + costLeft = (newArea - oldArea) + costInheritance; + } + + // Cost of descending into right child + float costRight; + AABB rightCombinedAABB; + rightCombinedAABB.Merge(leafAABB, bvhNodes[rightChild].boundingBox); + if (bvhNodes[rightChild].IsLeaf()) + { + costRight = rightCombinedAABB.GetSurfaceArea() + costInheritance; + } + else + { + float oldArea = bvhNodes[rightChild].boundingBox.GetSurfaceArea(); + float newArea = rightCombinedAABB.GetSurfaceArea(); + costRight = (newArea - oldArea) + costInheritance; + } + + // Descend according to the minimum cost + if (costCreation < costLeft && costCreation < costRight) + { + break; + } + + if (costLeft < costRight) + { + currentIndex = leftChild; + } + else + { + currentIndex = rightChild; + } + } + + int bestSiblingIndex = currentIndex; + + // Create a new parent node for the merged hierarchy branch + int oldParentIndex = bvhNodes[bestSiblingIndex].parentIndex; + int newParentIndex = AllocateNode(); + bvhNodes[newParentIndex].parentIndex = oldParentIndex; + bvhNodes[newParentIndex].boundingBox.Merge(leafAABB, bvhNodes[bestSiblingIndex].boundingBox); + + if (oldParentIndex != -1) + { + // The sibling was not the root + if (bvhNodes[oldParentIndex].leftChildIndex == bestSiblingIndex) + { + bvhNodes[oldParentIndex].leftChildIndex = newParentIndex; + } + else + { + bvhNodes[oldParentIndex].rightChildIndex = newParentIndex; + } + } + else + { + // The sibling was the root + rootNodeIndex = newParentIndex; + } + + bvhNodes[newParentIndex].leftChildIndex = bestSiblingIndex; + bvhNodes[newParentIndex].rightChildIndex = leafNodeIndex; + bvhNodes[bestSiblingIndex].parentIndex = newParentIndex; + bvhNodes[leafNodeIndex].parentIndex = newParentIndex; + + // Walk back up the tree refitting AABBs to encapsulate their new children bounds + currentIndex = bvhNodes[leafNodeIndex].parentIndex; + while (currentIndex != -1) + { + int left = bvhNodes[currentIndex].leftChildIndex; + int right = bvhNodes[currentIndex].rightChildIndex; + + bvhNodes[currentIndex].boundingBox.Merge(bvhNodes[left].boundingBox, bvhNodes[right].boundingBox); + currentIndex = bvhNodes[currentIndex].parentIndex; + } +} + +void PhysicsManager::RemoveLeaf(int leafNodeIndex) +{ + if (leafNodeIndex == rootNodeIndex) + { + rootNodeIndex = -1; + return; + } + + int parentIndex = bvhNodes[leafNodeIndex].parentIndex; + int grandParentIndex = bvhNodes[parentIndex].parentIndex; + int siblingIndex; + + if (bvhNodes[parentIndex].leftChildIndex == leafNodeIndex) + { + siblingIndex = bvhNodes[parentIndex].rightChildIndex; + } + else + { + siblingIndex = bvhNodes[parentIndex].leftChildIndex; + } + + if (grandParentIndex != -1) + { + // Connect sibling to grand parent + if (bvhNodes[grandParentIndex].leftChildIndex == parentIndex) + { + bvhNodes[grandParentIndex].leftChildIndex = siblingIndex; + } + else + { + bvhNodes[grandParentIndex].rightChildIndex = siblingIndex; + } + bvhNodes[siblingIndex].parentIndex = grandParentIndex; + FreeNode(parentIndex); + + // Walk back up the tree refitting AABBs + int currentIndex = grandParentIndex; + while (currentIndex != -1) + { + int left = bvhNodes[currentIndex].leftChildIndex; + int right = bvhNodes[currentIndex].rightChildIndex; + + bvhNodes[currentIndex].boundingBox.Merge(bvhNodes[left].boundingBox, bvhNodes[right].boundingBox); + currentIndex = bvhNodes[currentIndex].parentIndex; + } + } + else + { + // The parent was the root + rootNodeIndex = siblingIndex; + bvhNodes[siblingIndex].parentIndex = -1; + FreeNode(parentIndex); + } +} + +void PhysicsManager::QueryTree(int nodeIndex, Physics3D* queryBody, const AABB& queryAABB, std::vector& outPairs) +{ + if (nodeIndex == -1) + { + return; + } + + // Skip if AABBs do not intersect + if (bvhNodes[nodeIndex].boundingBox.Intersects(queryAABB) == false) + { + return; + } + + if (bvhNodes[nodeIndex].IsLeaf()) + { + // Avoid self-collision and duplicate pairs (compare memory address) + Physics3D* foundBody = bvhNodes[nodeIndex].physicsBody; + if (queryBody != foundBody && queryBody < foundBody) + { + outPairs.push_back({ queryBody, foundBody }); + } + } + else + { + QueryTree(bvhNodes[nodeIndex].leftChildIndex, queryBody, queryAABB, outPairs); + QueryTree(bvhNodes[nodeIndex].rightChildIndex, queryBody, queryAABB, outPairs); + } +} + +int PhysicsManager::AllocateNode2D() +{ + if (freeListIndex2D == -1) + { + int oldCapacity = static_cast(bvhNodes2D.size()); + int newCapacity = oldCapacity * 2; + bvhNodes2D.resize(newCapacity); + + for (int i = oldCapacity; i < newCapacity - 1; ++i) + { + bvhNodes2D[i].nextFreeNodeIndex = i + 1; + } + bvhNodes2D[newCapacity - 1].nextFreeNodeIndex = -1; + freeListIndex2D = oldCapacity; + } + + int allocatedIndex = freeListIndex2D; + freeListIndex2D = bvhNodes2D[allocatedIndex].nextFreeNodeIndex; + + bvhNodes2D[allocatedIndex].parentIndex = -1; + bvhNodes2D[allocatedIndex].leftChildIndex = -1; + bvhNodes2D[allocatedIndex].rightChildIndex = -1; + bvhNodes2D[allocatedIndex].physicsBody = nullptr; + + return allocatedIndex; +} + +void PhysicsManager::FreeNode2D(int nodeIndex) +{ + bvhNodes2D[nodeIndex].nextFreeNodeIndex = freeListIndex2D; + freeListIndex2D = nodeIndex; +} + +AABB2D PhysicsManager::ComputeAABB2D(Physics2D* body) +{ + AABB2D resultAabb; + Object* currentOwner = body->GetOwner(); + glm::vec2 position = currentOwner->GetPosition(); + + // Default half extent + glm::vec2 halfExtent(0.5f); + const auto& polygon = body->GetCollidePolygon(); + + // Calculate extent based on collider type + if (body->GetCollideType() == CollideType::POLYGON && polygon.size() >= 3) + { + float minX = FLT_MAX, maxX = -FLT_MAX; + float minY = FLT_MAX, maxY = -FLT_MAX; + + for (const auto& vertex : polygon) + { + minX = std::min(minX, vertex.x); + maxX = std::max(maxX, vertex.x); + minY = std::min(minY, vertex.y); + maxY = std::max(maxY, vertex.y); + } + halfExtent = glm::vec2((maxX - minX) * 0.5f, (maxY - minY) * 0.5f); + } + else if (body->GetCollideType() == CollideType::CIRCLE) + { + float radius = body->GetCircleCollideRadius(); + halfExtent = glm::vec2(radius); + } + + resultAabb.minExtent = position - halfExtent; + resultAabb.maxExtent = position + halfExtent; + return resultAabb; +} + +void PhysicsManager::InsertLeaf2D(int leafNodeIndex) +{ + if (rootNodeIndex2D == -1) + { + rootNodeIndex2D = leafNodeIndex; + return; + } + + // Find the best sibling for the new leaf using Perimeter Heuristic, which is the 2D version of the Surface Area Heuristic + AABB2D leafAABB = bvhNodes2D[leafNodeIndex].boundingBox; + int currentIndex = rootNodeIndex2D; + + while (bvhNodes2D[currentIndex].IsLeaf() == false) + { + int leftChild = bvhNodes2D[currentIndex].leftChildIndex; + int rightChild = bvhNodes2D[currentIndex].rightChildIndex; + + float area = bvhNodes2D[currentIndex].boundingBox.GetPerimeter(); // Use perimeter instead of surface area for 2D calculations + + AABB2D combinedAABB; + combinedAABB.Merge(bvhNodes2D[currentIndex].boundingBox, leafAABB); + float combinedArea = combinedAABB.GetPerimeter(); + + float costCreation = 2.0f * combinedArea; + float costInheritance = 2.0f * (combinedArea - area); + + // Left child cost + float costLeft; + AABB2D leftCombinedAABB; + leftCombinedAABB.Merge(leafAABB, bvhNodes2D[leftChild].boundingBox); + if (bvhNodes2D[leftChild].IsLeaf()) + { + costLeft = leftCombinedAABB.GetPerimeter() + costInheritance; + } + else + { + float oldArea = bvhNodes2D[leftChild].boundingBox.GetPerimeter(); + float newArea = leftCombinedAABB.GetPerimeter(); + costLeft = (newArea - oldArea) + costInheritance; + } + + // Right child cost + float costRight; + AABB2D rightCombinedAABB; + rightCombinedAABB.Merge(leafAABB, bvhNodes2D[rightChild].boundingBox); + if (bvhNodes2D[rightChild].IsLeaf()) + { + costRight = rightCombinedAABB.GetPerimeter() + costInheritance; + } + else + { + float oldArea = bvhNodes2D[rightChild].boundingBox.GetPerimeter(); + float newArea = rightCombinedAABB.GetPerimeter(); + costRight = (newArea - oldArea) + costInheritance; + } + + if (costCreation < costLeft && costCreation < costRight) + { + break; + } + + if (costLeft < costRight) + { + currentIndex = leftChild; + } + else + { + currentIndex = rightChild; + } + } + + int bestSiblingIndex = currentIndex; + + // Create a new parent node for the merged 2D hierarchy branch + int oldParentIndex = bvhNodes2D[bestSiblingIndex].parentIndex; + int newParentIndex = AllocateNode2D(); + + bvhNodes2D[newParentIndex].parentIndex = oldParentIndex; + bvhNodes2D[newParentIndex].boundingBox.Merge(leafAABB, bvhNodes2D[bestSiblingIndex].boundingBox); + + if (oldParentIndex != -1) + { + if (bvhNodes2D[oldParentIndex].leftChildIndex == bestSiblingIndex) + { + bvhNodes2D[oldParentIndex].leftChildIndex = newParentIndex; + } + else + { + bvhNodes2D[oldParentIndex].rightChildIndex = newParentIndex; + } + } + else + { + rootNodeIndex2D = newParentIndex; + } + + bvhNodes2D[newParentIndex].leftChildIndex = bestSiblingIndex; + bvhNodes2D[newParentIndex].rightChildIndex = leafNodeIndex; + bvhNodes2D[bestSiblingIndex].parentIndex = newParentIndex; + bvhNodes2D[leafNodeIndex].parentIndex = newParentIndex; + + // Walk back up the tree refitting AABBs to match the new bounds of their children + currentIndex = bvhNodes2D[leafNodeIndex].parentIndex; + while (currentIndex != -1) + { + int left = bvhNodes2D[currentIndex].leftChildIndex; + int right = bvhNodes2D[currentIndex].rightChildIndex; + + bvhNodes2D[currentIndex].boundingBox.Merge(bvhNodes2D[left].boundingBox, bvhNodes2D[right].boundingBox); + currentIndex = bvhNodes2D[currentIndex].parentIndex; + } +} + +void PhysicsManager::RemoveLeaf2D(int leafNodeIndex) +{ + if (leafNodeIndex == rootNodeIndex2D) + { + rootNodeIndex2D = -1; + return; + } + + int parentIndex = bvhNodes2D[leafNodeIndex].parentIndex; + int grandParentIndex = bvhNodes2D[parentIndex].parentIndex; + int siblingIndex; + + if (bvhNodes2D[parentIndex].leftChildIndex == leafNodeIndex) + { + siblingIndex = bvhNodes2D[parentIndex].rightChildIndex; + } + else + { + siblingIndex = bvhNodes2D[parentIndex].leftChildIndex; + } + + if (grandParentIndex != -1) + { + if (bvhNodes2D[grandParentIndex].leftChildIndex == parentIndex) + { + bvhNodes2D[grandParentIndex].leftChildIndex = siblingIndex; + } + else + { + bvhNodes2D[grandParentIndex].rightChildIndex = siblingIndex; + } + bvhNodes2D[siblingIndex].parentIndex = grandParentIndex; + FreeNode2D(parentIndex); + + // Walk back up the tree refitting AABBs + int currentIndex = grandParentIndex; + while (currentIndex != -1) + { + int left = bvhNodes2D[currentIndex].leftChildIndex; + int right = bvhNodes2D[currentIndex].rightChildIndex; + + bvhNodes2D[currentIndex].boundingBox.Merge(bvhNodes2D[left].boundingBox, bvhNodes2D[right].boundingBox); + currentIndex = bvhNodes2D[currentIndex].parentIndex; + } + } + else + { + rootNodeIndex2D = siblingIndex; + bvhNodes2D[siblingIndex].parentIndex = -1; + FreeNode2D(parentIndex); + } +} + +void PhysicsManager::QueryTree2D(int nodeIndex, Physics2D* queryBody, const AABB2D& queryAABB, std::vector& outPairs) +{ + if (nodeIndex == -1) + { + return; + } + + if (bvhNodes2D[nodeIndex].boundingBox.Intersects(queryAABB) == false) + { + return; + } + + if (bvhNodes2D[nodeIndex].IsLeaf()) + { + Physics2D* foundBody = bvhNodes2D[nodeIndex].physicsBody; + if (queryBody != foundBody && queryBody < foundBody) + { + outPairs.push_back({ queryBody, foundBody }); + } + } + else + { + QueryTree2D(bvhNodes2D[nodeIndex].leftChildIndex, queryBody, queryAABB, outPairs); + QueryTree2D(bvhNodes2D[nodeIndex].rightChildIndex, queryBody, queryAABB, outPairs); + } +} diff --git a/Game/source/3DPhysicsDemo.cpp b/Game/source/3DPhysicsDemo.cpp index a86f7d1e..3bd389dd 100644 --- a/Game/source/3DPhysicsDemo.cpp +++ b/Game/source/3DPhysicsDemo.cpp @@ -166,14 +166,14 @@ void PhysicsDemo::Update(float dt) void PhysicsDemo::ImGuiDraw(float /*dt*/) { ImGui::Begin("Physics Control"); - /*if (mode == PhysicsMode::ThreeDimension) + if (mode == PhysicsMode::ThreeDimension) { - if (ImGui::Button("Switch to 2D")) + /*if (ImGui::Button("Switch to 2D")) { ClearScene(); mode = PhysicsMode::TwoDimension; Init(); - } + }*/ } else { @@ -183,7 +183,26 @@ void PhysicsDemo::ImGuiDraw(float /*dt*/) mode = PhysicsMode::ThreeDimension; Init(); } - }*/ + } + + + if (ImGui::Button("Spawn Object (Amount = 100)")) + { + if (mode == PhysicsMode::ThreeDimension) + { + for (int i = 0; i < 100; ++i) + { + Spawn3D(); + } + } + else + { + for (size_t i = 0; i < 30; i++) + { + Spawn2D(); + } + } + } if (ImGui::Button("Spawn Object")) { From 27cec9c1a44edbd7e9f27bd5e83ea54e94981237 Mon Sep 17 00:00:00 2001 From: Doyeong Lee Date: Sat, 25 Apr 2026 15:27:14 +0900 Subject: [PATCH 3/5] Multithreaded Skeletal Animation & Debug Normal Visualization Fixes Skeletal Animation Multithreading (Job System Integration) - Migrated skeletal animation bone transform calculations to the background using the Job System for parallel processing. - Separated heavy CPU-bound matrix calculations from GPU memory uploads to optimize the main thread's frame time. Render Infrastructure Enhancements (Command Queue Pattern) - Implemented QueueGPUCommand: Introduced a thread-safe mechanism to queue GPU-related tasks (e.g., OpenGL buffer updates) from worker threads, preventing API context affinity issues. - Implemented ProcessGPUCommands: Added a synchronization point on the main thread to flush and execute deferred GPU commands, ensuring stability in thread-dependent rendering APIs. Normal Vector Visualization Pipeline Fix (GL/VK/DX) - Fixed an issue where debug normal vectors on animated meshes remained frozen in T-pose by passing bone IDs and weights into the debug rendering pipeline. - Updated the Normal3D shader to perform skeletal skinning computations identical to the main 3D shader, ensuring normal lines accurately follow the animated geometry. - Resolved a Vulkan-specific bug where the vertex uniform dynamic offset was overwritten by the fragment offset, causing incorrect bone matrices to be bound and distorting specific joints. - Fixed a DirectX 12 state creation error by explicitly mapping the BLENDINDICES and BLENDWEIGHTS attributes to the debug normal pipeline layout. --- Engine/include/BasicComponents/Physics3D.hpp | 25 ++-- .../BasicComponents/SkeletalAnimator.hpp | 5 +- Engine/include/Engine.hpp | 3 + Engine/include/Material.hpp | 2 + Engine/include/RenderManager.hpp | 27 ++++ Engine/include/SkeletalAnimationManager.hpp | 28 ++++ Engine/shaders/glsl/Normal3D.vert | 33 ++++- Engine/shaders/hlsl/Normal3D.frag.hlsl | 4 +- Engine/shaders/hlsl/Normal3D.vert.hlsl | 138 +++++++++++++++++- Engine/shaders/slang/Normal3D.slang | 52 ++++++- Engine/shaders/spirv/Normal3D.vert.spv | Bin 1364 -> 4648 bytes .../BasicComponents/SkeletalAnimator.cpp | 88 +++++++---- Engine/source/DXForwardRenderContext.cpp | 14 +- Engine/source/GLRenderManager.cpp | 9 ++ Engine/source/GameStateManager.cpp | 21 ++- Engine/source/ObjectManager.cpp | 13 -- Engine/source/PhysicsManager.cpp | 26 ---- Engine/source/RenderManager.cpp | 85 ++++++++++- Engine/source/SkeletalAnimationManager.cpp | 53 +++++++ Engine/source/VKRenderManager.cpp | 51 ++++++- 20 files changed, 559 insertions(+), 118 deletions(-) create mode 100644 Engine/include/SkeletalAnimationManager.hpp create mode 100644 Engine/source/SkeletalAnimationManager.cpp diff --git a/Engine/include/BasicComponents/Physics3D.hpp b/Engine/include/BasicComponents/Physics3D.hpp index 6a2dffc9..0af70f9d 100644 --- a/Engine/include/BasicComponents/Physics3D.hpp +++ b/Engine/include/BasicComponents/Physics3D.hpp @@ -75,7 +75,7 @@ class Physics3D : public IComponent Physics3D() : IComponent(ComponentTypes::PHYSICS3D) {}; ~Physics3D() override; - void Init() override ; + void Init() override; void Update(float dt) override; void UpdatePhysics(float dt); void End() override {}; @@ -117,16 +117,16 @@ class Physics3D : public IComponent void Teleport(glm::vec3 newPosition); void SetFriction(float f) { friction = f; } - void SetGravity(float g, bool isGravityOnParam = true) - { - gravity = g; - isGravityOn = isGravityOnParam; - if (isGravityOnParam) Awake(); + void SetGravity(float g, bool isGravityOnParam = true) + { + gravity = g; + isGravityOn = isGravityOnParam; + if (isGravityOnParam) Awake(); } - void SetIsGravityOn(bool state) - { - isGravityOn = state; - if (state) Awake(); + void SetIsGravityOn(bool state) + { + isGravityOn = state; + if (state) Awake(); } void SetMass(float m); void SetRestitution(float amount) { restitution = amount; } @@ -203,14 +203,14 @@ class Physics3D : public IComponent float momentOfInertia = 1.0f; float inverseInertia = 1.0f; - ColliderType3D colliderType = ColliderType3D::BOX; + ColliderType3D colliderType = ColliderType3D::BOX; BodyType3D bodyType = BodyType3D::RIGID; CollisionDetectionMode collisionMode = CollisionDetectionMode::DISCRETE; glm::vec3 ComputePolygonCenter(const std::vector& points); // Continuous collision detection mode for preventing tunneling at high speeds bool SweptSpheres(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult); - + // Gilbert-Johnson-Keerthi distance algorithm and Expanding Polytope Algorithm for convex collision resolution // universal support function that handles all shape types glm::vec3 GetShapeSupportPoint(const GjkShape& shape, glm::vec3 searchDirection); @@ -244,4 +244,5 @@ class Physics3D : public IComponent //bool SweptSphereVsOBB(Physics3D* boxBody, float dt, CollisionResult& outResult); //======== Legacy: SAT ========// + //@TODO: Create Capsule collider type }; \ No newline at end of file diff --git a/Engine/include/BasicComponents/SkeletalAnimator.hpp b/Engine/include/BasicComponents/SkeletalAnimator.hpp index c49329b0..7538a914 100644 --- a/Engine/include/BasicComponents/SkeletalAnimator.hpp +++ b/Engine/include/BasicComponents/SkeletalAnimator.hpp @@ -34,9 +34,12 @@ class SkeletalAnimator : public IComponent { public: SkeletalAnimator(); + ~SkeletalAnimator() override; void Init() override; - void Update(float dt) override; + void Update(float dt) override; // Wrapper: calls UpdateBoneTransforms + QueueGPUBoneUpload + void UpdateBoneTransforms(float dt); // CPU only: time advance, bone transform calculation + void QueueGPUBoneUpload(); // Queues GPU buffer upload to main thread void End() override; // Animation Control diff --git a/Engine/include/Engine.hpp b/Engine/include/Engine.hpp index d8d8f781..37a8b985 100644 --- a/Engine/include/Engine.hpp +++ b/Engine/include/Engine.hpp @@ -15,6 +15,7 @@ #include "Logger.hpp" #include "Particle/ParticleManager.hpp" #include "PhysicsManager.hpp" +#include "SkeletalAnimationManager.hpp" class Engine { @@ -37,6 +38,7 @@ class Engine static Logger& GetLogger() { return *Instance().logger; } static JobSystem& GetJobSystem() { return Instance().jobSystem; } static const InputSnapshot& GetInputSnapshot() { return Instance().inputSnapshot; } + static SkeletalAnimationManager& GetSkeletalAnimationManager() { return Instance().skeletalAnimationManager; } void Init(const char* title, int windowWidth, int windowHeight, bool fullScreen, WindowMode mode); void Update(); @@ -65,5 +67,6 @@ class Engine PhysicsManager physicsManager; JobSystem jobSystem; InputSnapshot inputSnapshot; + SkeletalAnimationManager skeletalAnimationManager; Logger* logger; }; \ No newline at end of file diff --git a/Engine/include/Material.hpp b/Engine/include/Material.hpp index ef4d432e..4fc82cfb 100644 --- a/Engine/include/Material.hpp +++ b/Engine/include/Material.hpp @@ -90,6 +90,8 @@ namespace ThreeDimension struct alignas(16) NormalVertex { glm::vec3 position; + int boneIDs[MAX_BONE_INFLUENCE]{ -1, -1, -1, -1 }; + float weights[MAX_BONE_INFLUENCE]{ 0.0f, 0.0f, 0.0f, 0.0f }; }; #endif diff --git a/Engine/include/RenderManager.hpp b/Engine/include/RenderManager.hpp index eca4b993..9ed3906a 100644 --- a/Engine/include/RenderManager.hpp +++ b/Engine/include/RenderManager.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "Window.hpp" #include "Utility.hpp" #include "Interface/ISprite.hpp" @@ -94,6 +96,27 @@ class RenderManager //} } + // Thread-safe: queue a GPU command to be executed on the main thread + void QueueGPUCommand(std::function command) + { + std::lock_guard lock(gpuCommandMutex); + gpuCommandQueue.push_back(std::move(command)); + } + + // Main thread only: execute all queued GPU commands and clear the queue + void ProcessGPUCommands() + { + std::vector> commands; + { + std::lock_guard lock(gpuCommandMutex); + commands.swap(gpuCommandQueue); + } + for (auto& cmd : commands) + { + cmd(); + } + } + void CreateMesh( std::vector& subMeshes, MeshType type, const std::filesystem::path& path, int stacks, int slices, @@ -211,6 +234,10 @@ class RenderManager // Deferred Deletion std::vector> functionQueue; + + // GPU Command Queue (written by worker threads, flushed on main thread) + std::vector> gpuCommandQueue; + std::mutex gpuCommandMutex; }; inline glm::mat4 aiMatrix4x4ToGlm(const aiMatrix4x4* mat) diff --git a/Engine/include/SkeletalAnimationManager.hpp b/Engine/include/SkeletalAnimationManager.hpp new file mode 100644 index 00000000..b5c74f67 --- /dev/null +++ b/Engine/include/SkeletalAnimationManager.hpp @@ -0,0 +1,28 @@ +//Author: DOYEONG LEE +//Project: CubeEngine +//File: SkeletalAnimationManager.hpp +#pragma once + +#include +#include + +class SkeletalAnimator; + +class SkeletalAnimationManager +{ +public: + SkeletalAnimationManager() = default; + ~SkeletalAnimationManager() = default; + + // Called from SkeletalAnimator::Init() / End() + void AddAnimator(SkeletalAnimator* animator); + void RemoveAnimator(SkeletalAnimator* animator); + + // Runs bone matrix calculation in parallel via JobSystem, + // then queues GPU upload for each animator + void Update(float dt); + +private: + std::vector animators; + std::mutex registryMutex; // Protects Register / Unregister +}; diff --git a/Engine/shaders/glsl/Normal3D.vert b/Engine/shaders/glsl/Normal3D.vert index 40616077..423f723b 100644 --- a/Engine/shaders/glsl/Normal3D.vert +++ b/Engine/shaders/glsl/Normal3D.vert @@ -8,6 +8,10 @@ layout(location = 0) in vec3 i_pos; +// Bone skinning attributes (same locations as main 3D.vert) +layout(location = 7) in ivec4 i_boneIds; +layout(location = 8) in vec4 i_weights; + struct vMatrix { mat4 model; @@ -15,9 +19,12 @@ struct vMatrix mat4 transposeInverseModel; mat4 view; mat4 projection; + mat4 decode; vec4 color; // @TODO move to push constants later vec3 viewPosition; + + mat4 finalBones[128]; }; #if VULKAN @@ -31,5 +38,29 @@ layout(std140, binding = 2) uniform vUniformMatrix void main() { - gl_Position = matrix.projection * matrix.view * matrix.model * vec4(i_pos, 1.0); + // Apply skeletal skinning if this vertex is influenced by bones + float totalWeight = i_weights[0] + i_weights[1] + i_weights[2] + i_weights[3]; + + vec4 skinnedPos; + if (totalWeight > 0.01) + { + skinnedPos = vec4(0.0); + float invTotal = 1.0 / totalWeight; + + for (int i = 0; i < 4; ++i) + { + if (i_boneIds[i] < 0 || i_boneIds[i] >= 128) continue; + if (i_weights[i] <= 0.0) continue; + + float w = i_weights[i] * invTotal; + skinnedPos += matrix.finalBones[i_boneIds[i]] * vec4(i_pos, 1.0) * w; + } + } + else + { + // Non-skinned vertex: use position directly + skinnedPos = vec4(i_pos, 1.0); + } + + gl_Position = matrix.projection * matrix.view * matrix.model * skinnedPos; } \ No newline at end of file diff --git a/Engine/shaders/hlsl/Normal3D.frag.hlsl b/Engine/shaders/hlsl/Normal3D.frag.hlsl index e4a38d5e..a6df0488 100644 --- a/Engine/shaders/hlsl/Normal3D.frag.hlsl +++ b/Engine/shaders/hlsl/Normal3D.frag.hlsl @@ -9,14 +9,14 @@ #endif -#line 8 "slang/Normal3D.slang" +#line 10 "slang/Normal3D.slang" struct VSOutput_0 { float4 position_0 : SV_POSITION; }; -#line 34 +#line 80 float4 fragmentMain(VSOutput_0 input_0) : SV_TARGET { return float4(1.0f, 1.0f, 1.0f, 1.0f); diff --git a/Engine/shaders/hlsl/Normal3D.vert.hlsl b/Engine/shaders/hlsl/Normal3D.vert.hlsl index 8b1a84cb..5c29cf29 100644 --- a/Engine/shaders/hlsl/Normal3D.vert.hlsl +++ b/Engine/shaders/hlsl/Normal3D.vert.hlsl @@ -9,18 +9,38 @@ #endif -#line 13 "slang/Normal3D.slang" +#line 15 "slang/Normal3D.slang" +struct vMatrix_0 +{ + float4x4 model_0; + float4x4 transposeInverseModel_0; + float4x4 view_0; + float4x4 projection_0; + float4x4 decode_0; + float4 color_0; + float4 viewPosition_0; + float4x4 finalBones_0[int(128)]; +}; + + + +cbuffer matrix_0 : register(b0) +{ + vMatrix_0 matrix_0; +} + +#line 31 struct PushConstants_0 { float4x4 modelToNDC_0; }; -cbuffer modelToNDC_1 : register(b0) +cbuffer modelToNDC_1 : register(b0, space1) { PushConstants_0 modelToNDC_1; } -#line 8 +#line 10 struct VSOutput_0 { float4 position_0 : SV_POSITION; @@ -31,15 +51,123 @@ struct VSOutput_0 struct VSInput_0 { float3 position_1 : POSITION0; + int4 boneIDs_0 : BLENDINDICES0; + float4 weights_0 : BLENDWEIGHTS0; }; -#line 24 +#line 42 VSOutput_0 vertexMain(VSInput_0 input_0) { + +#line 42 + VSInput_0 _S1 = input_0; + VSOutput_0 output_0; - output_0.position_0 = mul(modelToNDC_1.modelToNDC_0, float4(input_0.position_1, 1.0f)); + + float totalWeight_0 = input_0.weights_0[int(0)] + input_0.weights_0[int(1)] + input_0.weights_0[int(2)] + input_0.weights_0[int(3)]; + +#line 47 + float4 skinnedPos_0; + + + if(totalWeight_0 > 0.00999999977648258f) + { + float4 _S2 = float4(0.0f, 0.0f, 0.0f, 0.0f); + float _S3 = 1.0f / totalWeight_0; + +#line 53 + int i_0 = int(0); + +#line 53 + skinnedPos_0 = _S2; + + for(;;) + { + +#line 55 + if(i_0 < int(4)) + { + } + else + { + +#line 55 + break; + } + +#line 55 + int _S4 = i_0; + +#line 55 + bool _S5; + + if((_S1.boneIDs_0[i_0]) < int(0)) + { + +#line 57 + _S5 = true; + +#line 57 + } + else + { + +#line 57 + _S5 = (_S1.boneIDs_0[_S4]) >= int(128); + +#line 57 + } + +#line 57 + if(_S5) + { + +#line 58 + i_0 = i_0 + int(1); + +#line 55 + continue; + } + +#line 55 + int _S6 = i_0; + + + + if((_S1.weights_0[i_0]) <= 0.0f) + { + +#line 60 + i_0 = i_0 + int(1); + +#line 55 + continue; + } + +#line 55 + skinnedPos_0 = skinnedPos_0 + mul(matrix_0.finalBones_0[_S1.boneIDs_0[_S4]], float4(_S1.position_1, 1.0f)) * (_S1.weights_0[_S6] * _S3); + +#line 55 + i_0 = i_0 + int(1); + +#line 55 + } + +#line 50 + } + else + { + +#line 50 + skinnedPos_0 = float4(_S1.position_1, 1.0f); + +#line 50 + } + +#line 74 + output_0.position_0 = mul(modelToNDC_1.modelToNDC_0, skinnedPos_0); return output_0; } diff --git a/Engine/shaders/slang/Normal3D.slang b/Engine/shaders/slang/Normal3D.slang index e80dc9ff..0540ec20 100644 --- a/Engine/shaders/slang/Normal3D.slang +++ b/Engine/shaders/slang/Normal3D.slang @@ -2,7 +2,9 @@ struct VSInput { - float3 position : POSITION0; + [[vk::location(0)]] float3 position : POSITION0; + [[vk::location(7)]] int4 boneIDs : BLENDINDICES0; + [[vk::location(8)]] float4 weights : BLENDWEIGHTS0; } struct VSOutput @@ -10,12 +12,28 @@ struct VSOutput float4 position : SV_POSITION; }; +struct vMatrix +{ + float4x4 model; + float4x4 transposeInverseModel; + float4x4 view; + float4x4 projection; + float4x4 decode; + float4 color; + float4 viewPosition; + + // Bone matrices — same UBO as the main 3D shader (binding 0, set 0) + float4x4 finalBones[128]; +}; + +[[vk::binding(0, 0)]] ConstantBuffer matrix : register(b0, space0); + struct PushConstants { float4x4 modelToNDC; } #if defined(__hlsl__) -ConstantBuffer modelToNDC : register(b0, space0); +ConstantBuffer modelToNDC : register(b0, space1); #else [[vk::push_constant]] PushConstants modelToNDC; #endif @@ -25,7 +43,35 @@ VSOutput vertexMain(VSInput input) { VSOutput output; - output.position = mul(modelToNDC.modelToNDC, float4(input.position, 1.0)); + // Apply skeletal skinning if this vertex is influenced by bones + float totalWeight = input.weights[0] + input.weights[1] + input.weights[2] + input.weights[3]; + + float4 skinnedPos; + if (totalWeight > 0.01) + { + skinnedPos = float4(0.0, 0.0, 0.0, 0.0); + float weightNormalizationScale = 1.0 / totalWeight; + + for (int i = 0; i < 4; i++) + { + if (input.boneIDs[i] < 0 || input.boneIDs[i] >= 128) + continue; + if (input.weights[i] <= 0.0f) + continue; + + float normalizedWeight = input.weights[i] * weightNormalizationScale; + skinnedPos += mul(matrix.finalBones[input.boneIDs[i]], float4(input.position, 1.0f)) * normalizedWeight; + } + } + else + { + // Non-skinned vertex: use position directly + skinnedPos = float4(input.position, 1.0); + } + + // We still use the push constant modelToNDC for consistency with existing draw call + // Or we could use matrix.projection * matrix.view * matrix.model * skinnedPos + output.position = mul(modelToNDC.modelToNDC, skinnedPos); return output; } diff --git a/Engine/shaders/spirv/Normal3D.vert.spv b/Engine/shaders/spirv/Normal3D.vert.spv index a47f2fc5f5c9286ed5ab963dcf65921bd9638cb1..f43042e24a0f3bfa880f4d0924c1ffd0f19ddf4a 100644 GIT binary patch literal 4648 zcmbuC`EOKJ6vrPOX37?o9Tl-qk*%V@1W^`SsR5LlLRs9)&^};bIL z6%f%1?i=DRBJl?$!R2TF6W5sd{k(T>dsBZg@se-PIp2HkIp>~x?`z4_)Yk@?K|v5q z4}R89#>k*1fVnKlBsTix!O%pn(a)|zv5c`txIwr{$O{?$8!m}|R{yS$tZplo%VB40 zRQ#K%!wD6LOu`Hl!yi)(Kw#FgT%wm7O3Hizw-N>L%s@5;9?jY=(rtx=^Kcjo7* z2|2C6pV3)}3&?CRBv>Wcy1lw(X;iMpg>qbNcPm(vOXl(IiaNv6n&|H4rD?C${)?*( z4v8yAm99dmxF_tiLd8+*5y}5h>zusRia(>j*5SePvfG;~mBMa{lxureviU{Ht{d_V z`6YAaE=c!^FQb2bE;uZEN7^q7kov+il08c1bf4h3D{-YzuIlK*73Cdar5d)-hr}nF z_;BZT6vLeoNxxXf&Frp3Tf>g@yy-psV4*YYP=x-xS#cdvDXK{3g4)C{C!LtOn090O zW7leKQ?Xnq-L7k+%aK*oqvFy|q-S$M_Q1QH)EQRda92z6LY8SA?_-j{z~-%BlLMQ( zh&{yYHc4vZ?JQU3)sl?*RA@~mxi92mp1Z~9pC6pOOWq*^A9v2a&kLF(Pow#`v+VcD z^!1(?^p@e~9mn~>H42WSHn<*-%NX~q$8lCkJOy!UIjba|$5AI-tH;qlxDDdyBj+<& zU^sh9Y6he2dp23U%ZSsGV64`VtIq84vdIHSU3L0N4gfz+EBOM}3HT0^#CE>)8((ik+Yj*2SLwm z*yO(>vHSg(o86#_iL*?A^WoO66we+f>SvoI{&162F?SOVoqLE)jcX*S3l4jo+00Ln zU6CY?>)meY?BDH)n)eAk!VJyOtNjA84oK2x;*qI0s^}N4`Ty zZ0_`k0XE+yH_sg(<=JDTk2U)@MZYAS@t1UJ_(32aaExtu z&X2zt@MZ3ta*UQ-EWqy+)#-G{;bK`|^0=+=CY90G)6zE5jz#i#!qwt6Ph=D(A zpR~O+OUF0OmHth{TX$=hNoVZ@N!B8NRSnlj&JY;PUn$8v68kE%k<>p;lEFC=|7rn$ zBxl2Wy;I;WoV6a@W!y?hVq7c9pa;xhPZWrQBrkPgBeC6{kxh!bPIA4#d!P<-ua>+= zfajdrB=0p&BH`%meF8kgo#*}1;Tev5Ksp?E#&HiC$Ng~JL&k9z;K^T#jwh z;rtvW8#r)P#fP{IBFvX{;YL# z`v=V7Lrv81l)!rO*Gn?U4}ZM?M?9q4$J5f?UJporMi?Q$<1$^K-mwoS)-8alL}?_r7d+hTF#n(&79Z9~$ScJ!71o<0Emsg5x-|vjRNB<@i`S zoS)+p_$razN@N}xZ~PkcA`x%ATlePSQvadTft=RD{a`yrp3`%*gRLf_aI zd6}z7ekG8T*u=os{eC_teU{K9xNpVR(&1TmFi7vvdFk-nU+x}f{*AylX0Q2NFb-e$ z`x=gSJWZ0fFm-^^lg>NsQNFC?_X6BSfqcxtv3`am`HOMHloSpf0cD|X}ZMD*zvFaH!+q46Hk;_&wkU6V*AA8@95BKzVDiKL!!SZn|!PK(i0qD zYvSf!z_6(wAD$(x=cHixE5T=fMSe>wdB_V7HNTLW{di6MD_PV`%uD&y&3mDCeDZTI zaNrZqo8ru_W=R}#MZkW^gKJ-3dS|BOd!txtyCx+cy%R&7*QMYe6hn_U0;a`d)V3-G zM~I`3n^JHjV!R75>WbK{fKg|}8UdqbFgVtwaHMB90GetRotate3D(); glm::vec3 objScale = owner->GetSize(); - // Apply translation delta in local space - // Match the Engine's exact rotation convention (using negative Euler angles internally) glm::quat engineRotQuat = glm::quat(glm::radians(-objRotE)); if (glm::length(dt) > 0.000001f) @@ -152,13 +171,9 @@ void SkeletalAnimator::Update(float dt) owner->SetPosition(objPos + scaledAndRotatedTranslation); } - // Directly add delta rotation only if there is a noticeable rotation change - // This prevents glm::eulerAngles from constantly flipping axes in ImGui when dr is identity if (glm::abs(dr.w) < 0.999999f || glm::length(glm::vec3(dr.x, dr.y, dr.z)) > 0.000001f) { glm::quat newEngineRotQuat = engineRotQuat * dr; - - // Negate the extracted Euler angles to match the Engine's positive logic glm::vec3 newEulerAngles = -glm::degrees(glm::eulerAngles(newEngineRotQuat)); if (std::isnan(newEulerAngles.x)) newEulerAngles.x = objRotE.x; @@ -183,42 +198,53 @@ void SkeletalAnimator::Update(float dt) { if (finalBoneMatrices.size() < meshData->boneInfoMap.size()) finalBoneMatrices.resize(meshData->boneInfoMap.size(), glm::mat4(1.0f)); - + encodeMatrix = meshData->meshNormalizationTransform; } } } - // Calculate bone hierarchy transforms + // Calculate bone hierarchy transforms — result stored in finalBoneMatrices (CPU only) CalculateBoneTransform(¤tAnimation->GetRootNode(), glm::mat4(1.0f), encodeMatrix, false); - // Upload bone matrices to GPU - if (owner) + lastRootMotionTime = currentTime; +} + +void SkeletalAnimator::QueueGPUBoneUpload() +{ + if (!currentAnimation) return; + + Object* owner = GetOwner(); + if (!owner) return; + + DynamicSprite* sprite = owner->GetComponent(); + if (!sprite) return; + + for (auto& subMesh : sprite->GetSubMeshes()) { - DynamicSprite* sprite = owner->GetComponent(); - if (sprite) - { - for (auto& subMesh : sprite->GetSubMeshes()) - { - auto* meshData = subMesh->GetData(); - if (!meshData) continue; + auto* meshData = subMesh->GetData(); + if (!meshData) continue; - int count = (std::min)((int)finalBoneMatrices.size(), ThreeDimension::MAX_BONES); - for (int i = 0; i < count; ++i) - meshData->vertexUniform.finalBones[i] = finalBoneMatrices[i]; + // Copy bone matrices into CPU-side uniform struct (safe on worker thread) + int count = (std::min)((int)finalBoneMatrices.size(), ThreeDimension::MAX_BONES); + for (int i = 0; i < count; ++i) + { + meshData->vertexUniform.finalBones[i] = finalBoneMatrices[i]; + } - if (std::holds_alternative>>(meshData->vertexUniformBuffer)) - { - auto& glBuffer = std::get>>(meshData->vertexUniformBuffer); - glBuffer->UpdateUniform(sizeof(ThreeDimension::VertexUniform), &meshData->vertexUniform); - } + // Defer GPU upload — executed on main thread via FlushGPUCommands() + Engine::GetRenderManager()->QueueGPUCommand([meshData]() + { + if (std::holds_alternative>>(meshData->vertexUniformBuffer)) + { + auto& glBuffer = std::get>>(meshData->vertexUniformBuffer); + glBuffer->UpdateUniform(sizeof(ThreeDimension::VertexUniform), &meshData->vertexUniform); } - } + }); } - - lastRootMotionTime = currentTime; } + void SkeletalAnimator::PlayAnimation(SkeletalAnimation* newAnimation, bool isLoop, float speed, float blendDuration) { if (currentAnimation == newAnimation) return; diff --git a/Engine/source/DXForwardRenderContext.cpp b/Engine/source/DXForwardRenderContext.cpp index 172738e2..20305180 100644 --- a/Engine/source/DXForwardRenderContext.cpp +++ b/Engine/source/DXForwardRenderContext.cpp @@ -103,8 +103,9 @@ void DXForwardRenderContext::Initialize() #ifdef _DEBUG // Create root signature and pipeline for Normal 3D rootParameters.clear(); - rootParameters.resize(1, {}); - rootParameters[0].InitAsConstants(16, 0, 0, D3D12_SHADER_VISIBILITY_VERTEX); + rootParameters.resize(2, {}); + rootParameters[0].InitAsConstantBufferView(0, 0, D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC, D3D12_SHADER_VISIBILITY_VERTEX); + rootParameters[1].InitAsConstants(16, 0, 1, D3D12_SHADER_VISIBILITY_VERTEX); m_renderManager->CreateRootSignature(m_rootSignature3DNormal, rootParameters); DXHelper::ThrowIfFailed(m_rootSignature3DNormal->SetName(L"Normal 3D Root Signature")); @@ -112,9 +113,12 @@ void DXForwardRenderContext::Initialize() positionLayout.format = DXGI_FORMAT_R32G32B32_FLOAT; positionLayout.offset = offsetof(ThreeDimension::NormalVertex, position); + DXAttributeLayout boneIndexLayoutNormal{ "BLENDINDICES", 0, DXGI_FORMAT_R32G32B32A32_SINT, 0, offsetof(ThreeDimension::NormalVertex, boneIDs), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA }; + DXAttributeLayout weightLayoutNormal{ "BLENDWEIGHTS", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, offsetof(ThreeDimension::NormalVertex, weights), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA }; + m_pipeline3DNormal = DXPipeLineBuilder(m_renderManager->m_device, m_rootSignature3DNormal) .SetShaders("../Engine/shaders/hlsl/Normal3D.vert.hlsl", "../Engine/shaders/hlsl/Normal3D.frag.hlsl") - .SetLayout(std::initializer_list{ positionLayout }) + .SetLayout(std::initializer_list{ positionLayout, boneIndexLayoutNormal, weightLayoutNormal }) .SetRasterizer(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_BACK, true) .SetDepthStencil(true, true) .SetRenderTargets(rtvFormats) @@ -283,9 +287,11 @@ void DXForwardRenderContext::Execute(ICommandListWrapper* commandListWrapper) commandList->SetGraphicsRootSignature(m_rootSignature3DNormal.Get()); commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_LINELIST); + commandList->SetGraphicsRootConstantBufferView(0, spriteData->GetVertexUniformBuffer>()->GetGPUVirtualAddress(m_renderManager->m_frameIndex)); + auto& vertexUniform = spriteData->vertexUniform; glm::mat4 modelToNDC = vertexUniform.projection * vertexUniform.view * vertexUniform.model; - commandList->SetGraphicsRoot32BitConstants(0, 16, &modelToNDC, 0); + commandList->SetGraphicsRoot32BitConstants(1, 16, &modelToNDC, 0); D3D12_VERTEX_BUFFER_VIEW nvbv = buffer->normalVertexBuffer->GetView(); commandList->IASetVertexBuffers(0, 1, &nvbv); diff --git a/Engine/source/GLRenderManager.cpp b/Engine/source/GLRenderManager.cpp index 3474d82f..e648306a 100644 --- a/Engine/source/GLRenderManager.cpp +++ b/Engine/source/GLRenderManager.cpp @@ -35,6 +35,11 @@ void GLRenderManager::Initialize(SDL_Window* window_, SDL_GLContext context_) gl3DShader.LoadShader({ { GLShader::VERTEX, "../Engine/shaders/glsl/3D.vert" }, { GLShader::FRAGMENT, "../Engine/shaders/glsl/3D.frag" } }); #ifdef _DEBUG glNormal3DShader.LoadShader({ { GLShader::VERTEX, "../Engine/shaders/glsl/Normal3D.vert" }, { GLShader::FRAGMENT, "../Engine/shaders/glsl/Normal3D.frag" } }); + GLuint normalBlockIndex = glGetUniformBlockIndex(glNormal3DShader.GetProgramHandle(), "vUniformMatrix"); + if (normalBlockIndex != GL_INVALID_INDEX) + { + glUniformBlockBinding(glNormal3DShader.GetProgramHandle(), normalBlockIndex, 2); + } #endif //Lighting @@ -227,6 +232,9 @@ bool GLRenderManager::BeginRender(glm::vec3 bgColor) { glNormal3DShader.Use(true); + // Bind VertexUniform (binding 2) so Normal3D.vert can access finalBones[] + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 2, spriteData->GetVertexUniformBuffer>()->GetHandle())); + buffer->normalVertexArray->Use(true); GLsizei size = static_cast(spriteData->normalVertices.size()); glDrawArrays(GL_LINES, 0, size); @@ -234,6 +242,7 @@ bool GLRenderManager::BeginRender(glm::vec3 bgColor) glNormal3DShader.Use(false); } + #endif break; } diff --git a/Engine/source/GameStateManager.cpp b/Engine/source/GameStateManager.cpp index d949014c..34963d1a 100644 --- a/Engine/source/GameStateManager.cpp +++ b/Engine/source/GameStateManager.cpp @@ -172,12 +172,12 @@ void GameStateManager::UpdateGameLogic(float dt) } else { - // Phase 1: Main thread — level logic (uses InputSnapshot, may queue object operations) + // Main thread — level logic (may call PlayAnimation, queue object ops) levelList.at(static_cast(currentLevel))->Update(dt); - // Phase 2: Parallel jobs — independent system updates auto& js = Engine::GetJobSystem(); + // Parallel — object and particle updates auto objectsHandle = js.QueueWork([dt]() { Engine::GetObjectManager().Update(dt); @@ -188,26 +188,31 @@ void GameStateManager::UpdateGameLogic(float dt) Engine::GetParticleManager().Update(dt); }); - auto spriteHandle = js.QueueWork([dt]() + js.WaitForAll({ objectsHandle, particleHandle }); + + // Bone matrix calculation + auto animHandle = js.QueueWork([dt]() { - Engine::GetSpriteManager().Update(dt); + Engine::GetSkeletalAnimationManager().Update(dt); }); + js.WaitForWork(animHandle); - // Wait for object updates before physics (objects may modify velocities) - js.WaitForAll({ objectsHandle, particleHandle, spriteHandle }); + // GPU upload + Engine::GetRenderManager()->ProcessGPUCommands(); - // Phase 3: Physics runs after object updates to avoid race conditions + // Physics — after object updates (objects may modify velocities) auto physicsHandle = js.QueueWork([dt]() { Engine::GetPhysicsManager().Update(dt); }); js.WaitForWork(physicsHandle); - // Phase 4: Main thread — camera (uses InputManager directly for mouse mode) + // Main thread — camera (uses InputManager directly for mouse mode) Engine::GetCameraManager().Update(); } } + void GameStateManager::UpdateDraw(float dt) { if (!(SDL_GetWindowFlags(Engine::GetWindow().GetWindow()) & SDL_WINDOW_MINIMIZED)) diff --git a/Engine/source/ObjectManager.cpp b/Engine/source/ObjectManager.cpp index 25bfd886..b7a4b64e 100644 --- a/Engine/source/ObjectManager.cpp +++ b/Engine/source/ObjectManager.cpp @@ -1065,19 +1065,6 @@ void ObjectManager::RenderBoneHierarchy(const AssimpNodeData* node, const std::m glm::vec3 currentPos = glm::vec3(nodeWorldMatrix[3]); // Extract translation glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(currentPos, view, proj); - // Debug: Log bone positions - static bool debugPrint = true; - if (debugPrint && node->name.find("Armature") == std::string::npos) - { - char debugBuffer[256]; - snprintf(debugBuffer, sizeof(debugBuffer), - "Bone: %s | GlobalPos: (%.2f, %.2f, %.2f) | ScreenPos: (%.2f, %.2f)", - node->name.c_str(), - currentPos.x, currentPos.y, currentPos.z, - screenPos.x, screenPos.y); - Engine::GetLogger().LogDebug(LogCategory::Engine, debugBuffer); - } - // Draw joint point if (screenPos.x != -1 && screenPos.y != -1) { diff --git a/Engine/source/PhysicsManager.cpp b/Engine/source/PhysicsManager.cpp index 50e977a1..36e3c34c 100644 --- a/Engine/source/PhysicsManager.cpp +++ b/Engine/source/PhysicsManager.cpp @@ -41,12 +41,6 @@ CollisionMode PhysicsManager::GetCollisionMode(ObjectType typeA, ObjectType type void PhysicsManager::AddBody2D(Physics2D* body) { - //// Add the body if it's not already in the list - //if (std::find(bodies2D.begin(), bodies2D.end(), body) == bodies2D.end()) - //{ - // bodies2D.push_back(body); - //} - if (std::find(bodies2D.begin(), bodies2D.end(), body) == bodies2D.end()) { bodies2D.push_back(body); @@ -77,13 +71,6 @@ void PhysicsManager::AddBody2D(Physics2D* body) void PhysicsManager::RemoveBody2D(Physics2D* body) { - //// Search and erase the specified 2D body - //auto iterator = std::find(bodies2D.begin(), bodies2D.end(), body); - //if (iterator != bodies2D.end()) - //{ - // bodies2D.erase(iterator); - //} - auto iterator = std::find(bodies2D.begin(), bodies2D.end(), body); if (iterator != bodies2D.end()) { @@ -107,12 +94,6 @@ void PhysicsManager::RemoveBody2D(Physics2D* body) void PhysicsManager::AddBody3D(Physics3D* body) { - //// Add the body if it's not already in the list - //if (std::find(bodies3D.begin(), bodies3D.end(), body) == bodies3D.end()) - //{ - // bodies3D.push_back(body); - //} - if (std::find(bodies3D.begin(), bodies3D.end(), body) == bodies3D.end()) { bodies3D.push_back(body); @@ -147,13 +128,6 @@ void PhysicsManager::AddBody3D(Physics3D* body) void PhysicsManager::RemoveBody3D(Physics3D* body) { - //// Search and erase the specified 3D body - //auto iterator = std::find(bodies3D.begin(), bodies3D.end(), body); - //if (iterator != bodies3D.end()) - //{ - // bodies3D.erase(iterator); - //} - auto iterator = std::find(bodies3D.begin(), bodies3D.end(), body); if (iterator != bodies3D.end()) { diff --git a/Engine/source/RenderManager.cpp b/Engine/source/RenderManager.cpp index 7e818a64..01d6a73c 100644 --- a/Engine/source/RenderManager.cpp +++ b/Engine/source/RenderManager.cpp @@ -503,10 +503,26 @@ void RenderManager::CreateMesh( { #ifdef _DEBUG glm::vec3 start = it->position; - glm::vec3 end = it->position + it->normal * 0.1f; + glm::vec3 end = it->position + it->normal * 0.1f; - normalVertices.push_back(ThreeDimension::NormalVertex{ start }); - normalVertices.push_back(ThreeDimension::NormalVertex{ end }); + ThreeDimension::NormalVertex nvStart; + nvStart.position = start; + for (int b = 0; b < ThreeDimension::MAX_BONE_INFLUENCE; ++b) + { + nvStart.boneIDs[b] = it->boneIDs[b]; + nvStart.weights[b] = it->weights[b]; + } + + ThreeDimension::NormalVertex nvEnd; + nvEnd.position = end; + for (int b = 0; b < ThreeDimension::MAX_BONE_INFLUENCE; ++b) + { + nvEnd.boneIDs[b] = it->boneIDs[b]; + nvEnd.weights[b] = it->weights[b]; + } + + normalVertices.push_back(nvStart); + normalVertices.push_back(nvEnd); #endif } @@ -651,8 +667,27 @@ void RenderManager::CreateMesh( normal_position_layout.offset = 0; normal_position_layout.relative_offset = offsetof(ThreeDimension::NormalVertex, position); - buffer->normalVertexArray->AddVertexBuffer(std::move(*buffer->normalVertexBuffer), sizeof(ThreeDimension::NormalVertex), { normal_position_layout }); + GLAttributeLayout normal_bone_id_layout; + normal_bone_id_layout.component_type = GLAttributeLayout::Int; + normal_bone_id_layout.component_dimension = GLAttributeLayout::_4; + normal_bone_id_layout.normalized = false; + normal_bone_id_layout.vertex_layout_location = 7; + normal_bone_id_layout.stride = sizeof(ThreeDimension::NormalVertex); + normal_bone_id_layout.offset = 0; + normal_bone_id_layout.relative_offset = offsetof(ThreeDimension::NormalVertex, boneIDs); + + GLAttributeLayout normal_weight_layout; + normal_weight_layout.component_type = GLAttributeLayout::Float; + normal_weight_layout.component_dimension = GLAttributeLayout::_4; + normal_weight_layout.normalized = false; + normal_weight_layout.vertex_layout_location = 8; + normal_weight_layout.stride = sizeof(ThreeDimension::NormalVertex); + normal_weight_layout.offset = 0; + normal_weight_layout.relative_offset = offsetof(ThreeDimension::NormalVertex, weights); + + buffer->normalVertexArray->AddVertexBuffer(std::move(*buffer->normalVertexBuffer), sizeof(ThreeDimension::NormalVertex), { normal_position_layout, normal_bone_id_layout, normal_weight_layout }); #endif + } } @@ -903,10 +938,26 @@ void RenderManager::ProcessMesh( #ifdef _DEBUG glm::vec3 start = it->position; - glm::vec3 end = it->position + it->normal * 0.1f; + glm::vec3 end = it->position + it->normal * 0.1f; + + ThreeDimension::NormalVertex nvStart; + nvStart.position = start; + for (int b = 0; b < ThreeDimension::MAX_BONE_INFLUENCE; ++b) + { + nvStart.boneIDs[b] = it->boneIDs[b]; + nvStart.weights[b] = it->weights[b]; + } + + ThreeDimension::NormalVertex nvEnd; + nvEnd.position = end; + for (int b = 0; b < ThreeDimension::MAX_BONE_INFLUENCE; ++b) + { + nvEnd.boneIDs[b] = it->boneIDs[b]; + nvEnd.weights[b] = it->weights[b]; + } - normalVertices.push_back(ThreeDimension::NormalVertex{ start }); - normalVertices.push_back(ThreeDimension::NormalVertex{ end }); + normalVertices.push_back(nvStart); + normalVertices.push_back(nvEnd); #endif } @@ -1060,7 +1111,25 @@ void RenderManager::ProcessMesh( normal_position_layout.offset = 0; normal_position_layout.relative_offset = offsetof(ThreeDimension::NormalVertex, position); - buffer->normalVertexArray->AddVertexBuffer(std::move(*buffer->normalVertexBuffer), sizeof(ThreeDimension::NormalVertex), { normal_position_layout }); + GLAttributeLayout normal_bone_id_layout; + normal_bone_id_layout.component_type = GLAttributeLayout::Int; + normal_bone_id_layout.component_dimension = GLAttributeLayout::_4; + normal_bone_id_layout.normalized = false; + normal_bone_id_layout.vertex_layout_location = 7; + normal_bone_id_layout.stride = sizeof(ThreeDimension::NormalVertex); + normal_bone_id_layout.offset = 0; + normal_bone_id_layout.relative_offset = offsetof(ThreeDimension::NormalVertex, boneIDs); + + GLAttributeLayout normal_weight_layout; + normal_weight_layout.component_type = GLAttributeLayout::Float; + normal_weight_layout.component_dimension = GLAttributeLayout::_4; + normal_weight_layout.normalized = false; + normal_weight_layout.vertex_layout_location = 8; + normal_weight_layout.stride = sizeof(ThreeDimension::NormalVertex); + normal_weight_layout.offset = 0; + normal_weight_layout.relative_offset = offsetof(ThreeDimension::NormalVertex, weights); + + buffer->normalVertexArray->AddVertexBuffer(std::move(*buffer->normalVertexBuffer), sizeof(ThreeDimension::NormalVertex), { normal_position_layout, normal_bone_id_layout, normal_weight_layout }); #endif } } diff --git a/Engine/source/SkeletalAnimationManager.cpp b/Engine/source/SkeletalAnimationManager.cpp new file mode 100644 index 00000000..b818287c --- /dev/null +++ b/Engine/source/SkeletalAnimationManager.cpp @@ -0,0 +1,53 @@ +//Author: DOYEONG LEE +//Project: CubeEngine +//File: SkeletalAnimationManager.cpp +#include "SkeletalAnimationManager.hpp" +#include "BasicComponents/SkeletalAnimator.hpp" +#include "Engine.hpp" + +#include + +void SkeletalAnimationManager::AddAnimator(SkeletalAnimator* animator) +{ + std::lock_guard lock(registryMutex); + if (std::find(animators.begin(), animators.end(), animator) == animators.end()) + { + animators.push_back(animator); + } +} + +void SkeletalAnimationManager::RemoveAnimator(SkeletalAnimator* animator) +{ + std::lock_guard lock(registryMutex); + auto it = std::find(animators.begin(), animators.end(), animator); + if (it != animators.end()) + { + animators.erase(it); + } +} + +void SkeletalAnimationManager::Update(float dt) +{ + if (animators.empty()) + { + return; + } + + // Each animator's bone calculation is independent — safe to parallelize + auto handle = Engine::GetJobSystem().QueueParallelWork( + static_cast(animators.size()), + [this, dt](uint32_t begin, uint32_t end) + { + for (uint32_t i = begin; i < end; ++i) + { + // CPU bone transform calculation + animators[i]->UpdateBoneTransforms(dt); + + // Queue GPU upload to be flushed on main thread + animators[i]->QueueGPUBoneUpload(); + } + }, + 1 // One animator per batch — each is independent + ); + Engine::GetJobSystem().WaitForWork(handle); +} diff --git a/Engine/source/VKRenderManager.cpp b/Engine/source/VKRenderManager.cpp index 50adb7e9..92831cdd 100644 --- a/Engine/source/VKRenderManager.cpp +++ b/Engine/source/VKRenderManager.cpp @@ -139,7 +139,10 @@ void VKRenderManager::Initialize(SDL_Window* window_) #ifdef _DEBUG // Initialize Descriptor3DNormal - vkDescriptor3DNormal = new VKDescriptor(vkInit, {}, {}); + VKDescriptorLayout normalVertexLayout; + normalVertexLayout.descriptorType = VKDescriptorLayout::UNIFORM_DYNAMIC; + normalVertexLayout.descriptorCount = 1; + vkDescriptor3DNormal = new VKDescriptor(vkInit, { normalVertexLayout }, {}); #endif vkShader2D = new VKShader(vkInit->GetDevice()); @@ -203,8 +206,18 @@ void VKRenderManager::Initialize(SDL_Window* window_) position_layout.format = VK_FORMAT_R32G32B32_SFLOAT; position_layout.offset = offsetof(ThreeDimension::NormalVertex, position); + VKAttributeLayout normal_boneId_layout; + normal_boneId_layout.vertex_layout_location = 7; + normal_boneId_layout.format = VK_FORMAT_R32G32B32A32_SINT; + normal_boneId_layout.offset = offsetof(ThreeDimension::NormalVertex, boneIDs); + + VKAttributeLayout normal_weight_layout; + normal_weight_layout.vertex_layout_location = 8; + normal_weight_layout.format = VK_FORMAT_R32G32B32A32_SFLOAT; + normal_weight_layout.offset = offsetof(ThreeDimension::NormalVertex, weights); + vkPipeline3DNormal = new VKPipeLine(vkInit->GetDevice(), vkDescriptor3DNormal->GetDescriptorSetLayout()); - vkPipeline3DNormal->InitPipeLine(vkNormal3DShader->GetVertexModule(), vkNormal3DShader->GetFragmentModule(), vkSwapChain->GetSwapChainImageExtent(), &vkRenderPass, sizeof(ThreeDimension::NormalVertex), { position_layout }, vkRenderTarget->GetMSAASamples(), VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_CULL_MODE_BACK_BIT, POLYGON_MODE::FILL, true, sizeof(glm::mat4), VK_SHADER_STAGE_VERTEX_BIT); + vkPipeline3DNormal->InitPipeLine(vkNormal3DShader->GetVertexModule(), vkNormal3DShader->GetFragmentModule(), vkSwapChain->GetSwapChainImageExtent(), &vkRenderPass, sizeof(ThreeDimension::NormalVertex), { position_layout, normal_boneId_layout, normal_weight_layout }, vkRenderTarget->GetMSAASamples(), VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_CULL_MODE_BACK_BIT, POLYGON_MODE::FILL, true, sizeof(glm::mat4), VK_SHADER_STAGE_VERTEX_BIT); #endif // Uniform @@ -820,6 +833,29 @@ bool VKRenderManager::BeginRender(glm::vec3 bgColor) vkUpdateDescriptorSets(*vkInit->GetDevice(), 1, &descriptorWrite, 0, nullptr); } } +#ifdef _DEBUG + if (m_normalVectorVisualization) + { + auto& vertexUniformBuffer = uniformBuffer3D.vertexUniformBuffer; + VkDescriptorSet* normalVertexDescriptorSet = &(*vkDescriptor3DNormal->GetVertexDescriptorSets())[frameIndex]; + { + VkDescriptorBufferInfo bufferInfo; + bufferInfo.buffer = (*vertexUniformBuffer->GetUniformBuffers())[frameIndex]; + bufferInfo.offset = 0; + bufferInfo.range = sizeof(ThreeDimension::VertexUniform); + + VkWriteDescriptorSet descriptorWrite{}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.dstSet = *normalVertexDescriptorSet; + descriptorWrite.dstBinding = 0; + descriptorWrite.descriptorCount = 1; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + descriptorWrite.pBufferInfo = &bufferInfo; + + vkUpdateDescriptorSets(*vkInit->GetDevice(), 1, &descriptorWrite, 0, nullptr); + } + } +#endif break; } @@ -1017,8 +1053,15 @@ bool VKRenderManager::BeginRender(glm::vec3 bgColor) //Dynamic Viewport & Scissor vkCmdSetViewport(*currentCommandBuffer, 0, 1, &viewport); vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); - //Change Primitive Topology - //vkCmdSetPrimitiveTopology(*currentCommandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + + // Bind Vertex DescriptorSet (for bone matrices) + // Recalculate the vertex uniform dynamic offset (dynamicOffset was overwritten by the fragment uniform calc above) + size_t normalAlignment = vkInit->GetMinUniformBufferOffsetAlignment(); + size_t normalUniformSize = sizeof(ThreeDimension::VertexUniform); + uint32_t normalDynamicOffset = static_cast(subMeshIndex * ((normalUniformSize + normalAlignment - 1) & ~(normalAlignment - 1))); + VkDescriptorSet* normalVertexDescriptorSet = &(*vkDescriptor3DNormal->GetVertexDescriptorSets())[frameIndex]; + vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DNormal->GetPipeLineLayout(), 0, 1, normalVertexDescriptorSet, 1, &normalDynamicOffset); + //Push Constant Model-To_NDC auto& vertexUniform = spriteData->vertexUniform; glm::mat4 modelToNDC = vertexUniform.projection * vertexUniform.view * vertexUniform.model; From a6324c7a3a50bb4dca634ee1bf78bdd93ba47e98 Mon Sep 17 00:00:00 2001 From: Doyeong Lee Date: Mon, 27 Apr 2026 07:16:52 +0900 Subject: [PATCH 4/5] Implementation of Multi-Camera System Multi-Camera Rendering System - Modified CameraManager to manage the lifecycle, activation states, and rendering priority (layering) of multiple cameras. - Implemented synchronized Viewport and Scissor Rectangle synchronization across DX12 (Forward, Deferred, Lighting, Skybox), OpenGL, and Vulkan backends. - Resolved a critical deferred rendering artifact where sub-viewport rendering caused G-Buffer UV misalignment; fixed by decoupling Viewport (Full Render Resolution) and Scissor Rect (Camera Sub-region) in lighting passes to align sampling coordinates. - Standardized cross-backend coordinate systems, specifically addressing Y-axis inversion between OpenGL and DX/VK to ensure uniform camera behavior across all APIs. - Integrated an ImGui-based visual debug system that renders 3D camera frustums, orientations (Pitch/Yaw), and field-of-view in real-time. - Optimized debug visualization (Physics colliders and skeletons) to support screen-space occlusion, ensuring debug lines are correctly clipped by overlapping camera viewports. - Ensured full compatibility with FidelityFX (FSR) by dynamically scaling sub-viewports based on the ratio between internal render and display resolutions. --- Engine/include/Camera.hpp | 23 +- Engine/include/CameraManager.hpp | 68 +-- Engine/include/DX2DRenderContext.hpp | 2 +- Engine/include/DXForwardRenderContext.hpp | 2 +- Engine/include/DXGBufferContext.hpp | 2 +- Engine/include/DXGlobalLightingContext.hpp | 2 +- Engine/include/DXLocalLightingContext.hpp | 2 +- Engine/include/DXNaiveLightingContext.hpp | 2 +- Engine/include/DXPostProcessContext.hpp | 2 +- Engine/include/DXShadowMapContext.hpp | 2 +- Engine/include/DXSkyboxRenderContext.hpp | 2 +- Engine/include/DXWorkGraphsContext.hpp | 2 +- Engine/include/Interface/IRenderContext.hpp | 4 +- Engine/include/Interface/ISprite.hpp | 19 +- .../include/Interface/IWorkGraphsContext.hpp | 2 +- Engine/include/RenderManager.hpp | 2 +- .../source/BasicComponents/DynamicSprite.cpp | 19 +- Engine/source/BasicComponents/ISprite.cpp | 118 +++++ .../source/BasicComponents/StaticSprite.cpp | 4 +- Engine/source/Camera.cpp | 15 +- Engine/source/CameraManager.cpp | 453 +++++++++++++++--- Engine/source/DX2DRenderContext.cpp | 44 +- Engine/source/DXForwardRenderContext.cpp | 31 +- Engine/source/DXGBufferContext.cpp | 33 +- Engine/source/DXGlobalLightingContext.cpp | 26 +- Engine/source/DXLocalLightingContext.cpp | 28 +- Engine/source/DXNaiveLightingContext.cpp | 26 +- Engine/source/DXPostProcessContext.cpp | 2 +- Engine/source/DXRenderManager.cpp | 83 +++- Engine/source/DXShadowMapContext.cpp | 50 +- Engine/source/DXSkyboxRenderContext.cpp | 26 +- Engine/source/DXWorkGraphsContext.cpp | 5 +- Engine/source/Engine.cpp | 1 - Engine/source/GLRenderManager.cpp | 261 +++++----- Engine/source/GameStateManager.cpp | 2 + Engine/source/InputManager.cpp | 4 +- Engine/source/ObjectManager.cpp | 94 +++- Engine/source/RenderManager.cpp | 19 +- Engine/source/VKRenderManager.cpp | 368 ++++++++------ Game/source/3DPhysicsDemo.cpp | 20 +- Game/source/Background.cpp | 26 +- Game/source/BeatEmUpDemo/BEUPlayer.cpp | 6 +- Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp | 16 +- .../BeatEmUpDemo/BeatEmUpDemoSystem.cpp | 4 +- Game/source/MultipleLights.cpp | 12 +- Game/source/PBR.cpp | 12 +- Game/source/PlatformDemo/PlatformDemo.cpp | 1 + .../PlatformDemo/PlatformDemoSystem.cpp | 4 +- Game/source/ProceduralMeshes.cpp | 12 +- Game/source/SkeletalAnimationDemo.cpp | 10 +- Game/source/VerticesDemo.cpp | 6 +- 51 files changed, 1392 insertions(+), 587 deletions(-) diff --git a/Engine/include/Camera.hpp b/Engine/include/Camera.hpp index 3b7276df..146cf041 100644 --- a/Engine/include/Camera.hpp +++ b/Engine/include/Camera.hpp @@ -7,6 +7,8 @@ #include "glm/glm.hpp" #include "Ray.hpp" +#include + enum class CameraMoveDir { FOWARD, @@ -32,6 +34,14 @@ enum CameraCenterMode NormalizedDeviceCoordinates }; +struct ViewportRect +{ + float x; + float y; + float width; + float height; +}; + class Camera { public: @@ -77,10 +87,13 @@ class Camera void SetYaw(float amount) noexcept { yaw = amount; } void SetBaseFov(float amount) noexcept { baseFov = amount; } void SetCameraSensitivity(float amount) noexcept { cameraSensitivity = amount; } - void UpdaetCameraDirectrion(glm::vec2 dir); + void UpdateCameraDirection(glm::vec2 dir); void SetIsThirdPersonViewMod(bool state) { isThirdPersonView = state; } void SetCameraDistance(float amount) noexcept { cameraDistance = amount; } void SetCameraOffset(glm::vec3 amount) noexcept { cameraOffset = amount; } + void SetViewport(float x, float y, float w, float h) { viewport = { x, y, w, h }; } + void SetName(const std::string& name_) { name = name_; } + void SetIsActive(bool state) { isActive = state; } float GetNear() { return nearClip; } float GetFar() { return farClip; } @@ -90,6 +103,10 @@ class Camera float GetIsThirdPersonView() { return isThirdPersonView; } float GetCameraDistance() { return cameraDistance; } glm::vec3 GetCameraOffset() { return cameraOffset; } + ViewportRect GetViewport() const { return viewport; } + std::string GetName() const { return name; } + bool GetIsActive() const { return isActive; } + glm::vec3 GetUpVector() const { return up; } glm::vec3 GetBackVector() const { return back; } @@ -127,4 +144,8 @@ class Camera float cameraSensitivity = 1.f; float cameraDistance = 5.0f; //In ThirdPersonView bool isThirdPersonView = false; //In ThirdPersonView + + ViewportRect viewport{ 0.0f, 0.0f, 1.0f, 1.0f }; + std::string name = "Camera"; + bool isActive = true; }; \ No newline at end of file diff --git a/Engine/include/CameraManager.hpp b/Engine/include/CameraManager.hpp index e15e676d..ef6c09a7 100644 --- a/Engine/include/CameraManager.hpp +++ b/Engine/include/CameraManager.hpp @@ -4,6 +4,9 @@ #pragma once #include "Camera.hpp" +#include +#include + class Object; class CameraManager { @@ -13,64 +16,27 @@ class CameraManager void Init(glm::vec2 viewSize, CameraType type = CameraType::TwoDimension, float zoom = 45.f, float angle = 0.f); void Update(); + void DrawCameraDebug(); void Reset(); - CameraType GetCameraType() const { return camera.GetCameraType(); } + void DeleteAllCameras(); - // 2D, 3D - void SetZoom(float zoom) noexcept { camera.SetZoom(zoom); } - void SetViewSize(int width, int height) noexcept { camera.SetViewSize(width, height); } - void SetTarget(glm::vec3 pos) { camera.SetTarget(pos); } - void SetCameraPosition(glm::vec3 pos) { camera.SetCameraPosition(pos); } + Camera* AddCamera(CameraType type, std::string name = ""); + void DeleteCamera(int index); + void SetMainCamera(int index); + int GetMainCameraIndex() const { return mainCameraIndex; } - float GetZoom() noexcept { return camera.GetZoom(); } - glm::vec2 GetViewSize() { return camera.GetViewSize(); } - glm::vec3 GetCenter() { return camera.GetCenter(); } - glm::vec3 GetCameraPosition() { return camera.GetCameraPosition(); } - glm::mat4 GetViewMatrix() { return camera.GetViewMatrix(); } - glm::mat4 GetProjectionMatrix() { return camera.GetProjectionMatrix(); } + Camera* GetCamera(int index = 0); + size_t GetCameraCount() const; void ControlCamera(float dt); - - //2D - float GetRotate2D() { return camera.GetRotate2D(); } - void SetRotate2D(float angle) noexcept { camera.Rotate2D(angle); } - - void SetCameraCenterMode(CameraCenterMode cameraCenterMode) noexcept { camera.SetCameraCenterMode(cameraCenterMode); } - bool IsInCamera(Object* object); //2D (TBD in 3D) - - //3D - void LookAt(glm::vec3 pos) { camera.LookAt(pos); } - - void MoveCameraPos(CameraMoveDir dir, float speed) { camera.MoveCameraPos(dir, speed); } - void UpdaetCameraDirectrion(glm::vec2 dir) { camera.UpdaetCameraDirectrion(dir); } - void SetCameraSensitivity(float amount) noexcept { camera.SetCameraSensitivity(amount); } - float GetCameraSensitivity() noexcept { return camera.GetCameraSensitivity(); } - - void SetNear(float amount) noexcept { camera.SetNear(amount); } - void SetFar(float amount) noexcept { camera.SetFar(amount); } - void SetPitch(float amount) noexcept { camera.SetPitch(amount); } - void SetYaw(float amount) noexcept { camera.SetYaw(amount); } - void SetBaseFov(float amount) noexcept { camera.SetBaseFov(amount); } - void SetIsThirdPersonViewMod(bool state) { camera.SetIsThirdPersonViewMod(state); } - void SetCameraDistance(float amount) noexcept { camera.SetCameraDistance(amount); } - void SetCameraOffset(glm::vec3 amount) noexcept { camera.SetCameraOffset(amount); } - - float GetNear() noexcept { return camera.GetNear(); } - float GetFar() noexcept { return camera.GetFar(); } - float GetPitch() noexcept { return camera.GetPitch(); } - float GetYaw() noexcept { return camera.GetYaw(); } - float GetBaseFov() { return camera.GetBaseFov(); } - float GetIsThirdPersonView() { return camera.GetIsThirdPersonView();} - float GetCameraDistance() { return camera.GetCameraDistance(); } - glm::vec3 GetCameraOffset() { return camera.GetCameraOffset(); } - - glm::vec3 GetUpVector() const { return camera.GetUpVector(); } - glm::vec3 GetBackVector() const { return camera.GetBackVector(); } - glm::vec3 GetRightVector() const { return camera.GetRightVector(); } + bool IsInCamera(Object* object, int index = 0); + bool IsScreenPointOccluded(glm::vec2 screenPos, int cameraIndex); - Ray CalculateRayFrom2DPosition(glm::vec2 pos) { return camera.CalculateRayFrom2DPosition(pos); } void CameraControllerImGui(); int currentObjIndex = 0; private: - Camera camera; + std::vector> cameras; + int mainCameraIndex = 0; + int selectedCameraIndex = 0; + int maxCameraIndex = 0; }; diff --git a/Engine/include/DX2DRenderContext.hpp b/Engine/include/DX2DRenderContext.hpp index 5c6b991b..7da569d1 100644 --- a/Engine/include/DX2DRenderContext.hpp +++ b/Engine/include/DX2DRenderContext.hpp @@ -16,7 +16,7 @@ class DX2DRenderContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; private: DXRenderManager* m_renderManager; diff --git a/Engine/include/DXForwardRenderContext.hpp b/Engine/include/DXForwardRenderContext.hpp index 448b08e3..4dd99a0a 100644 --- a/Engine/include/DXForwardRenderContext.hpp +++ b/Engine/include/DXForwardRenderContext.hpp @@ -17,7 +17,7 @@ class DXForwardRenderContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; private: DXRenderManager* m_renderManager; diff --git a/Engine/include/DXGBufferContext.hpp b/Engine/include/DXGBufferContext.hpp index f312e718..5c73ce70 100644 --- a/Engine/include/DXGBufferContext.hpp +++ b/Engine/include/DXGBufferContext.hpp @@ -39,7 +39,7 @@ class DXGBufferContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; ID3D12DescriptorHeap* GetSRVHeap() const { return m_srvHeap.Get(); } diff --git a/Engine/include/DXGlobalLightingContext.hpp b/Engine/include/DXGlobalLightingContext.hpp index 10afe6b4..7b7ab0dd 100644 --- a/Engine/include/DXGlobalLightingContext.hpp +++ b/Engine/include/DXGlobalLightingContext.hpp @@ -17,7 +17,7 @@ class DXGlobalLightingContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; private: DXRenderManager* m_renderManager; diff --git a/Engine/include/DXLocalLightingContext.hpp b/Engine/include/DXLocalLightingContext.hpp index 9ede0148..55005150 100644 --- a/Engine/include/DXLocalLightingContext.hpp +++ b/Engine/include/DXLocalLightingContext.hpp @@ -21,7 +21,7 @@ class DXLocalLightingContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; private: DXRenderManager* m_renderManager; diff --git a/Engine/include/DXNaiveLightingContext.hpp b/Engine/include/DXNaiveLightingContext.hpp index ab475a65..95b94955 100644 --- a/Engine/include/DXNaiveLightingContext.hpp +++ b/Engine/include/DXNaiveLightingContext.hpp @@ -17,7 +17,7 @@ class DXNaiveLightingContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; private: DXRenderManager* m_renderManager; diff --git a/Engine/include/DXPostProcessContext.hpp b/Engine/include/DXPostProcessContext.hpp index db7fb6d5..7a78485e 100644 --- a/Engine/include/DXPostProcessContext.hpp +++ b/Engine/include/DXPostProcessContext.hpp @@ -16,7 +16,7 @@ class DXPostProcessContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; void UpdateScalePreset(const FidelityFX::UpscaleEffect& effect, const FfxFsr1QualityMode& mode, const FidelityFX::CASScalePreset& preset) const; diff --git a/Engine/include/DXShadowMapContext.hpp b/Engine/include/DXShadowMapContext.hpp index abfbc05a..1fd1ad0e 100644 --- a/Engine/include/DXShadowMapContext.hpp +++ b/Engine/include/DXShadowMapContext.hpp @@ -20,7 +20,7 @@ class DXShadowMapContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; void SetEnabled(const bool enabled) { m_enabled = enabled; } diff --git a/Engine/include/DXSkyboxRenderContext.hpp b/Engine/include/DXSkyboxRenderContext.hpp index 34cd795d..621b8a7c 100644 --- a/Engine/include/DXSkyboxRenderContext.hpp +++ b/Engine/include/DXSkyboxRenderContext.hpp @@ -17,7 +17,7 @@ class DXSkyboxRenderContext : public IRenderContext void Initialize() override; void OnResize() override; - void Execute(ICommandListWrapper* commandListWrapper) override; + void Execute(ICommandListWrapper* commandListWrapper, class Camera* camera = nullptr) override; void CleanUp() override; void LoadSkybox(const std::filesystem::path& path); diff --git a/Engine/include/DXWorkGraphsContext.hpp b/Engine/include/DXWorkGraphsContext.hpp index 4b7149d6..e26d88c7 100644 --- a/Engine/include/DXWorkGraphsContext.hpp +++ b/Engine/include/DXWorkGraphsContext.hpp @@ -29,7 +29,7 @@ class DXWorkGraphsContext : public IWorkGraphsContext void CheckMeshNodesSupport() const; void InitializeWorkGraphs() override; - void ExecuteWorkGraphs() override; + void ExecuteWorkGraphs(class Camera* camera = nullptr) override; void PrintWorkGraphsResults() override; private: DXRenderManager* m_renderManager; diff --git a/Engine/include/Interface/IRenderContext.hpp b/Engine/include/Interface/IRenderContext.hpp index bfeeea0a..89eec1ac 100644 --- a/Engine/include/Interface/IRenderContext.hpp +++ b/Engine/include/Interface/IRenderContext.hpp @@ -4,6 +4,8 @@ #pragma once #include "Interface/ICommandListWrapper.hpp" +class Camera; + class IRenderContext { public: @@ -11,6 +13,6 @@ class IRenderContext virtual void Initialize() = 0; virtual void OnResize() = 0; - virtual void Execute(ICommandListWrapper* commandListWrapper) = 0; + virtual void Execute(ICommandListWrapper* commandListWrapper, Camera* camera = nullptr) = 0; virtual void CleanUp() = 0; }; diff --git a/Engine/include/Interface/ISprite.hpp b/Engine/include/Interface/ISprite.hpp index 1a9b9950..792c7e77 100644 --- a/Engine/include/Interface/ISprite.hpp +++ b/Engine/include/Interface/ISprite.hpp @@ -49,22 +49,23 @@ class ISprite : public IComponent int GetSlices() const { return slices; }; std::string GetTextureName() { return textureName; } bool GetIsTex() const { return isTex; } - glm::vec3 GetSpecularColor(int index = 0) { return subMeshes[index]->GetData()->material.specularColor; } - float GetShininess(int index = 0) { return subMeshes[index]->GetData()->material.shininess; } - float GetMetallic(int index = 0) { return subMeshes[index]->GetData()->material.metallic; } - float GetRoughness(int index = 0) { return subMeshes[index]->GetData()->material.roughness; } + glm::vec3 GetSpecularColor(int index = 0); + float GetShininess(int index = 0); + float GetMetallic(int index = 0); + float GetRoughness(int index = 0); + SpriteDrawType GetSpriteDrawType() const { return spriteDrawType; } // Buffer std::vector& GetSubMeshes() { return subMeshes; } //Setter void SetColor(glm::vec4 color); - void SetSpriteDrawType(SpriteDrawType type) { spriteDrawType = type; } + void SetSpriteDrawType(SpriteDrawType type); void ChangeTexture(std::string name); void SetIsTex(bool state); - void SetSpecularColor(glm::vec3 sColor, int index = 0) { subMeshes[index]->GetData()->material.specularColor = sColor; } - void SetShininess(float amount, int index = 0) { subMeshes[index]->GetData()->material.shininess = amount; } - void SetMetallic(float amount, int index = 0) { subMeshes[index]->GetData()->material.metallic = amount; } - void SetRoughness(float amount, int index = 0) { subMeshes[index]->GetData()->material.roughness = amount; } + void SetSpecularColor(glm::vec3 sColor, int index = 0); + void SetShininess(float amount, int index = 0); + void SetMetallic(float amount, int index = 0); + void SetRoughness(float amount, int index = 0); //For CompFuncQueue virtual void CreateQuad(glm::vec4 color_) = 0; diff --git a/Engine/include/Interface/IWorkGraphsContext.hpp b/Engine/include/Interface/IWorkGraphsContext.hpp index 8c543f68..bc195526 100644 --- a/Engine/include/Interface/IWorkGraphsContext.hpp +++ b/Engine/include/Interface/IWorkGraphsContext.hpp @@ -18,6 +18,6 @@ class IWorkGraphsContext ~IWorkGraphsContext() = default; virtual void InitializeWorkGraphs() = 0; - virtual void ExecuteWorkGraphs() = 0; + virtual void ExecuteWorkGraphs(class Camera* camera = nullptr) = 0; virtual void PrintWorkGraphsResults() = 0; }; diff --git a/Engine/include/RenderManager.hpp b/Engine/include/RenderManager.hpp index 9ed3906a..6bebe1c3 100644 --- a/Engine/include/RenderManager.hpp +++ b/Engine/include/RenderManager.hpp @@ -147,7 +147,7 @@ class RenderManager //static float CalculatePointLightRadius(const glm::vec3& lightColor, float intensity, float constant, float linear, float quadratic); // Helper function to world-to-screen transform for ImGui drawing - glm::vec2 WorldToScreen(glm::vec3 worldPos, const glm::mat4& view, const glm::mat4& proj); + glm::vec2 WorldToScreen(glm::vec3 worldPos, const glm::mat4& view, const glm::mat4& proj, class Camera* camera = nullptr); void RenderingControllerForImGui(); //Skybox diff --git a/Engine/source/BasicComponents/DynamicSprite.cpp b/Engine/source/BasicComponents/DynamicSprite.cpp index 2bd95055..cd112ad6 100644 --- a/Engine/source/BasicComponents/DynamicSprite.cpp +++ b/Engine/source/BasicComponents/DynamicSprite.cpp @@ -262,13 +262,13 @@ void DynamicSprite::UpdateView() case SpriteDrawType::TwoDimension: { auto& vertexUniform = subMesh->GetData()->vertexUniform; - vertexUniform.view = Engine::GetCameraManager().GetViewMatrix(); + vertexUniform.view = Engine::GetCameraManager().GetCamera()->GetViewMatrix(); break; } case SpriteDrawType::ThreeDimension: { auto& vertexUniform = subMesh->GetData()->vertexUniform; - vertexUniform.view = Engine::GetCameraManager().GetViewMatrix(); + vertexUniform.view = Engine::GetCameraManager().GetCamera()->GetViewMatrix(); // @TODO move to push constants later glm::mat4 inverseView = glm::inverse(vertexUniform.view); vertexUniform.viewPosition = glm::vec4( @@ -298,24 +298,27 @@ void DynamicSprite::UpdateProjection() case SpriteDrawType::TwoDimension: { auto& vertexUniform = subMesh->GetData()->vertexUniform; - vertexUniform.projection = Engine::GetCameraManager().GetProjectionMatrix(); + vertexUniform.projection = Engine::GetCameraManager().GetCamera()->GetProjectionMatrix(); break; } case SpriteDrawType::ThreeDimension: { auto& vertexUniform = subMesh->GetData()->vertexUniform; - vertexUniform.projection = Engine::GetCameraManager().GetProjectionMatrix(); + vertexUniform.projection = Engine::GetCameraManager().GetCamera()->GetProjectionMatrix(); break; } case SpriteDrawType::UI: { auto& vertexUniform = subMesh->GetData()->vertexUniform; - glm::vec2 cameraViewSize = Engine::GetCameraManager().GetViewSize(); - vertexUniform.projection = glm::ortho(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); + glm::vec2 cameraViewSize = Engine::GetCameraManager().GetCamera()->GetViewSize(); // Flip y-axis for Vulkan - if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::VK) + if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::GL) { - vertexUniform.projection[1][1] *= -1; + vertexUniform.projection = glm::orthoRH_NO(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); + } + else + { + vertexUniform.projection = glm::orthoRH_ZO(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); } break; } diff --git a/Engine/source/BasicComponents/ISprite.cpp b/Engine/source/BasicComponents/ISprite.cpp index e72e8c63..cf679a66 100644 --- a/Engine/source/BasicComponents/ISprite.cpp +++ b/Engine/source/BasicComponents/ISprite.cpp @@ -47,6 +47,8 @@ void ISprite::AddMesh3D(MeshType type, const std::filesystem::path& path, int st glm::vec4 ISprite::GetColor() { + if (subMeshes.empty()) return { 1.f, 1.f, 1.f, 1.f }; + if (spriteDrawType == SpriteDrawType::TwoDimension || spriteDrawType == SpriteDrawType::UI) { auto& vertexUniform = subMeshes[0]->GetData()->vertexUniform; @@ -63,6 +65,15 @@ glm::vec4 ISprite::GetColor() void ISprite::SetColor(glm::vec4 color) { + if (subMeshes.empty()) + { + Engine::GetObjectManager().QueueComponentFunction(this, [=](ISprite* sprite) + { + sprite->SetColor(color); + }); + return; + } + if (spriteDrawType == SpriteDrawType::TwoDimension || spriteDrawType == SpriteDrawType::UI) { auto& vertexUniform = subMeshes[0]->GetData()->vertexUniform; @@ -79,6 +90,15 @@ void ISprite::SetColor(glm::vec4 color) void ISprite::ChangeTexture(std::string name) { + if (subMeshes.empty()) + { + Engine::GetObjectManager().QueueComponentFunction(this, [=](ISprite* sprite) + { + sprite->ChangeTexture(name); + }); + return; + } + RenderManager* renderManager = Engine::Instance().GetRenderManager(); switch (renderManager->GetGraphicsMode()) { @@ -221,6 +241,15 @@ void ISprite::ChangeTexture(std::string name) void ISprite::SetIsTex(bool state) { + if (subMeshes.empty()) + { + Engine::GetObjectManager().QueueComponentFunction(this, [=](ISprite* sprite) + { + sprite->SetIsTex(state); + }); + return; + } + isTex = state; RenderManager* renderManager = Engine::Instance().GetRenderManager(); switch (renderManager->GetGraphicsMode()) @@ -280,3 +309,92 @@ void ISprite::SetIsTex(bool state) break; } } + +glm::vec3 ISprite::GetSpecularColor(int index) +{ + if (index < 0 || index >= static_cast(subMeshes.size())) return { 0.f, 0.f, 0.f }; + return subMeshes[index]->GetData()->material.specularColor; +} + +float ISprite::GetShininess(int index) +{ + if (index < 0 || index >= static_cast(subMeshes.size())) return 0.f; + return subMeshes[index]->GetData()->material.shininess; +} + +float ISprite::GetMetallic(int index) +{ + if (index < 0 || index >= static_cast(subMeshes.size())) return 0.f; + return subMeshes[index]->GetData()->material.metallic; +} + +float ISprite::GetRoughness(int index) +{ + if (index < 0 || index >= static_cast(subMeshes.size())) return 0.f; + return subMeshes[index]->GetData()->material.roughness; +} + +void ISprite::SetSpecularColor(glm::vec3 sColor, int index) +{ + if (index < 0 || index >= static_cast(subMeshes.size())) + { + Engine::GetObjectManager().QueueComponentFunction(this, [=](ISprite* sprite) + { + sprite->SetSpecularColor(sColor, index); + }); + return; + } + subMeshes[index]->GetData()->material.specularColor = sColor; +} + +void ISprite::SetShininess(float amount, int index) +{ + if (index < 0 || index >= static_cast(subMeshes.size())) + { + Engine::GetObjectManager().QueueComponentFunction(this, [=](ISprite* sprite) + { + sprite->SetShininess(amount, index); + }); + return; + } + subMeshes[index]->GetData()->material.shininess = amount; +} + +void ISprite::SetMetallic(float amount, int index) +{ + if (index < 0 || index >= static_cast(subMeshes.size())) + { + Engine::GetObjectManager().QueueComponentFunction(this, [=](ISprite* sprite) + { + sprite->SetMetallic(amount, index); + }); + return; + } + subMeshes[index]->GetData()->material.metallic = amount; +} + +void ISprite::SetRoughness(float amount, int index) +{ + if (index < 0 || index >= static_cast(subMeshes.size())) + { + Engine::GetObjectManager().QueueComponentFunction(this, [=](ISprite* sprite) + { + sprite->SetRoughness(amount, index); + }); + return; + } + subMeshes[index]->GetData()->material.roughness = amount; +} +void ISprite::SetSpriteDrawType(SpriteDrawType type) +{ + if (subMeshes.empty()) + { + Engine::GetObjectManager().QueueComponentFunction(this, [=](ISprite* sprite) + { + sprite->SetSpriteDrawType(type); + }); + return; + } + + spriteDrawType = type; +} diff --git a/Engine/source/BasicComponents/StaticSprite.cpp b/Engine/source/BasicComponents/StaticSprite.cpp index 42776e67..44b4ad5e 100644 --- a/Engine/source/BasicComponents/StaticSprite.cpp +++ b/Engine/source/BasicComponents/StaticSprite.cpp @@ -126,7 +126,7 @@ void StaticSprite::UpdateView() for (auto& subMesh : subMeshes) { auto& vertexUniform = subMesh->GetData()->vertexUniform; - vertexUniform.view = Engine::GetCameraManager().GetViewMatrix(); + vertexUniform.view = Engine::GetCameraManager().GetCamera()->GetViewMatrix(); // @TODO move to push constants later glm::mat4 inverseView = glm::inverse(vertexUniform.view); vertexUniform.viewPosition = glm::vec4( @@ -143,7 +143,7 @@ void StaticSprite::UpdateProjection() for (auto& subMesh : subMeshes) { auto& vertexUniform = subMesh->GetData()->vertexUniform; - vertexUniform.projection = Engine::GetCameraManager().GetProjectionMatrix(); + vertexUniform.projection = Engine::GetCameraManager().GetCamera()->GetProjectionMatrix(); } } diff --git a/Engine/source/Camera.cpp b/Engine/source/Camera.cpp index fd9a04a7..08e7a3b1 100644 --- a/Engine/source/Camera.cpp +++ b/Engine/source/Camera.cpp @@ -10,7 +10,7 @@ Camera::~Camera() { - Engine::GetLogger().LogDebug(LogCategory::Engine, "Camera Deleted"); + Engine::GetLogger().LogDebug(LogCategory::Engine, "Camera [" + name + "] Deleted"); } void Camera::Update() @@ -33,6 +33,7 @@ void Camera::Update() glm::rotate(glm::mat4(1.0f), glm::radians(rotate2D), glm::vec3(0.0f, 0.0f, 1.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(zoom, zoom, 1.0f)); } + switch (Engine::GetRenderManager()->GetGraphicsMode()) { case GraphicsMode::GL: @@ -53,6 +54,7 @@ void Camera::Update() glm::vec3 desiredPosition = cameraCenter + cameraOffset; direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); + switch (Engine::GetRenderManager()->GetGraphicsMode()) { case GraphicsMode::GL: @@ -65,6 +67,7 @@ void Camera::Update() direction.y = sin(glm::radians(-pitch)); break; } + direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); back = glm::normalize(direction); @@ -83,7 +86,6 @@ void Camera::Update() switch (Engine::GetRenderManager()->GetGraphicsMode()) { case GraphicsMode::GL: - if (isThirdPersonView == true) { view = glm::lookAt(cameraPosition, cameraCenter, up); @@ -95,8 +97,8 @@ void Camera::Update() } projection = glm::perspectiveRH_NO(glm::radians(baseFov / log2(zoom + 1.0f)), static_cast(wSize.x) / static_cast(wSize.y), nearClip, farClip); break; - case GraphicsMode::VK: + case GraphicsMode::VK: if (isThirdPersonView == true) { view = glm::lookAt({ cameraPosition.x, -cameraPosition.y, cameraPosition.z }, { cameraCenter.x, -cameraCenter.y, cameraCenter.z }, up); @@ -111,10 +113,9 @@ void Camera::Update() projection = glm::perspectiveRH_ZO(glm::radians(baseFov / log2(zoom + 1.0f)), wSize.x / wSize.y, nearClip, farClip); // Flip y-axis for Vulkan projection[1][1] *= -1.0f; - break; - case GraphicsMode::DX: + case GraphicsMode::DX: if (isThirdPersonView == true) { view = glm::lookAtRH({ cameraPosition.x, -cameraPosition.y, cameraPosition.z }, { cameraCenter.x, -cameraCenter.y, cameraCenter.z }, up); @@ -127,10 +128,10 @@ void Camera::Update() } //projection = glm::perspectiveRH_ZO(glm::radians(baseFov / log2(zoom + 1.0f)), static_cast(wSize.x) / static_cast(wSize.y), nearClip, farClip); projection = glm::perspectiveRH_ZO(glm::radians(baseFov / log2(zoom + 1.0f)), wSize.x / wSize.y, nearClip, farClip); - break; } break; + default: break; } @@ -225,7 +226,7 @@ void Camera::MoveCameraPos(CameraMoveDir dir, float speed) } } -void Camera::UpdaetCameraDirectrion(glm::vec2 dir) +void Camera::UpdateCameraDirection(glm::vec2 dir) { yaw += dir.x * cameraSensitivity; pitch += dir.y * cameraSensitivity; diff --git a/Engine/source/CameraManager.cpp b/Engine/source/CameraManager.cpp index a25c1844..f4535f83 100644 --- a/Engine/source/CameraManager.cpp +++ b/Engine/source/CameraManager.cpp @@ -9,12 +9,77 @@ CameraManager::~CameraManager() Engine::GetLogger().LogDebug(LogCategory::Engine, "Camera Manager Deleted"); } +Camera* CameraManager::AddCamera(CameraType type, std::string name) +{ + auto newCamera = std::make_unique(); + newCamera->SetCameraType(type); + + if (name.empty() == true) + { + name = "Camera " + std::to_string(maxCameraIndex); + } + maxCameraIndex++; + newCamera->SetName(name); + + glm::vec2 wSize = Engine::GetWindow().GetWindowSize(); + newCamera->SetViewSize(static_cast(wSize.x), static_cast(wSize.y)); + + cameras.push_back(std::move(newCamera)); + + return cameras.back().get(); +} + +void CameraManager::DeleteCamera(int index) +{ + if (index >= 0 && index < cameras.size()) + { + cameras.erase(cameras.begin() + index); + + if (mainCameraIndex >= cameras.size()) + { + mainCameraIndex = static_cast(cameras.size()) - 1; + } + + if (selectedCameraIndex >= cameras.size()) + { + selectedCameraIndex = static_cast(cameras.size()) - 1; + } + } +} + +void CameraManager::SetMainCamera(int index) +{ + if (index >= 0 && index < cameras.size()) + { + mainCameraIndex = index; + } +} + +Camera* CameraManager::GetCamera(int index) +{ + if (index >= 0 && index < cameras.size()) + { + return cameras[index].get(); + } + return nullptr; +} + +size_t CameraManager::GetCameraCount() const +{ + return cameras.size(); +} + void CameraManager::Init(glm::vec2 viewSize, CameraType type, float zoom, float angle) { - camera.SetCameraType(type); - camera.SetViewSize(static_cast(viewSize.x), static_cast(viewSize.y)); - camera.SetZoom(zoom); - camera.Rotate2D(angle); + AddCamera(type, "Main Camera"); + Camera* mainCam = GetCamera(0); + + if (mainCam != nullptr) + { + mainCam->SetViewSize(static_cast(viewSize.x), static_cast(viewSize.y)); + mainCam->SetZoom(zoom); + mainCam->Rotate2D(angle); + } switch (type) { @@ -30,21 +95,59 @@ void CameraManager::Init(glm::vec2 viewSize, CameraType type, float zoom, float void CameraManager::Update() { - camera.Update(); + for (auto& cam : cameras) + { + if (cam->GetIsActive() == true) + { + cam->Update(); + } + } } void CameraManager::Reset() { - camera.Reset(); + for (auto& cam : cameras) + { + cam->Reset(); + } + maxCameraIndex = 0; + mainCameraIndex = 0; + selectedCameraIndex = 0; Engine::GetLogger().LogDebug(LogCategory::Engine, "Camera Manager Reset"); } -bool CameraManager::IsInCamera(Object* object) +void CameraManager::DeleteAllCameras() +{ + if(cameras.empty() == true) + { + return; + } + + cameras.clear(); + maxCameraIndex = 0; + mainCameraIndex = 0; + selectedCameraIndex = 0; + Engine::GetLogger().LogDebug(LogCategory::Engine, "All Cameras Deleted"); +} + +bool CameraManager::IsInCamera(Object* object, int index) { + if (cameras.empty() == true) + { + return false; + } + + Camera* targetCam = GetCamera(index); + + if (targetCam == nullptr || targetCam->GetIsActive() == false) + { + return false; + } + glm::vec2 position = { object->GetPosition().x, object->GetPosition().y }; glm::vec2 size = { object->GetSize().x, object->GetSize().y }; - glm::vec2 viewSize = GetViewSize(); - glm::vec2 cameraCenter = GetCenter(); + glm::vec2 viewSize = targetCam->GetViewSize(); + glm::vec2 cameraCenter = targetCam->GetCenter(); if (position.x - (size.x) < (viewSize.x / 2.f + cameraCenter.x) && position.x + (size.x) > -(viewSize.x / 2.f - cameraCenter.x) && position.y - (size.y) < (viewSize.y / 2.f + cameraCenter.y) && position.y + (size.y) > -(viewSize.y / 2.f - cameraCenter.y)) @@ -54,11 +157,40 @@ bool CameraManager::IsInCamera(Object* object) return false; } +bool CameraManager::IsScreenPointOccluded(glm::vec2 screenPos, int cameraIndex) +{ + ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); + glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; + glm::vec2 windowSize = { imguiViewport->Size.x, imguiViewport->Size.y }; + + // Check all cameras drawn AFTER the specified camera index + for (int i = cameraIndex + 1; i < static_cast(cameras.size()); ++i) + { + if (cameras[i]->GetIsActive()) + { + ViewportRect vp = cameras[i]->GetViewport(); + + float left = vp.x * windowSize.x + windowPos.x; + float top = vp.y * windowSize.y + windowPos.y; + float right = (vp.x + vp.width) * windowSize.x + windowPos.x; + float bottom = (vp.y + vp.height) * windowSize.y + windowPos.y; + + if (screenPos.x >= left && screenPos.x <= right && + screenPos.y >= top && screenPos.y <= bottom) + { + return true; // Occluded by a later camera + } + } + } + return false; +} + void CameraManager::CameraControllerImGui() { if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::Q)) { SDL_Window* window = Engine::Instance().GetWindow().GetWindow(); + if (SDL_GetWindowRelativeMouseMode(window) == false) { Engine::Instance().GetInputManager().SetRelativeMouseMode(true); @@ -70,126 +202,297 @@ void CameraManager::CameraControllerImGui() } ImGui::Begin("CameraController"); - glm::vec3 position = GetCameraPosition(); - float zoom = GetZoom(); + if (cameras.empty() == false) + { + ImGui::SetNextItemWidth(150.0f); + if (ImGui::BeginCombo("##CameraList", cameras[selectedCameraIndex]->GetName().c_str())) + { + for (int i = 0; i < cameras.size(); i++) + { + bool isSelected = (selectedCameraIndex == i); + if (ImGui::Selectable(cameras[i]->GetName().c_str(), isSelected)) + { + selectedCameraIndex = i; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + } + + if (ImGui::Button("Add")) + { + if(Engine::GetRenderManager()->GetRenderType() == RenderType::ThreeDimension) + { + AddCamera(CameraType::ThreeDimension); + selectedCameraIndex = cameras.size() - 1; + } + else + { + AddCamera(CameraType::TwoDimension); + selectedCameraIndex = cameras.size() - 1; + } + } + + if (cameras.empty() == false && cameras.size() > 1) + { + ImGui::SameLine(); + if (ImGui::Button("Remove")) + { + DeleteCamera(selectedCameraIndex); + if (selectedCameraIndex > 0) + { + selectedCameraIndex = selectedCameraIndex - 1; + } + else if(selectedCameraIndex == 0) + { + selectedCameraIndex = 0; + } + } + } + + ImGui::Separator(); - if (GetCameraType() == CameraType::ThreeDimension) + if (cameras.empty() == false) { - float nearClip = GetNear(); - float farClip = GetFar(); - float pitch = GetPitch(); - float yaw = GetYaw(); + Camera* selectedCam = cameras[selectedCameraIndex].get(); - glm::vec3 cameraOffset = GetCameraOffset(); - float cameraDistance = GetCameraDistance(); - bool isThirdPersonView = GetIsThirdPersonView(); + bool isActive = selectedCam->GetIsActive(); + if (ImGui::Checkbox("Is Active", &isActive)) + { + selectedCam->SetIsActive(isActive); + } + + if (isActive == true) + { + ViewportRect v = selectedCam->GetViewport(); + float vArr[4] = { v.x, v.y, v.width, v.height }; + + if (ImGui::DragFloat4("Viewport (X,Y,W,H)", vArr, 0.01f, 0.0f, 1.0f)) + { + selectedCam->SetViewport(vArr[0], vArr[1], vArr[2], vArr[3]); + } - bool isRelativeOn = Engine::GetInputManager().GetRelativeMouseMode(); - ImGui::Checkbox("Relative Mouse Mod (Press Q)", &isRelativeOn); - Engine::GetInputManager().SetRelativeMouseMode(isRelativeOn); + glm::vec3 position = selectedCam->GetCameraPosition(); + float zoom = selectedCam->GetZoom(); - ImGui::Checkbox("Third Person View Mod", &isThirdPersonView); - SetIsThirdPersonViewMod(isThirdPersonView); + if (selectedCam->GetCameraType() == CameraType::ThreeDimension) + { + float nearClip = selectedCam->GetNear(); + float farClip = selectedCam->GetFar(); + float pitch = selectedCam->GetPitch(); + float yaw = selectedCam->GetYaw(); - float cameraSensitivity = GetCameraSensitivity(); - ImGui::SliderFloat("CameraSensitivity", &cameraSensitivity, 0.1f, 100.f); - SetCameraSensitivity(cameraSensitivity); + glm::vec3 cameraOffset = selectedCam->GetCameraOffset(); + float cameraDistance = selectedCam->GetCameraDistance(); + bool isThirdPersonView = selectedCam->GetIsThirdPersonView(); - ImGui::DragFloat3("Position", &position.x, 0.01f); - SetCameraPosition(position); + bool isRelativeOn = Engine::GetInputManager().GetRelativeMouseMode(); + ImGui::Checkbox("Relative Mouse Mod (Press Q)", &isRelativeOn); + Engine::GetInputManager().SetRelativeMouseMode(isRelativeOn); - ImGui::DragFloat("Zoom", &zoom, 0.5f); - SetZoom(zoom); + ImGui::Checkbox("Third Person View Mod", &isThirdPersonView); + selectedCam->SetIsThirdPersonViewMod(isThirdPersonView); - ImGui::DragFloat("Near", &nearClip, 0.05f); - SetNear(nearClip); + float cameraSensitivity = selectedCam->GetCameraSensitivity(); + ImGui::SliderFloat("CameraSensitivity", &cameraSensitivity, 0.1f, 100.f); + selectedCam->SetCameraSensitivity(cameraSensitivity); - ImGui::DragFloat("Far", &farClip, 0.05f); - SetFar(farClip); + ImGui::DragFloat3("Position", &position.x, 0.01f); + selectedCam->SetCameraPosition(position); - ImGui::DragFloat("Pitch", &pitch, 0.5f); - SetPitch(pitch); + ImGui::DragFloat("Zoom", &zoom, 0.5f); + selectedCam->SetZoom(zoom); - ImGui::DragFloat("Yaw", &yaw, 0.5f); - SetYaw(yaw); + ImGui::DragFloat("Near", &nearClip, 0.05f); + selectedCam->SetNear(nearClip); - if (isThirdPersonView == true && !Engine::GetObjectManager().GetObjectMap().empty()) - { - if (ImGui::CollapsingHeader("Third Person View Option", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::BeginChild("Scrolling", ImVec2(0, 100)); - int index = 0; - for (auto& object : Engine::GetObjectManager().GetObjectMap()) + ImGui::DragFloat("Far", &farClip, 0.05f); + selectedCam->SetFar(farClip); + + ImGui::DragFloat("Pitch", &pitch, 0.5f); + selectedCam->SetPitch(pitch); + + ImGui::DragFloat("Yaw", &yaw, 0.5f); + selectedCam->SetYaw(yaw); + + if (isThirdPersonView == true && !Engine::GetObjectManager().GetObjectMap().empty()) { - ImGui::PushStyleColor(ImGuiCol_Text, (currentObjIndex == index) ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f) : ImGui::GetStyleColorVec4(ImGuiCol_Text)); - if (ImGui::Selectable(object.second.get()->GetName().c_str(), index)) + if (ImGui::CollapsingHeader("Third Person View Option", ImGuiTreeNodeFlags_DefaultOpen)) { - currentObjIndex = index; + ImGui::BeginChild("Scrolling", ImVec2(0, 100)); + int index = 0; + + for (auto& object : Engine::GetObjectManager().GetObjectMap()) + { + ImGui::PushStyleColor(ImGuiCol_Text, (currentObjIndex == index) ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f) : ImGui::GetStyleColorVec4(ImGuiCol_Text)); + + if (ImGui::Selectable(object.second.get()->GetName().c_str(), index)) + { + currentObjIndex = index; + } + + ImGui::PopStyleColor(); + index++; + } + + ImGui::EndChild(); + selectedCam->SetTarget(Engine::GetObjectManager().FindObjectWithId(currentObjIndex)->GetPosition()); + + ImGui::DragFloat("Distance", &cameraDistance, 0.05f); + selectedCam->SetCameraDistance(cameraDistance); + + ImGui::DragFloat3("Offset", &cameraOffset.x, 0.01f); + selectedCam->SetCameraOffset(cameraOffset); } - ImGui::PopStyleColor(); - index++; } - ImGui::EndChild(); - SetTarget(Engine::GetObjectManager().FindObjectWithId(currentObjIndex)->GetPosition()); + } + else if (selectedCam->GetCameraType() == CameraType::TwoDimension) + { + float rotate2D = selectedCam->GetRotate2D(); + + ImGui::DragFloat2("Position", &position.x, 0.1f); + selectedCam->SetCameraPosition(position); - ImGui::DragFloat("Distance", &cameraDistance, 0.05f); - SetCameraDistance(cameraDistance); - ImGui::DragFloat3("Offset", &cameraOffset.x, 0.01f); - SetCameraOffset(cameraOffset); + ImGui::DragFloat("Zoom", &zoom, 0.1f); + selectedCam->SetZoom(zoom); + + ImGui::DragFloat("Rotation", &rotate2D, 0.5f); + selectedCam->Rotate2D(rotate2D); } } } - else if (GetCameraType() == CameraType::TwoDimension) + + DrawCameraDebug(); + + ImGui::End(); +} + +void CameraManager::DrawCameraDebug() +{ + Camera* mainCam = GetCamera(mainCameraIndex); + if (mainCam == nullptr) return; + + glm::mat4 mainView = mainCam->GetViewMatrix(); + glm::mat4 mainProj = mainCam->GetProjectionMatrix(); + ImDrawList* drawList = ImGui::GetBackgroundDrawList(); + + // Get Main Camera's Viewport for clipping + ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); + glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; + glm::vec2 windowSize = { imguiViewport->Size.x, imguiViewport->Size.y }; + ViewportRect mainVp = mainCam->GetViewport(); + + ImVec2 clipMin = { mainVp.x * windowSize.x + windowPos.x, mainVp.y * windowSize.y + windowPos.y }; + ImVec2 clipMax = { (mainVp.x + mainVp.width) * windowSize.x + windowPos.x, (mainVp.y + mainVp.height) * windowSize.y + windowPos.y }; + + drawList->PushClipRect(clipMin, clipMax); + + for (int i = 0; i < cameras.size(); ++i) { - float rotate2D = GetRotate2D(); + Camera* cam = cameras[i].get(); + if (cam == nullptr || cam->GetCameraType() != CameraType::ThreeDimension || i == mainCameraIndex) continue; - ImGui::DragFloat2("Position", &position.x, 0.1f); - SetCameraPosition(position); + // Color: Blue for active, Red for inactive + ImU32 color = cam->GetIsActive() ? IM_COL32(0, 120, 255, 255) : IM_COL32(255, 0, 0, 255); - ImGui::DragFloat("Zoom", &zoom, 0.1f); - SetZoom(zoom); + // 1. Draw camera position and name + glm::vec3 camPos = cam->GetCameraPosition(); + glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(camPos, mainView, mainProj, mainCam); - ImGui::DragFloat("Rotation", &rotate2D, 0.5f); - SetRotate2D(rotate2D); + if (screenPos.x >= clipMin.x && screenPos.x <= clipMax.x && + screenPos.y >= clipMin.y && screenPos.y <= clipMax.y) + { + if (!IsScreenPointOccluded(screenPos, mainCameraIndex)) + { + drawList->AddCircleFilled(ImVec2(screenPos.x, screenPos.y), 6.0f, color); + std::string label = cam->GetName(); + drawList->AddText(ImVec2(screenPos.x + 10.0f, screenPos.y - 10.0f), color, label.c_str()); + } + } + + // 2. Draw Frustum (Lines for Near, Far, FOV, Aspect) + glm::mat4 invVP = glm::inverse(cam->GetProjectionMatrix() * cam->GetViewMatrix()); + + // Z Range: GL is [-1, 1], others (DX/VK) are [0, 1] + float nearZ = (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::GL) ? -1.0f : 0.0f; + glm::vec4 ndc[8] = { + {-1,-1,nearZ,1}, {1,-1,nearZ,1}, {1,1,nearZ,1}, {-1,1,nearZ,1}, // Near + {-1,-1,1,1}, {1,-1,1,1}, {1,1,1,1}, {-1,1,1,1} // Far + }; + + glm::vec2 screenCorners[8]; + bool hasValidPoint = false; + for (int j = 0; j < 8; ++j) + { + glm::vec4 worldPos = invVP * ndc[j]; + glm::vec3 p = glm::vec3(worldPos) / worldPos.w; + screenCorners[j] = Engine::GetRenderManager()->WorldToScreen(p, mainView, mainProj, mainCam); + if (screenCorners[j].x != -1) hasValidPoint = true; + } + + if (hasValidPoint) + { + // Connect Near plane + for (int j = 0; j < 4; ++j) + drawList->AddLine(ImVec2(screenCorners[j].x, screenCorners[j].y), ImVec2(screenCorners[(j + 1) % 4].x, screenCorners[(j + 1) % 4].y), color, 1.5f); + + // Connect Far plane + for (int j = 0; j < 4; ++j) + drawList->AddLine(ImVec2(screenCorners[j + 4].x, screenCorners[j + 4].y), ImVec2(screenCorners[(j + 1) % 4 + 4].x, screenCorners[(j + 1) % 4 + 4].y), color, 1.5f); + + // Connect Near to Far + for (int j = 0; j < 4; ++j) + drawList->AddLine(ImVec2(screenCorners[j].x, screenCorners[j].y), ImVec2(screenCorners[j + 4].x, screenCorners[j + 4].y), color, 1.5f); + } } - ImGui::End(); + drawList->PopClipRect(); } void CameraManager::ControlCamera(float dt) { + Camera* selectedCam = GetCamera(selectedCameraIndex); + + if (selectedCam == nullptr || selectedCam->GetIsActive() == false) + { + return; + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::W)) { - MoveCameraPos(CameraMoveDir::FOWARD, 5.f * dt); + selectedCam->MoveCameraPos(CameraMoveDir::FOWARD, 5.f * dt); } if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::S)) { - MoveCameraPos(CameraMoveDir::BACKWARD, 5.f * dt); + selectedCam->MoveCameraPos(CameraMoveDir::BACKWARD, 5.f * dt); } if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::A)) { - MoveCameraPos(CameraMoveDir::LEFT, 5.f * dt); + selectedCam->MoveCameraPos(CameraMoveDir::LEFT, 5.f * dt); } if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::D)) { - MoveCameraPos(CameraMoveDir::RIGHT, 5.f * dt); + selectedCam->MoveCameraPos(CameraMoveDir::RIGHT, 5.f * dt); } if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::SPACE)) { - MoveCameraPos(CameraMoveDir::UP, 5.f * dt); + selectedCam->MoveCameraPos(CameraMoveDir::UP, 5.f * dt); } if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LSHIFT)) { - MoveCameraPos(CameraMoveDir::DOWN, 5.f * dt); + selectedCam->MoveCameraPos(CameraMoveDir::DOWN, 5.f * dt); } if (Engine::GetInputManager().GetMouseWheelMotion().y != 0.f) { - SetZoom(GetZoom() + Engine::GetInputManager().GetMouseWheelMotion().y); + selectedCam->SetZoom(selectedCam->GetZoom() + Engine::GetInputManager().GetMouseWheelMotion().y); } + SDL_Window* window = Engine::Instance().GetWindow().GetWindow(); + if (Engine::GetInputManager().IsMouseButtonPressed(MOUSEBUTTON::RIGHT) || SDL_GetWindowRelativeMouseMode(window) == true) { - UpdaetCameraDirectrion(Engine::Instance().GetInputManager().GetRelativeMouseState() * dt); + selectedCam->UpdateCameraDirection(Engine::Instance().GetInputManager().GetRelativeMouseState() * dt); } //TBD -} +} \ No newline at end of file diff --git a/Engine/source/DX2DRenderContext.cpp b/Engine/source/DX2DRenderContext.cpp index ac4c4596..d08e42d3 100644 --- a/Engine/source/DX2DRenderContext.cpp +++ b/Engine/source/DX2DRenderContext.cpp @@ -38,7 +38,7 @@ void DX2DRenderContext::OnResize() } -void DX2DRenderContext::Execute(ICommandListWrapper* commandListWrapper) +void DX2DRenderContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { DXCommandListWrapper* dxCommandListWrapper = dynamic_cast(commandListWrapper); ID3D12GraphicsCommandList10* commandList = dxCommandListWrapper->GetDXCommandList(); @@ -53,10 +53,31 @@ void DX2DRenderContext::Execute(ICommandListWrapper* commandListWrapper) // Set the viewport and scissor rect // @TODO This is weird but FidelityFX class takes care of viewport size (display size, render size) - uint32_t renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); - uint32_t renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); - D3D12_VIEWPORT viewport = { 0.f, 0.f, static_cast(renderWidth), static_cast(renderHeight), 0.f, 1.f }; - D3D12_RECT scissorRect = { 0, 0, static_cast(renderWidth), static_cast(renderHeight) }; + uint32_t renderWidth = m_renderManager->m_width; + uint32_t renderHeight = m_renderManager->m_height; + + if (m_renderManager->m_postProcessContext->GetFidelityFX()->GetCurrentEffect() != FidelityFX::UpscaleEffect::NONE) + { + renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + } + + Camera* activeCamera = camera ? camera : Engine::GetCameraManager().GetCamera(); + ViewportRect vp = activeCamera->GetViewport(); + + D3D12_VIEWPORT viewport = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast(vp.width * renderWidth), + static_cast(vp.height * renderHeight), + 0.0f, 1.0f + }; + D3D12_RECT scissorRect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; commandList->RSSetViewports(1, &viewport); commandList->RSSetScissorRects(1, &scissorRect); @@ -73,6 +94,19 @@ void DX2DRenderContext::Execute(ICommandListWrapper* commandListWrapper) auto* spriteData = subMesh->GetData(); auto* buffer = subMesh->GetBuffer(); + // Update Matrices per camera + spriteData->vertexUniform.view = activeCamera->GetViewMatrix(); + if (sprite->GetSpriteDrawType() == SpriteDrawType::UI) + { + glm::vec2 cameraViewSize = activeCamera->GetViewSize(); + spriteData->vertexUniform.projection = glm::orthoRH_ZO(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); + spriteData->vertexUniform.view = glm::mat4(1.f); + } + else + { + spriteData->vertexUniform.projection = activeCamera->GetProjectionMatrix(); + } + // Update Constant Buffer spriteData->GetVertexUniformBuffer>()->UpdateConstant(&spriteData->vertexUniform, sizeof(TwoDimension::VertexUniform), m_renderManager->m_frameIndex); spriteData->GetFragmentUniformBuffer>()->UpdateConstant(&spriteData->fragmentUniform, sizeof(TwoDimension::FragmentUniform), m_renderManager->m_frameIndex); diff --git a/Engine/source/DXForwardRenderContext.cpp b/Engine/source/DXForwardRenderContext.cpp index 20305180..483b8519 100644 --- a/Engine/source/DXForwardRenderContext.cpp +++ b/Engine/source/DXForwardRenderContext.cpp @@ -134,7 +134,7 @@ void DXForwardRenderContext::OnResize() } -void DXForwardRenderContext::Execute(ICommandListWrapper* commandListWrapper) +void DXForwardRenderContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { DXCommandListWrapper* dxCommandListWrapper = dynamic_cast(commandListWrapper); ID3D12GraphicsCommandList10* commandList = dxCommandListWrapper->GetDXCommandList(); @@ -152,10 +152,25 @@ void DXForwardRenderContext::Execute(ICommandListWrapper* commandListWrapper) // Set the viewport and scissor rect // @TODO This is weird but FidelityFX class takes care of viewport size (display size, render size) - uint32_t renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); - uint32_t renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); - D3D12_VIEWPORT viewport = { 0.f, 0.f, static_cast(renderWidth), static_cast(renderHeight), 0.f, 1.f }; - D3D12_RECT scissorRect = { 0, 0, static_cast(renderWidth), static_cast(renderHeight) }; + uint32_t renderWidth = m_renderManager->m_width; + uint32_t renderHeight = m_renderManager->m_height; + + if (m_renderManager->m_postProcessContext->GetFidelityFX()->GetCurrentEffect() != FidelityFX::UpscaleEffect::NONE) + { + renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + } + + Camera* activeCamera = camera ? camera : Engine::GetCameraManager().GetCamera(); + ViewportRect vp = activeCamera->GetViewport(); + + D3D12_VIEWPORT viewport = { vp.x * renderWidth, vp.y * renderHeight, vp.width * renderWidth, vp.height * renderHeight, 0.f, 1.f }; + D3D12_RECT scissorRect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; commandList->RSSetViewports(1, &viewport); commandList->RSSetScissorRects(1, &scissorRect); @@ -170,6 +185,12 @@ void DXForwardRenderContext::Execute(ICommandListWrapper* commandListWrapper) { auto* spriteData = subMesh->GetData(); auto* buffer = subMesh->GetBuffer(); + + spriteData->vertexUniform.view = activeCamera->GetViewMatrix(); + spriteData->vertexUniform.projection = activeCamera->GetProjectionMatrix(); + glm::mat4 inverseView = glm::inverse(spriteData->vertexUniform.view); + spriteData->vertexUniform.viewPosition = glm::vec4(inverseView[3].x, inverseView[3].y, inverseView[3].z, 1.0f); + if (m_renderManager->m_meshShaderEnabled) { commandList->SetPipelineState(m_meshPipeline3D->GetPipelineState().Get()); diff --git a/Engine/source/DXGBufferContext.cpp b/Engine/source/DXGBufferContext.cpp index b4381acd..ddd11169 100644 --- a/Engine/source/DXGBufferContext.cpp +++ b/Engine/source/DXGBufferContext.cpp @@ -210,7 +210,7 @@ void DXGBufferContext::OnResize() } } -void DXGBufferContext::Execute(ICommandListWrapper* commandListWrapper) +void DXGBufferContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { DXCommandListWrapper* dxCommandListWrapper = dynamic_cast(commandListWrapper); ID3D12GraphicsCommandList10* commandList = dxCommandListWrapper->GetDXCommandList(); @@ -222,10 +222,25 @@ void DXGBufferContext::Execute(ICommandListWrapper* commandListWrapper) // Set the viewport and scissor rect // @TODO This is weird but FidelityFX class takes care of viewport size (display size, render size) - uint32_t renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); - uint32_t renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); - D3D12_VIEWPORT viewport = { 0.f, 0.f, static_cast(renderWidth), static_cast(renderHeight), 0.f, 1.f }; - D3D12_RECT scissorRect = { 0, 0, static_cast(renderWidth), static_cast(renderHeight) }; + uint32_t renderWidth = m_renderManager->m_width; + uint32_t renderHeight = m_renderManager->m_height; + + if (m_renderManager->m_postProcessContext->GetFidelityFX()->GetCurrentEffect() != FidelityFX::UpscaleEffect::NONE) + { + renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + } + + Camera* activeCamera = camera ? camera : Engine::GetCameraManager().GetCamera(); + ViewportRect vp = activeCamera->GetViewport(); + + D3D12_VIEWPORT viewport = { vp.x * renderWidth, vp.y * renderHeight, vp.width * renderWidth, vp.height * renderHeight, 0.f, 1.f }; + D3D12_RECT scissorRect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; commandList->RSSetViewports(1, &viewport); commandList->RSSetScissorRects(1, &scissorRect); @@ -253,7 +268,7 @@ void DXGBufferContext::Execute(ICommandListWrapper* commandListWrapper) { CD3DX12_CPU_DESCRIPTOR_HANDLE currentRtvHandle(rtvHandle, static_cast(i), rtvDescriptorSize); float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - commandList->ClearRenderTargetView(currentRtvHandle, clearColor, 0, nullptr); + commandList->ClearRenderTargetView(currentRtvHandle, clearColor, 1, &scissorRect); } std::vector rtvHandles; @@ -271,6 +286,12 @@ void DXGBufferContext::Execute(ICommandListWrapper* commandListWrapper) { auto* spriteData = subMesh->GetData(); auto* buffer = subMesh->GetBuffer(); + + spriteData->vertexUniform.view = activeCamera->GetViewMatrix(); + spriteData->vertexUniform.projection = activeCamera->GetProjectionMatrix(); + glm::mat4 inverseView = glm::inverse(spriteData->vertexUniform.view); + spriteData->vertexUniform.viewPosition = glm::vec4(inverseView[3].x, inverseView[3].y, inverseView[3].z, 1.0f); + if (m_renderManager->m_meshShaderEnabled) { commandList->SetPipelineState(m_meshPipeline3D->GetPipelineState().Get()); diff --git a/Engine/source/DXGlobalLightingContext.cpp b/Engine/source/DXGlobalLightingContext.cpp index f1310d45..45cd5c02 100644 --- a/Engine/source/DXGlobalLightingContext.cpp +++ b/Engine/source/DXGlobalLightingContext.cpp @@ -53,7 +53,7 @@ void DXGlobalLightingContext::OnResize() m_renderManager->m_device->CopyDescriptorsSimple(4, destHandle, srcHandle, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); } -void DXGlobalLightingContext::Execute(ICommandListWrapper* commandListWrapper) +void DXGlobalLightingContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { DXCommandListWrapper* dxCommandListWrapper = dynamic_cast(commandListWrapper); ID3D12GraphicsCommandList10* commandList = dxCommandListWrapper->GetDXCommandList(); @@ -72,6 +72,28 @@ void DXGlobalLightingContext::Execute(ICommandListWrapper* commandListWrapper) D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_renderManager->m_renderTarget->GetHDRRtvHeap()->GetCPUDescriptorHandleForHeapStart(); commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); + uint32_t renderWidth = m_renderManager->m_width; + uint32_t renderHeight = m_renderManager->m_height; + + if (m_renderManager->m_postProcessContext->GetFidelityFX()->GetCurrentEffect() != FidelityFX::UpscaleEffect::NONE) + { + renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + } + + Camera* activeCamera = camera ? camera : Engine::GetCameraManager().GetCamera(); + ViewportRect vp = activeCamera->GetViewport(); + + D3D12_VIEWPORT viewport = { 0.f, 0.f, static_cast(renderWidth), static_cast(renderHeight), 0.f, 1.f }; + D3D12_RECT scissorRect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; + commandList->RSSetViewports(1, &viewport); + commandList->RSSetScissorRects(1, &scissorRect); + commandList->SetGraphicsRootSignature(m_rootSignature.Get()); ID3D12DescriptorHeap* ppHeaps[] = { m_renderManager->m_srvHeap.Get() }; commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps); @@ -82,7 +104,7 @@ void DXGlobalLightingContext::Execute(ICommandListWrapper* commandListWrapper) commandList->SetGraphicsRootConstantBufferView(0, m_renderManager->directionalLightUniformBuffer->GetGPUVirtualAddress(m_renderManager->m_frameIndex)); } - glm::mat4 inverseView = glm::inverse(Engine::GetCameraManager().GetViewMatrix()); + glm::mat4 inverseView = glm::inverse(activeCamera->GetViewMatrix()); pushConstants = { .lightViewProjection = m_renderManager->m_shadowMapContext->GetLightViewProjection(), .viewPosition = pushConstants.viewPosition = glm::vec3( diff --git a/Engine/source/DXLocalLightingContext.cpp b/Engine/source/DXLocalLightingContext.cpp index a6795896..d3b4985a 100644 --- a/Engine/source/DXLocalLightingContext.cpp +++ b/Engine/source/DXLocalLightingContext.cpp @@ -64,7 +64,7 @@ void DXLocalLightingContext::OnResize() m_renderManager->m_device->CopyDescriptorsSimple(4, destHandle, srcHandle, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); } -void DXLocalLightingContext::Execute(ICommandListWrapper* commandListWrapper) +void DXLocalLightingContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { DXCommandListWrapper* dxCommandListWrapper = dynamic_cast(commandListWrapper); ID3D12GraphicsCommandList10* commandList = dxCommandListWrapper->GetDXCommandList(); @@ -74,6 +74,28 @@ void DXLocalLightingContext::Execute(ICommandListWrapper* commandListWrapper) D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_renderManager->m_renderTarget->GetHDRRtvHeap()->GetCPUDescriptorHandleForHeapStart(); commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); + uint32_t renderWidth = m_renderManager->m_width; + uint32_t renderHeight = m_renderManager->m_height; + + if (m_renderManager->m_postProcessContext->GetFidelityFX()->GetCurrentEffect() != FidelityFX::UpscaleEffect::NONE) + { + renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + } + + Camera* activeCamera = camera ? camera : Engine::GetCameraManager().GetCamera(); + ViewportRect vp = activeCamera->GetViewport(); + + D3D12_VIEWPORT viewport = { vp.x * renderWidth, vp.y * renderHeight, vp.width * renderWidth, vp.height * renderHeight, 0.f, 1.f }; + D3D12_RECT scissorRect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; + commandList->RSSetViewports(1, &viewport); + commandList->RSSetScissorRects(1, &scissorRect); + commandList->SetGraphicsRootSignature(m_rootSignature.Get()); ID3D12DescriptorHeap* ppHeaps[] = { m_renderManager->m_srvHeap.Get() }; commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps); @@ -95,9 +117,9 @@ void DXLocalLightingContext::Execute(ICommandListWrapper* commandListWrapper) commandList->IASetVertexBuffers(0, 1, &vbv); commandList->IASetIndexBuffer(&ibv); - glm::mat4 inverseView = glm::inverse(Engine::GetCameraManager().GetViewMatrix()); + glm::mat4 inverseView = glm::inverse(activeCamera->GetViewMatrix()); PushConstants pushConstants = { - .viewProjection = Engine::GetCameraManager().GetProjectionMatrix() * Engine::GetCameraManager().GetViewMatrix(), + .viewProjection = activeCamera->GetProjectionMatrix() * activeCamera->GetViewMatrix(), .viewPosition = glm::vec3( inverseView[3].x, inverseView[3].y, diff --git a/Engine/source/DXNaiveLightingContext.cpp b/Engine/source/DXNaiveLightingContext.cpp index 2a0e4ff1..cb8714b0 100644 --- a/Engine/source/DXNaiveLightingContext.cpp +++ b/Engine/source/DXNaiveLightingContext.cpp @@ -50,7 +50,7 @@ void DXNaiveLightingContext::OnResize() m_renderManager->m_device->CopyDescriptorsSimple(4, destHandle, srcHandle, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); } -void DXNaiveLightingContext::Execute(ICommandListWrapper* commandListWrapper) +void DXNaiveLightingContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { DXCommandListWrapper* dxCommandListWrapper = dynamic_cast(commandListWrapper); ID3D12GraphicsCommandList10* commandList = dxCommandListWrapper->GetDXCommandList(); @@ -69,6 +69,28 @@ void DXNaiveLightingContext::Execute(ICommandListWrapper* commandListWrapper) D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_renderManager->m_renderTarget->GetHDRRtvHeap()->GetCPUDescriptorHandleForHeapStart(); commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); + uint32_t renderWidth = m_renderManager->m_width; + uint32_t renderHeight = m_renderManager->m_height; + + if (m_renderManager->m_postProcessContext->GetFidelityFX()->GetCurrentEffect() != FidelityFX::UpscaleEffect::NONE) + { + renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + } + + Camera* activeCamera = camera ? camera : Engine::GetCameraManager().GetCamera(); + ViewportRect vp = activeCamera->GetViewport(); + + D3D12_VIEWPORT viewport = { vp.x * renderWidth, vp.y * renderHeight, vp.width * renderWidth, vp.height * renderHeight, 0.f, 1.f }; + D3D12_RECT scissorRect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; + commandList->RSSetViewports(1, &viewport); + commandList->RSSetScissorRects(1, &scissorRect); + commandList->SetGraphicsRootSignature(m_rootSignature.Get()); ID3D12DescriptorHeap* ppHeaps[] = { m_renderManager->m_srvHeap.Get() }; commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps); @@ -84,7 +106,7 @@ void DXNaiveLightingContext::Execute(ICommandListWrapper* commandListWrapper) commandList->SetGraphicsRootConstantBufferView(1, m_renderManager->pointLightUniformBuffer->GetGPUVirtualAddress(m_renderManager->m_frameIndex)); } - glm::mat4 inverseView = glm::inverse(Engine::GetCameraManager().GetViewMatrix()); + glm::mat4 inverseView = glm::inverse(activeCamera->GetViewMatrix()); pushConstants = { .viewPosition = pushConstants.viewPosition = glm::vec3( inverseView[3].x, diff --git a/Engine/source/DXPostProcessContext.cpp b/Engine/source/DXPostProcessContext.cpp index a36f2947..8ef943d1 100644 --- a/Engine/source/DXPostProcessContext.cpp +++ b/Engine/source/DXPostProcessContext.cpp @@ -63,7 +63,7 @@ void DXPostProcessContext::OnResize() m_fidelityFX->OnResize(m_renderManager->m_device, m_renderManager->m_width, m_renderManager->m_height); } -void DXPostProcessContext::Execute(ICommandListWrapper* commandListWrapper) +void DXPostProcessContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { DXCommandListWrapper* dxCommandListWrapper = dynamic_cast(commandListWrapper); ID3D12GraphicsCommandList10* commandList = dxCommandListWrapper->GetDXCommandList(); diff --git a/Engine/source/DXRenderManager.cpp b/Engine/source/DXRenderManager.cpp index db382451..35373b59 100644 --- a/Engine/source/DXRenderManager.cpp +++ b/Engine/source/DXRenderManager.cpp @@ -396,7 +396,24 @@ bool DXRenderManager::BeginRender(glm::vec3 bgColor) CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), static_cast(m_frameIndex), m_rtvDescriptorSize); m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); - m_2dRenderContext->Execute(&wrapper); + size_t cameraCount = Engine::GetCameraManager().GetCameraCount(); + for (size_t c = 0; c < cameraCount; ++c) + { + Camera* cam = Engine::GetCameraManager().GetCamera(static_cast(c)); + if (cam == nullptr || !cam->GetIsActive()) continue; + + ViewportRect vp = cam->GetViewport(); + D3D12_RECT rect = { + static_cast(vp.x * m_width), + static_cast(vp.y * m_height), + static_cast((vp.x + vp.width) * m_width), + static_cast((vp.y + vp.height) * m_height) + }; + m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 1, &rect); + m_commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 1, &rect); + + m_2dRenderContext->Execute(&wrapper, cam); + } } break; case RenderType::ThreeDimension: @@ -411,11 +428,31 @@ bool DXRenderManager::BeginRender(glm::vec3 bgColor) m_commandList->ResourceBarrier(1, &barrier); m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); - if (m_workGraphsEnabled && m_meshNodesEnabled) m_workGraphsContext->ExecuteWorkGraphs(); - else + size_t cameraCount = Engine::GetCameraManager().GetCameraCount(); + for (size_t c = 0; c < cameraCount; ++c) { - m_shadowMapContext->Execute(&wrapper); - m_forwardRenderContext->Execute(&wrapper); + Camera* cam = Engine::GetCameraManager().GetCamera(static_cast(c)); + if (cam == nullptr || !cam->GetIsActive()) continue; + + uint32_t renderWidth = m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + uint32_t renderHeight = m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + ViewportRect vp = cam->GetViewport(); + D3D12_RECT rect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; + m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 1, &rect); + m_commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 1, &rect); + + if (m_workGraphsEnabled && m_meshNodesEnabled) m_workGraphsContext->ExecuteWorkGraphs(cam); + else + { + if (c == 0) m_shadowMapContext->Execute(&wrapper, cam); + m_forwardRenderContext->Execute(&wrapper, cam); + } + if (m_skyboxEnabled) m_skyboxRenderContext->Execute(&wrapper, cam); } } // Deferred Rendering @@ -429,13 +466,37 @@ bool DXRenderManager::BeginRender(glm::vec3 bgColor) clearColor[3] = 0.f; // Set alpha to 0 for discarding in lighting pass shader m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); - m_shadowMapContext->Execute(&wrapper); - m_gBufferContext->Execute(&wrapper); - //m_naiveLightingContext->Execute(&wrapper); - m_globalLightingContext->Execute(&wrapper); - if (!m_meshletVisualization) m_localLightingContext->Execute(&wrapper); + size_t cameraCount = Engine::GetCameraManager().GetCameraCount(); + for (size_t c = 0; c < cameraCount; ++c) + { + Camera* cam = Engine::GetCameraManager().GetCamera(static_cast(c)); + if (cam == nullptr || !cam->GetIsActive()) continue; + + uint32_t renderWidth = m_width; + uint32_t renderHeight = m_height; + + if (m_postProcessContext->GetFidelityFX()->GetCurrentEffect() != FidelityFX::UpscaleEffect::NONE) + { + renderWidth = m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + renderHeight = m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + } + ViewportRect vp = cam->GetViewport(); + D3D12_RECT rect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; + m_commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 1, &rect); + + if (c == 0) m_shadowMapContext->Execute(&wrapper, cam); + m_gBufferContext->Execute(&wrapper, cam); + //m_naiveLightingContext->Execute(&wrapper, cam); + m_globalLightingContext->Execute(&wrapper, cam); + if (!m_meshletVisualization) m_localLightingContext->Execute(&wrapper, cam); + if (m_skyboxEnabled) m_skyboxRenderContext->Execute(&wrapper, cam); + } } - if (m_skyboxEnabled) m_skyboxRenderContext->Execute(&wrapper); m_postProcessContext->Execute(&wrapper); } break; diff --git a/Engine/source/DXShadowMapContext.cpp b/Engine/source/DXShadowMapContext.cpp index c8b21ac4..437dd38e 100644 --- a/Engine/source/DXShadowMapContext.cpp +++ b/Engine/source/DXShadowMapContext.cpp @@ -65,7 +65,7 @@ void DXShadowMapContext::OnResize() { } -void DXShadowMapContext::Execute(ICommandListWrapper* commandListWrapper) +void DXShadowMapContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { if (!m_enabled || m_renderManager->directionalLightUniforms.empty()) return; @@ -380,21 +380,44 @@ void DXShadowMapContext::DrawImGui() if (showDebugFrustum) { + CameraManager& camManager = Engine::GetCameraManager(); + int mainCamIdx = camManager.GetMainCameraIndex(); + Camera* mainCam = camManager.GetCamera(mainCamIdx); + + // Skip if camera is inactive + if (!mainCam || !mainCam->GetIsActive()) return; + ImDrawList* drawList = ImGui::GetBackgroundDrawList(); - glm::mat4 cameraView = Engine::GetCameraManager().GetViewMatrix(); - glm::mat4 cameraProj = Engine::GetCameraManager().GetProjectionMatrix(); + glm::mat4 cameraView = mainCam->GetViewMatrix(); + glm::mat4 cameraProj = mainCam->GetProjectionMatrix(); + + ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); + glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; + glm::vec2 windowSize = { imguiViewport->Size.x, imguiViewport->Size.y }; + ViewportRect vp = mainCam->GetViewport(); + + ImVec2 clipMin = { vp.x * windowSize.x + windowPos.x, vp.y * windowSize.y + windowPos.y }; + ImVec2 clipMax = { (vp.x + vp.width) * windowSize.x + windowPos.x, (vp.y + vp.height) * windowSize.y + windowPos.y }; + + drawList->PushClipRect(clipMin, clipMax); // Draw light position and target on the screen for debugging glm::vec2 lightScreenPos = Engine::GetRenderManager()->WorldToScreen(m_lightPosition, cameraView, cameraProj); glm::vec2 targetScreenPos = Engine::GetRenderManager()->WorldToScreen(m_lightTarget, cameraView, cameraProj); if (lightScreenPos.x != -1.0f && targetScreenPos.x != -1.0f) { - // Draw light position - drawList->AddCircleFilled(ImVec2(lightScreenPos.x, lightScreenPos.y), 8.0f, IM_COL32(255, 255, 0, 255)); - drawList->AddText(ImVec2(lightScreenPos.x + 10, lightScreenPos.y - 10), IM_COL32(255, 255, 0, 255), "Sun"); - // Draw light target - drawList->AddCircleFilled(ImVec2(targetScreenPos.x, targetScreenPos.y), 4.0f, IM_COL32(255, 0, 0, 255)); - drawList->AddText(ImVec2(targetScreenPos.x + 10, targetScreenPos.y - 10), IM_COL32(255, 0, 0, 255), "Target"); + // Draw light position if not occluded + if (!camManager.IsScreenPointOccluded(lightScreenPos, mainCamIdx)) + { + drawList->AddCircleFilled(ImVec2(lightScreenPos.x, lightScreenPos.y), 8.0f, IM_COL32(255, 255, 0, 255)); + drawList->AddText(ImVec2(lightScreenPos.x + 10, lightScreenPos.y - 10), IM_COL32(255, 255, 0, 255), "Sun"); + } + // Draw light target if not occluded + if (!camManager.IsScreenPointOccluded(targetScreenPos, mainCamIdx)) + { + drawList->AddCircleFilled(ImVec2(targetScreenPos.x, targetScreenPos.y), 4.0f, IM_COL32(255, 0, 0, 255)); + drawList->AddText(ImVec2(targetScreenPos.x + 10, targetScreenPos.y - 10), IM_COL32(255, 0, 0, 255), "Target"); + } } // @TODO Study how this code works @@ -420,7 +443,12 @@ void DXShadowMapContext::DrawImGui() auto DrawLine = [&](int index1, int index2) { - if (valid[index1] && valid[index2]) drawList->AddLine(screenCorners[index1], screenCorners[index2], IM_COL32(0, 255, 255, 255), 2.0f); + if (valid[index1] && valid[index2] && + !camManager.IsScreenPointOccluded(glm::vec2(screenCorners[index1].x, screenCorners[index1].y), mainCamIdx) && + !camManager.IsScreenPointOccluded(glm::vec2(screenCorners[index2].x, screenCorners[index2].y), mainCamIdx)) + { + drawList->AddLine(screenCorners[index1], screenCorners[index2], IM_COL32(0, 255, 255, 255), 2.0f); + } }; // Near @@ -429,5 +457,7 @@ void DXShadowMapContext::DrawImGui() DrawLine(4, 5); DrawLine(5, 6); DrawLine(6, 7); DrawLine(7, 4); // Connect Near and Far DrawLine(0, 4); DrawLine(1, 5); DrawLine(2, 6); DrawLine(3, 7); + + drawList->PopClipRect(); } } diff --git a/Engine/source/DXSkyboxRenderContext.cpp b/Engine/source/DXSkyboxRenderContext.cpp index d6e0e5cb..853128d2 100644 --- a/Engine/source/DXSkyboxRenderContext.cpp +++ b/Engine/source/DXSkyboxRenderContext.cpp @@ -40,7 +40,7 @@ void DXSkyboxRenderContext::OnResize() Initialize(); } -void DXSkyboxRenderContext::Execute(ICommandListWrapper* commandListWrapper) +void DXSkyboxRenderContext::Execute(ICommandListWrapper* commandListWrapper, Camera* camera) { if (!m_renderManager->m_skyboxEnabled || !m_skybox) return; @@ -60,7 +60,29 @@ void DXSkyboxRenderContext::Execute(ICommandListWrapper* commandListWrapper) ID3D12DescriptorHeap* ppHeapsSkybox[] = { m_renderManager->m_srvHeap.Get() }; commandList->SetDescriptorHeaps(_countof(ppHeapsSkybox), ppHeapsSkybox); - glm::mat4 worldToNDC[2] = { Engine::GetCameraManager().GetViewMatrix(), Engine::GetCameraManager().GetProjectionMatrix() }; + Camera* activeCamera = camera ? camera : Engine::GetCameraManager().GetCamera(); + + uint32_t renderWidth = m_renderManager->m_width; + uint32_t renderHeight = m_renderManager->m_height; + + if (m_renderManager->m_postProcessContext->GetFidelityFX()->GetCurrentEffect() != FidelityFX::UpscaleEffect::NONE) + { + renderWidth = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderWidth(); + renderHeight = m_renderManager->m_postProcessContext->GetFidelityFX()->GetRenderHeight(); + } + ViewportRect vp = activeCamera->GetViewport(); + + D3D12_VIEWPORT viewport = { vp.x * renderWidth, vp.y * renderHeight, vp.width * renderWidth, vp.height * renderHeight, 0.f, 1.f }; + D3D12_RECT scissorRect = { + static_cast(vp.x * renderWidth), + static_cast(vp.y * renderHeight), + static_cast((vp.x + vp.width) * renderWidth), + static_cast((vp.y + vp.height) * renderHeight) + }; + commandList->RSSetViewports(1, &viewport); + commandList->RSSetScissorRects(1, &scissorRect); + + glm::mat4 worldToNDC[2] = { activeCamera->GetViewMatrix(), activeCamera->GetProjectionMatrix() }; commandList->SetGraphicsRoot32BitConstants(0, 32, &worldToNDC, 0); commandList->SetGraphicsRootDescriptorTable(1, m_skybox->GetCubemapSrv()); diff --git a/Engine/source/DXWorkGraphsContext.cpp b/Engine/source/DXWorkGraphsContext.cpp index 5d7a0c90..167527d7 100644 --- a/Engine/source/DXWorkGraphsContext.cpp +++ b/Engine/source/DXWorkGraphsContext.cpp @@ -178,7 +178,7 @@ void DXWorkGraphsContext::InitializeWorkGraphs() #endif } -void DXWorkGraphsContext::ExecuteWorkGraphs() +void DXWorkGraphsContext::ExecuteWorkGraphs(Camera* camera) { #if USE_PREVIEW_SDK if (!m_renderManager->m_workGraphsEnabled || !m_renderManager->m_meshNodesEnabled) return; @@ -194,7 +194,8 @@ void DXWorkGraphsContext::ExecuteWorkGraphs() if (spriteData->meshlets.empty()) return; // Update Culling Data - glm::mat4 viewProjection = Engine::GetCameraManager().GetProjectionMatrix() * Engine::GetCameraManager().GetViewMatrix(); + Camera* activeCamera = camera ? camera : Engine::GetCameraManager().GetCamera(); + glm::mat4 viewProjection = activeCamera->GetProjectionMatrix() * activeCamera->GetViewMatrix(); auto planes = ExtractFrustumPlanes(viewProjection); CullingData cullingData; for (int i = 0; i < 6; ++i) cullingData.frustumPlanes[i] = planes[i]; diff --git a/Engine/source/Engine.cpp b/Engine/source/Engine.cpp index 2c4a40a1..0a361ebb 100644 --- a/Engine/source/Engine.cpp +++ b/Engine/source/Engine.cpp @@ -38,7 +38,6 @@ void Engine::Init(const char* title, int windowWidth, int windowHeight, bool ful dynamic_cast(renderManager)->Initialize(window.GetWindow()); } - cameraManager.Init({ windowWidth ,windowHeight }, CameraType::TwoDimension, 1.f); soundManager.Initialize(8); spriteManager = new SpriteManager; diff --git a/Engine/source/GLRenderManager.cpp b/Engine/source/GLRenderManager.cpp index e648306a..1399b353 100644 --- a/Engine/source/GLRenderManager.cpp +++ b/Engine/source/GLRenderManager.cpp @@ -67,6 +67,7 @@ bool GLRenderManager::BeginRender(glm::vec3 bgColor) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); break; } + glCheck(glDisable(GL_SCISSOR_TEST)); glCheck(glClearColor(bgColor.r, bgColor.g, bgColor.b, 1.f)); glCheck(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); @@ -134,160 +135,170 @@ bool GLRenderManager::BeginRender(glm::vec3 bgColor) } std::vector sprites = Engine::Instance().GetSpriteManager().GetDynamicSprites(); - for (const auto& sprite : sprites) + size_t cameraCount = Engine::GetCameraManager().GetCameraCount(); + + for (size_t c = 0; c < cameraCount; ++c) { - for (auto& subMesh : sprite->GetSubMeshes()) + Camera* cam = Engine::GetCameraManager().GetCamera(static_cast(c)); + if (cam == nullptr || !cam->GetIsActive()) continue; + + ViewportRect vp = cam->GetViewport(); + GLsizei w, h; + SDL_GetWindowSizeInPixels(Engine::GetWindow().GetWindow(), &w, &h); + GLint vx = static_cast(vp.x * w); + GLint vy = static_cast((1.0f - vp.y - vp.height) * h); + GLsizei vw = static_cast(vp.width * w); + GLsizei vh = static_cast(vp.height * h); + glViewport(vx, vy, vw, vh); + + // Clear only the camera's viewport region + glEnable(GL_SCISSOR_TEST); + glScissor(vx, vy, vw, vh); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + + for (const auto& sprite : sprites) { - auto* buffer = subMesh->GetBuffer(); - switch (rMode) - { - case RenderType::TwoDimension: + for (auto& subMesh : sprite->GetSubMeshes()) { - gl2DShader.Use(true); + auto* buffer = subMesh->GetBuffer(); + switch (rMode) + { + case RenderType::TwoDimension: + { + gl2DShader.Use(true); - auto* spriteData = subMesh->GetData(); + auto* spriteData = subMesh->GetData(); - spriteData->GetVertexUniformBuffer>()->UpdateUniform(sizeof(TwoDimension::VertexUniform), &spriteData->vertexUniform); - spriteData->GetFragmentUniformBuffer>()->UpdateUniform(sizeof(TwoDimension::FragmentUniform), &spriteData->fragmentUniform); + spriteData->vertexUniform.view = cam->GetViewMatrix(); + if (sprite->GetSpriteDrawType() == SpriteDrawType::UI) + { + glm::vec2 cameraViewSize = cam->GetViewSize(); + spriteData->vertexUniform.projection = glm::ortho(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); + spriteData->vertexUniform.view = glm::mat4(1.f); + } + else + { + spriteData->vertexUniform.projection = cam->GetProjectionMatrix(); + } - glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 0, spriteData->GetVertexUniformBuffer>()->GetHandle())); - glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 1, spriteData->GetFragmentUniformBuffer>()->GetHandle())); + spriteData->GetVertexUniformBuffer>()->UpdateUniform(sizeof(TwoDimension::VertexUniform), &spriteData->vertexUniform); + spriteData->GetFragmentUniformBuffer>()->UpdateUniform(sizeof(TwoDimension::FragmentUniform), &spriteData->fragmentUniform); - buffer->vertexArray->Use(true); - GLDrawIndexed(*buffer->vertexArray); - buffer->vertexArray->Use(false); + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 0, spriteData->GetVertexUniformBuffer>()->GetHandle())); + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 1, spriteData->GetFragmentUniformBuffer>()->GetHandle())); - gl2DShader.Use(false); - break; - } - case RenderType::ThreeDimension: - { - gl3DShader.Use(true); - - auto* spriteData = subMesh->GetData(); - - //// Initialize bone matrices to identity for non-skeletal meshes - //// This ensures all meshes render correctly, whether skinned or not - //if (spriteData->boneInfoMap.empty()) - //{ - // // Non-skeletal mesh: set all bone matrices to identity - // for (int i = 0; i < ThreeDimension::MAX_BONES; i++) - // { - // spriteData->vertexUniform.finalBones[i] = glm::mat4(1.0f); - // } - //} - // Only initialize bone matrices for non-skeletal meshes - if (spriteData->boneInfoMap.empty()) + buffer->vertexArray->Use(true); + GLDrawIndexed(*buffer->vertexArray); + buffer->vertexArray->Use(false); + + gl2DShader.Use(false); + break; + } + case RenderType::ThreeDimension: { - for (int i = 0; i < ThreeDimension::MAX_BONES; i++) + gl3DShader.Use(true); + + auto* spriteData = subMesh->GetData(); + + spriteData->vertexUniform.view = cam->GetViewMatrix(); + spriteData->vertexUniform.projection = cam->GetProjectionMatrix(); + glm::mat4 inverseView = glm::inverse(spriteData->vertexUniform.view); + spriteData->vertexUniform.viewPosition = glm::vec4(inverseView[3].x, inverseView[3].y, inverseView[3].z, 1.0f); + + //// Initialize bone matrices to identity for non-skeletal meshes + //// This ensures all meshes render correctly, whether skinned or not + //if (spriteData->boneInfoMap.empty()) + //{ + // // Non-skeletal mesh: set all bone matrices to identity + // for (int i = 0; i < ThreeDimension::MAX_BONES; i++) + // { + // spriteData->vertexUniform.finalBones[i] = glm::mat4(1.0f); + // } + //} + // Only initialize bone matrices for non-skeletal meshes + if (spriteData->boneInfoMap.empty()) { - spriteData->vertexUniform.finalBones[i] = glm::mat4(1.0f); + for (int i = 0; i < ThreeDimension::MAX_BONES; i++) + { + spriteData->vertexUniform.finalBones[i] = glm::mat4(1.0f); + } } - } - // Note: Skeletal meshes will have their bone matrices updated by SkeletalAnimator::Update() - - //auto& vertexUniformBuffer = std::get(sprite->GetBuffer()->buffer); - spriteData->GetVertexUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::VertexUniform), &spriteData->vertexUniform); - - //auto& fragmentUniformBuffer = std::get*>(sprite->GetFragmentUniformBuffer()->buffer); - spriteData->GetFragmentUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::FragmentUniform), &spriteData->fragmentUniform); - //auto& materialUniformBuffer = std::get*>(sprite->GetMaterialUniformBuffer()->buffer); - spriteData->GetMaterialUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::Material), &spriteData->material); + spriteData->GetVertexUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::VertexUniform), &spriteData->vertexUniform); + spriteData->GetFragmentUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::FragmentUniform), &spriteData->fragmentUniform); + spriteData->GetMaterialUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::Material), &spriteData->material); - buffer->vertexArray->Use(true); + buffer->vertexArray->Use(true); - for (int loc = 0; loc <= 8; ++loc) - { - GLint enabled, size, type, stride, bufferBinding; - glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled); - glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_SIZE, &size); - glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_TYPE, &type); - glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &stride); - glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &bufferBinding); - } + for (int loc = 0; loc <= 8; ++loc) + { + GLint enabled, size, type, stride, bufferBinding; + glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled); + glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_SIZE, &size); + glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_TYPE, &type); + glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &stride); + glGetVertexAttribiv(loc, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &bufferBinding); + } - glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 2, spriteData->GetVertexUniformBuffer>()->GetHandle())); - glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 3, spriteData->GetFragmentUniformBuffer>()->GetHandle())); - glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 4, spriteData->GetMaterialUniformBuffer>()->GetHandle())); + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 2, spriteData->GetVertexUniformBuffer>()->GetHandle())); + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 3, spriteData->GetFragmentUniformBuffer>()->GetHandle())); + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 4, spriteData->GetMaterialUniformBuffer>()->GetHandle())); - glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 5, directionalLightUniformBuffer->GetHandle())); - glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 6, pointLightUniformBuffer->GetHandle())); - GLDrawIndexed(*buffer->vertexArray); - buffer->vertexArray->Use(false); + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 5, directionalLightUniformBuffer->GetHandle())); + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 6, pointLightUniformBuffer->GetHandle())); + GLDrawIndexed(*buffer->vertexArray); + buffer->vertexArray->Use(false); - gl3DShader.Use(false); + gl3DShader.Use(false); #ifdef _DEBUG - //if (isDrawNormals) - //{ - // glNormal3DShader.Use(true); - // normalVertexArray.Use(true); - // //glDrawArrays(GL_LINES, 0, static_cast(normalVertices3D.size())); - // normalVertexArray.Use(false); - // glNormal3DShader.Use(false); - //} - - if (m_normalVectorVisualization) - { - glNormal3DShader.Use(true); + if (m_normalVectorVisualization) + { + glNormal3DShader.Use(true); - // Bind VertexUniform (binding 2) so Normal3D.vert can access finalBones[] - glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 2, spriteData->GetVertexUniformBuffer>()->GetHandle())); + // Bind VertexUniform (binding 2) so Normal3D.vert can access finalBones[] + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 2, spriteData->GetVertexUniformBuffer>()->GetHandle())); - buffer->normalVertexArray->Use(true); - GLsizei size = static_cast(spriteData->normalVertices.size()); - glDrawArrays(GL_LINES, 0, size); - buffer->normalVertexArray->Use(false); - - glNormal3DShader.Use(false); - } + buffer->normalVertexArray->Use(true); + GLsizei size = static_cast(spriteData->normalVertices.size()); + glDrawArrays(GL_LINES, 0, size); + buffer->normalVertexArray->Use(false); + glNormal3DShader.Use(false); + } #endif - break; - } + break; + } + } } } - } - //switch (rMode) - //{ - //case RenderType::TwoDimension: - // gl2DShader.Use(false); - // break; - //case RenderType::ThreeDimension: - // gl3DShader.Use(false); - // break; - //} - - //Skybox - if (m_skyboxEnabled) - { - skyboxShader.Use(true); - //if (vertexUniform3D != nullptr) - //{ - // vertexUniform3D->UpdateUniform(vertexUniforms3D.size() * sizeof(ThreeDimension::VertexUniform), vertexUniforms3D.data()); - //} - GLint viewLoc = glGetUniformLocation(skyboxShader.GetProgramHandle(), "view"); - GLint projectionLoc = glGetUniformLocation(skyboxShader.GetProgramHandle(), "projection"); - - std::span spanView(&Engine::GetCameraManager().GetViewMatrix()[0][0], 16); - glUniformMatrix4fv(viewLoc, 1, GL_FALSE, spanView.data()); - std::span spanProjection(&Engine::GetCameraManager().GetProjectionMatrix()[0][0], 16); - glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, spanProjection.data()); - - GLint skyboxLoc = glCheck(glGetUniformLocation(skyboxShader.GetProgramHandle(), "skybox")); - glCheck(glUniform1i(skyboxLoc, 0)); + //Skybox + if (m_skyboxEnabled) + { + skyboxShader.Use(true); + GLint viewLoc = glGetUniformLocation(skyboxShader.GetProgramHandle(), "view"); + GLint projectionLoc = glGetUniformLocation(skyboxShader.GetProgramHandle(), "projection"); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_CUBE_MAP, skybox->GetCubeMap()); + std::span spanView(&cam->GetViewMatrix()[0][0], 16); + glUniformMatrix4fv(viewLoc, 1, GL_FALSE, spanView.data()); + std::span spanProjection(&cam->GetProjectionMatrix()[0][0], 16); + glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, spanProjection.data()); + + GLint skyboxLoc = glCheck(glGetUniformLocation(skyboxShader.GetProgramHandle(), "skybox")); + glCheck(glUniform1i(skyboxLoc, 0)); - skyboxVertexArray.Use(true); - glCheck(glDrawArrays(GL_TRIANGLES, 0, 36)); - skyboxVertexArray.Use(false); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, skybox->GetCubeMap()); - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); - skyboxShader.Use(false); + skyboxVertexArray.Use(true); + glCheck(glDrawArrays(GL_TRIANGLES, 0, 36)); + skyboxVertexArray.Use(false); + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + skyboxShader.Use(false); + } } imguiManager->Begin(); diff --git a/Engine/source/GameStateManager.cpp b/Engine/source/GameStateManager.cpp index 34963d1a..2afc5584 100644 --- a/Engine/source/GameStateManager.cpp +++ b/Engine/source/GameStateManager.cpp @@ -34,6 +34,7 @@ void GameStateManager::LevelInit() void GameStateManager::LevelInit(GameLevel currentLevel_) { currentLevel = currentLevel_; + Engine::GetCameraManager().DeleteAllCameras(); LevelInit(); Engine::GetObjectManager().ProcessFunctionQueue(); state = State::UPDATE; @@ -55,6 +56,7 @@ void GameStateManager::Update(float dt) } break; case State::LOAD: + Engine::GetCameraManager().DeleteAllCameras(); LevelInit(); Engine::GetObjectManager().ProcessFunctionQueue(); Engine::Instance().GetTimer().Init(Engine::Instance().GetTimer().GetFrameRate()); diff --git a/Engine/source/InputManager.cpp b/Engine/source/InputManager.cpp index 04a95fd1..c8a44be9 100644 --- a/Engine/source/InputManager.cpp +++ b/Engine/source/InputManager.cpp @@ -137,11 +137,11 @@ glm::vec2 InputManager::GetMousePosition() float mouseX, mouseY; SDL_GetMouseState(&mouseX, &mouseY); glm::vec2 pos = { mouseX, mouseY }; - glm::vec2 windowViewSize = Engine::Instance().GetCameraManager().GetViewSize(); + glm::vec2 windowViewSize = Engine::Instance().GetCameraManager().GetCamera()->GetViewSize(); int w = 0; int h = 0; SDL_GetWindowSizeInPixels(Engine::Instance().GetWindow().GetWindow(), &w, &h); - float zoom = Engine::Instance().GetCameraManager().GetZoom(); + float zoom = Engine::Instance().GetCameraManager().GetCamera()->GetZoom(); pos = { windowViewSize.x / 2.f * (pos.x / (static_cast(w) / 2.f) - 1) / zoom, windowViewSize.y / 2.f * (pos.y / (static_cast(h) / 2.f) - 1) / zoom }; return pos; diff --git a/Engine/source/ObjectManager.cpp b/Engine/source/ObjectManager.cpp index b7a4b64e..8fa9af11 100644 --- a/Engine/source/ObjectManager.cpp +++ b/Engine/source/ObjectManager.cpp @@ -994,7 +994,18 @@ void ObjectManager::SkeletalAnimatorControllerForImGui(SkeletalAnimator* animato * rotationMatrix * glm::scale(glm::mat4(1.0f), scale); + ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); + glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; + glm::vec2 windowSize = { imguiViewport->Size.x, imguiViewport->Size.y }; + Camera* mainCam = Engine::GetCameraManager().GetCamera(); + ViewportRect vp = mainCam->GetViewport(); + + ImVec2 clipMin = { vp.x * windowSize.x + windowPos.x, vp.y * windowSize.y + windowPos.y }; + ImVec2 clipMax = { (vp.x + vp.width) * windowSize.x + windowPos.x, (vp.y + vp.height) * windowSize.y + windowPos.y }; + + ImGui::GetBackgroundDrawList()->PushClipRect(clipMin, clipMax); RenderBoneHierarchy(¤tAnim->GetRootNode(), transforms, model); + ImGui::GetBackgroundDrawList()->PopClipRect(); } } @@ -1057,16 +1068,23 @@ void ObjectManager::RenderBoneHierarchy(const AssimpNodeData* node, const std::m glm::mat4 nodeGlobalMatrix = animatedTransforms.at(node->name); glm::mat4 nodeWorldMatrix = objectTransform * nodeGlobalMatrix; + CameraManager& camManager = Engine::GetCameraManager(); + int mainCamIdx = camManager.GetMainCameraIndex(); + Camera* mainCam = camManager.GetCamera(mainCamIdx); + + // Skip if camera is inactive + if (!mainCam || !mainCam->GetIsActive()) return; + // Convert world position to screen coordinates - glm::mat4 view = Engine::GetCameraManager().GetViewMatrix(); - glm::mat4 proj = Engine::GetCameraManager().GetProjectionMatrix(); + glm::mat4 view = mainCam->GetViewMatrix(); + glm::mat4 proj = mainCam->GetProjectionMatrix(); ImDrawList* drawList = ImGui::GetBackgroundDrawList(); glm::vec3 currentPos = glm::vec3(nodeWorldMatrix[3]); // Extract translation glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(currentPos, view, proj); // Draw joint point - if (screenPos.x != -1 && screenPos.y != -1) + if (screenPos.x != -1 && screenPos.y != -1 && !camManager.IsScreenPointOccluded(screenPos, mainCamIdx)) { ImU32 color = IM_COL32(0, 255, 0, 255); if (node->name == selectedBoneName) color = IM_COL32(255, 0, 0, 255); @@ -1085,9 +1103,11 @@ void ObjectManager::RenderBoneHierarchy(const AssimpNodeData* node, const std::m glm::vec3 childPos = glm::vec3(childWorldMatrix[3]); glm::vec2 childScreenPos = Engine::GetRenderManager()->WorldToScreen(childPos, view, proj); - // Draw line if both positions are valid + // Draw line if both positions are valid and not occluded if (screenPos.x >= 0 && screenPos.y >= 0 && - childScreenPos.x >= 0 && childScreenPos.y >= 0) + childScreenPos.x >= 0 && childScreenPos.y >= 0 && + !camManager.IsScreenPointOccluded(screenPos, mainCamIdx) && + !camManager.IsScreenPointOccluded(childScreenPos, mainCamIdx)) { drawList->AddLine( ImVec2(screenPos.x, screenPos.y), @@ -1105,7 +1125,7 @@ void ObjectManager::SelectObjectWithMouse() //SelectObject if (Engine::GetInputManager().IsMouseButtonPressed(MOUSEBUTTON::LEFT) && isShowPopup == false) { - Ray ray = Engine::GetCameraManager().CalculateRayFrom2DPosition(Engine::GetInputManager().GetMousePosition()); + Ray ray = Engine::GetCameraManager().GetCamera()->CalculateRayFrom2DPosition(Engine::GetInputManager().GetMousePosition()); if (isDragObject == false) { float tMin, tMax; @@ -1148,8 +1168,8 @@ void ObjectManager::SelectObjectWithMouse() objT->GetComponent()->SetIsGravityOn(false); isObjGravityOn = true; } - ray = Engine::GetCameraManager().CalculateRayFrom2DPosition(Engine::GetInputManager().GetMousePosition()); - glm::vec3 planeNormal = Engine::GetCameraManager().GetBackVector(); + ray = Engine::GetCameraManager().GetCamera()->CalculateRayFrom2DPosition(Engine::GetInputManager().GetMousePosition()); + glm::vec3 planeNormal = Engine::GetCameraManager().GetCamera()->GetBackVector(); glm::vec3 objectPosition = objT->GetPosition(); float distanceToPlane = glm::dot(planeNormal, objectPosition - ray.origin) / glm::dot(planeNormal, ray.direction); glm::vec3 intersectionPoint = ray.origin + distanceToPlane * ray.direction; @@ -1414,10 +1434,27 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) Object* obj = phy->GetOwner(); if (!obj) return; - glm::mat4 view = Engine::GetCameraManager().GetViewMatrix(); - glm::mat4 proj = Engine::GetCameraManager().GetProjectionMatrix(); + CameraManager& camManager = Engine::GetCameraManager(); + int mainCamIdx = camManager.GetMainCameraIndex(); + Camera* mainCam = camManager.GetCamera(mainCamIdx); + + // Skip if camera is inactive + if (!mainCam || !mainCam->GetIsActive()) return; + + glm::mat4 view = mainCam->GetViewMatrix(); + glm::mat4 proj = mainCam->GetProjectionMatrix(); ImDrawList* drawList = ImGui::GetBackgroundDrawList(); + ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); + glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; + glm::vec2 windowSize = { imguiViewport->Size.x, imguiViewport->Size.y }; + ViewportRect vp = mainCam->GetViewport(); + + ImVec2 clipMin = { vp.x * windowSize.x + windowPos.x, vp.y * windowSize.y + windowPos.y }; + ImVec2 clipMax = { (vp.x + vp.width) * windowSize.x + windowPos.x, (vp.y + vp.height) * windowSize.y + windowPos.y }; + + drawList->PushClipRect(clipMin, clipMax); + ImU32 color; switch (phy->GetBodyType()) { @@ -1447,7 +1484,9 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) if (screenPos.x >= 0 && screenPos.y >= 0) { - if (!first && prevScreenPos.x >= 0 && prevScreenPos.y >= 0) + if (!first && prevScreenPos.x >= 0 && prevScreenPos.y >= 0 && + !camManager.IsScreenPointOccluded(prevScreenPos, mainCamIdx) && + !camManager.IsScreenPointOccluded(screenPos, mainCamIdx)) { drawList->AddLine(ImVec2(prevScreenPos.x, prevScreenPos.y), ImVec2(screenPos.x, screenPos.y), color, 2.0f); } @@ -1495,13 +1534,16 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) { glm::vec2 p1 = screenPoints[edges[i][0]]; glm::vec2 p2 = screenPoints[edges[i][1]]; - if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0) + if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0 && + !camManager.IsScreenPointOccluded(p1, mainCamIdx) && + !camManager.IsScreenPointOccluded(p2, mainCamIdx)) { drawList->AddLine(ImVec2(p1.x, p1.y), ImVec2(p2.x, p2.y), color, 2.0f); } } } } + drawList->PopClipRect(); } void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) @@ -1510,10 +1552,27 @@ void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) Object* obj = phy->GetOwner(); if (!obj) return; - glm::mat4 view = Engine::GetCameraManager().GetViewMatrix(); - glm::mat4 proj = Engine::GetCameraManager().GetProjectionMatrix(); + CameraManager& camManager = Engine::GetCameraManager(); + int mainCamIdx = camManager.GetMainCameraIndex(); + Camera* mainCam = camManager.GetCamera(mainCamIdx); + + // Skip if camera is inactive + if (!mainCam || !mainCam->GetIsActive()) return; + + glm::mat4 view = mainCam->GetViewMatrix(); + glm::mat4 proj = mainCam->GetProjectionMatrix(); ImDrawList* drawList = ImGui::GetBackgroundDrawList(); + ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); + glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; + glm::vec2 windowSize = { imguiViewport->Size.x, imguiViewport->Size.y }; + ViewportRect vp = mainCam->GetViewport(); + + ImVec2 clipMin = { vp.x * windowSize.x + windowPos.x, vp.y * windowSize.y + windowPos.y }; + ImVec2 clipMax = { (vp.x + vp.width) * windowSize.x + windowPos.x, (vp.y + vp.height) * windowSize.y + windowPos.y }; + + drawList->PushClipRect(clipMin, clipMax); + ImU32 color; switch (phy->GetBodyType()) { @@ -1564,7 +1623,9 @@ void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) { glm::vec2 p1 = screenPoints[i]; glm::vec2 p2 = screenPoints[(i + 1) % screenPoints.size()]; - if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0) + if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0 && + !camManager.IsScreenPointOccluded(p1, mainCamIdx) && + !camManager.IsScreenPointOccluded(p2, mainCamIdx)) { drawList->AddLine(ImVec2(p1.x, p1.y), ImVec2(p2.x, p2.y), color, 2.0f); } @@ -1578,9 +1639,10 @@ void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) glm::vec2 screenEdge = Engine::GetRenderManager()->WorldToScreen(glm::vec3(scaledPos.x + radius, scaledPos.y, 0.f), view, proj); float screenRadius = glm::distance(screenCenter, screenEdge); - if (screenCenter.x >= 0 && screenCenter.y >= 0) + if (screenCenter.x >= 0 && screenCenter.y >= 0 && !camManager.IsScreenPointOccluded(screenCenter, mainCamIdx)) { drawList->AddCircle(ImVec2(screenCenter.x, screenCenter.y), screenRadius, color, 32, 2.0f); } } + drawList->PopClipRect(); } diff --git a/Engine/source/RenderManager.cpp b/Engine/source/RenderManager.cpp index 01d6a73c..485ce752 100644 --- a/Engine/source/RenderManager.cpp +++ b/Engine/source/RenderManager.cpp @@ -1253,7 +1253,7 @@ glm::mat4 RenderManager::Quantize( // return (-linear + std::sqrt(discriminant)) / (2.f * quadratic); //} -glm::vec2 RenderManager::WorldToScreen(glm::vec3 worldPos, const glm::mat4& view, const glm::mat4& proj) +glm::vec2 RenderManager::WorldToScreen(glm::vec3 worldPos, const glm::mat4& view, const glm::mat4& proj, Camera* camera) { // Transform world space to clip space glm::vec4 clipSpace = proj * view * glm::vec4(worldPos, 1.0f); @@ -1265,21 +1265,24 @@ glm::vec2 RenderManager::WorldToScreen(glm::vec3 worldPos, const glm::mat4& view glm::vec3 ndc = glm::vec3(clipSpace) / clipSpace.w; // Map NDC to screen coordinates using ImGui viewport data - ImGuiViewport* viewport = ImGui::GetMainViewport(); - glm::vec2 windowPos = { viewport->Pos.x, viewport->Pos.y }; - glm::vec2 windowSize = { viewport->Size.x, viewport->Size.y }; + ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); + glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; + glm::vec2 windowSize = { imguiViewport->Size.x, imguiViewport->Size.y }; - // Convert NDC to screen space and flip Y-axis for ImGui coordinate system - float screenX = (ndc.x + 1.0f) * 0.5f * windowSize.x + windowPos.x; + Camera* cam = camera ? camera : Engine::GetCameraManager().GetCamera(); + ViewportRect vp = cam->GetViewport(); + + // Convert NDC to screen space within the camera's viewport + float screenX = (ndc.x + 1.0f) * 0.5f * (windowSize.x * vp.width) + (windowSize.x * vp.x) + windowPos.x; float screenY = 0.0f; if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::VK) { - screenY = (ndc.y + 1.0f) * 0.5f * windowSize.y + windowPos.y; + screenY = (ndc.y + 1.0f) * 0.5f * (windowSize.y * vp.height) + (windowSize.y * vp.y) + windowPos.y; } else { - screenY = (1.0f - ndc.y) * 0.5f * windowSize.y + windowPos.y; + screenY = (1.0f - ndc.y) * 0.5f * (windowSize.y * vp.height) + (windowSize.y * vp.y) + windowPos.y; } return glm::vec2{ screenX, screenY }; diff --git a/Engine/source/VKRenderManager.cpp b/Engine/source/VKRenderManager.cpp index 92831cdd..97be52be 100644 --- a/Engine/source/VKRenderManager.cpp +++ b/Engine/source/VKRenderManager.cpp @@ -927,181 +927,237 @@ bool VKRenderManager::BeginRender(glm::vec3 bgColor) VkDeviceSize vertexBufferOffset{ 0 }; - switch (rMode) + size_t cameraCount = Engine::GetCameraManager().GetCameraCount(); + // @TODO Can I use dynamic polygon type (FILL or LINE)? + uint64_t subMeshIndex2D{ 0 }; + uint64_t subMeshIndex3D{ 0 }; + + void* vertexMappedMemory2D = nullptr; + void* fragmentMappedMemory2D = nullptr; + void* vertexMappedMemory3D = nullptr; + void* fragmentMappedMemory3D = nullptr; + void* materialMappedMemory3D = nullptr; + + if (rMode == RenderType::TwoDimension) { - case RenderType::TwoDimension: + vertexMappedMemory2D = uniformBuffer2D.vertexUniformBuffer->GetMappedMemory(frameIndex); + fragmentMappedMemory2D = uniformBuffer2D.fragmentUniformBuffer->GetMappedMemory(frameIndex); + } + else if (rMode == RenderType::ThreeDimension) { - void* vertexMappedMemory = uniformBuffer2D.vertexUniformBuffer->GetMappedMemory(frameIndex); - void* fragmentMappedMemory = uniformBuffer2D.fragmentUniformBuffer->GetMappedMemory(frameIndex); - - // Bind Pipeline - vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline2D->GetPipeLine()); - for (size_t i = 0; i < sprites.size(); ++i) - { - for (auto& subMesh : sprites[i]->GetSubMeshes()) - { - auto* spriteData = subMesh->GetData(); - auto* buffer = subMesh->GetBuffer(); - // Bind Vertex Buffer - vkCmdBindVertexBuffers(*currentCommandBuffer, 0, 1, buffer->vertexBuffer->GetVertexBuffer(), &vertexBufferOffset); - // Bind Index Buffer - vkCmdBindIndexBuffer(*currentCommandBuffer, *buffer->indexBuffer->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); - // Dynamic Viewport & Scissor - vkCmdSetViewport(*currentCommandBuffer, 0, 1, &viewport); - vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); - // Bind Vertex DescriptorSet - size_t alignment = vkInit->GetMinUniformBufferOffsetAlignment(); - size_t uniformSize = sizeof(TwoDimension::VertexUniform); - uint32_t dynamicOffset = static_cast(i * ((uniformSize + alignment - 1) & ~(alignment - 1))); - - TwoDimension::VertexUniform* vertexDest = (TwoDimension::VertexUniform*)((uint8_t*)vertexMappedMemory + dynamicOffset); - *vertexDest = spriteData->vertexUniform; - // @TODO do not use magic number for dynamicOffsetCount - vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline2D->GetPipeLineLayout(), 0, 1, currentVertexDescriptorSet, 1, &dynamicOffset); - - // Bind Fragment DescriptorSet - uniformSize = sizeof(TwoDimension::FragmentUniform); - dynamicOffset = static_cast(i * ((uniformSize + alignment - 1) & ~(alignment - 1))); - - TwoDimension::FragmentUniform* fragmentDest = (TwoDimension::FragmentUniform*)((uint8_t*)fragmentMappedMemory + dynamicOffset); - *fragmentDest = spriteData->fragmentUniform; - - // @TODO do not use magic number for dynamicOffsetCount - vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline2D->GetPipeLineLayout(), 1, 1, currentFragmentDescriptorSet, 1, &dynamicOffset); - // Change Primitive Topology - //vkCmdSetPrimitiveTopology(*currentCommandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); - //Draw - vkCmdDrawIndexed(*currentCommandBuffer, static_cast(spriteData->indices.size()), 1, 0, 0, 0); - } - } - uniformBuffer2D.vertexUniformBuffer->UnmapMemory(frameIndex); - uniformBuffer2D.fragmentUniformBuffer->UnmapMemory(frameIndex); + vertexMappedMemory3D = uniformBuffer3D.vertexUniformBuffer->GetMappedMemory(frameIndex); + fragmentMappedMemory3D = uniformBuffer3D.fragmentUniformBuffer->GetMappedMemory(frameIndex); + materialMappedMemory3D = uniformBuffer3D.materialUniformBuffer->GetMappedMemory(frameIndex); } - break; - case RenderType::ThreeDimension: - void* vertexMappedMemory = uniformBuffer3D.vertexUniformBuffer->GetMappedMemory(frameIndex); - void* fragmentMappedMemory = uniformBuffer3D.fragmentUniformBuffer->GetMappedMemory(frameIndex); - void* materialMappedMemory = uniformBuffer3D.materialUniformBuffer->GetMappedMemory(frameIndex); - // @TODO Can I use dynamic polygon type (FILL or LINE)? - uint64_t subMeshIndex{ 0 }; - for (const auto& sprite : sprites) + for (size_t c = 0; c < cameraCount; ++c) + { + Camera* cam = Engine::GetCameraManager().GetCamera(static_cast(c)); + if (cam == nullptr || !cam->GetIsActive()) continue; + + ViewportRect vp = cam->GetViewport(); + viewport.x = vp.x * static_cast(vkSwapChain->GetSwapChainImageExtent()->width); + viewport.y = vp.y * static_cast(vkSwapChain->GetSwapChainImageExtent()->height); + viewport.width = vp.width * static_cast(vkSwapChain->GetSwapChainImageExtent()->width); + viewport.height = vp.height * static_cast(vkSwapChain->GetSwapChainImageExtent()->height); + vkCmdSetViewport(*currentCommandBuffer, 0, 1, &viewport); + + // Clear only the camera's viewport region + VkClearAttachment clearAttachments[2] = {}; + clearAttachments[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clearAttachments[0].colorAttachment = 0; + clearAttachments[0].clearValue.color = { {bgColor.r, bgColor.g, bgColor.b, 1.f} }; + clearAttachments[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + clearAttachments[1].clearValue.depthStencil = { 1.f, 0 }; + + VkClearRect clearRect = {}; + clearRect.rect.offset = { static_cast(viewport.x), static_cast(viewport.y) }; + clearRect.rect.extent = { static_cast(viewport.width), static_cast(viewport.height) }; + clearRect.baseArrayLayer = 0; + clearRect.layerCount = 1; + + vkCmdClearAttachments(*currentCommandBuffer, 2, clearAttachments, 1, &clearRect); + + switch (rMode) + { + case RenderType::TwoDimension: { - for (auto& subMesh : sprite->GetSubMeshes()) + // Bind Pipeline + vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline2D->GetPipeLine()); + for (size_t i = 0; i < sprites.size(); ++i) { - auto* spriteData = subMesh->GetData(); - auto* buffer = subMesh->GetBuffer(); - // Bind Pipeline - auto* pipeline = pMode == PolygonType::FILL ? vkPipeline3D : vkPipeline3DLine; - vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline->GetPipeLine()); - // Bind Vertex Buffer - vkCmdBindVertexBuffers(*currentCommandBuffer, 0, 1, buffer->vertexBuffer->GetVertexBuffer(), &vertexBufferOffset); - // Bind Index Buffer - vkCmdBindIndexBuffer(*currentCommandBuffer, *buffer->indexBuffer->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); - // Dynamic Viewport & Scissor - vkCmdSetViewport(*currentCommandBuffer, 0, 1, &viewport); - vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); - // Bind Vertex DescriptorSet - size_t alignment = vkInit->GetMinUniformBufferOffsetAlignment(); - size_t uniformSize = sizeof(ThreeDimension::VertexUniform); - uint32_t dynamicOffset = static_cast(subMeshIndex * ((uniformSize + alignment - 1) & ~(alignment - 1))); - - ThreeDimension::VertexUniform* vertexDest = (ThreeDimension::VertexUniform*)((uint8_t*)vertexMappedMemory + dynamicOffset); - *vertexDest = spriteData->vertexUniform; - // @TODO do not use magic number for dynamicOffsetCount - vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline->GetPipeLineLayout(), 0, 1, currentVertexDescriptorSet, 1, &dynamicOffset); - - // Bind Fragment DescriptorSet - uint32_t dynamicOffsets[2]; - // Fragment Uniform Offset - uniformSize = sizeof(ThreeDimension::FragmentUniform); - dynamicOffset = static_cast(subMeshIndex * ((uniformSize + alignment - 1) & ~(alignment - 1))); - dynamicOffsets[0] = dynamicOffset; - - ThreeDimension::FragmentUniform* fragmentDest = (ThreeDimension::FragmentUniform*)((uint8_t*)fragmentMappedMemory + dynamicOffset); - *fragmentDest = spriteData->fragmentUniform; - - // Material Uniform Offset - uniformSize = sizeof(ThreeDimension::Material); - dynamicOffset = static_cast(subMeshIndex * ((uniformSize + alignment - 1) & ~(alignment - 1))); - dynamicOffsets[1] = dynamicOffset; - - ThreeDimension::Material* materialDest = (ThreeDimension::Material*)((uint8_t*)materialMappedMemory + dynamicOffset); - *materialDest = spriteData->material; - - // @TODO do not use magic number for dynamicOffsetCount - vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline->GetPipeLineLayout(), 1, 1, currentFragmentDescriptorSet, 2, dynamicOffsets); - - // Change Primitive Topology - // vkCmdSetPrimitiveTopology(*currentCommandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); - // vkCmdSetPrimitiveTopology(*currentCommandBuffer, VK_PRIMITIVE_TOPOLOGY_LINE_LIST); - // Push Constant Active Lights - pushConstants.activeDirectionalLight = static_cast(directionalLightUniforms.size()); - pushConstants.activePointLight = static_cast(pointLightUniforms.size()); - vkCmdPushConstants(*currentCommandBuffer, *pipeline->GetPipeLineLayout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &pushConstants); - // Draw - vkCmdDrawIndexed(*currentCommandBuffer, static_cast(spriteData->indices.size()), 1, 0, 0, 0); - -#ifdef _DEBUG - if (m_normalVectorVisualization) + for (auto& subMesh : sprites[i]->GetSubMeshes()) { - VkBuffer* normalVertexBuffer = buffer->normalVertexBuffer->GetVertexBuffer(); - //Bind Pipeline - vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DNormal->GetPipeLine()); - //Bind Vertex Buffer - vkCmdBindVertexBuffers(*currentCommandBuffer, 0, 1, normalVertexBuffer, &vertexBufferOffset); - //Dynamic Viewport & Scissor - vkCmdSetViewport(*currentCommandBuffer, 0, 1, &viewport); + auto* spriteData = subMesh->GetData(); + auto* buffer = subMesh->GetBuffer(); + + spriteData->vertexUniform.view = cam->GetViewMatrix(); + if (sprites[i]->GetSpriteDrawType() == SpriteDrawType::UI) + { + glm::vec2 cameraViewSize = cam->GetViewSize(); + spriteData->vertexUniform.projection = glm::ortho(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); + // Flip y-axis for Vulkan + spriteData->vertexUniform.projection[1][1] *= -1; + spriteData->vertexUniform.view = glm::mat4(1.f); + } + else + { + spriteData->vertexUniform.projection = cam->GetProjectionMatrix(); + } + + // Bind Vertex Buffer + vkCmdBindVertexBuffers(*currentCommandBuffer, 0, 1, buffer->vertexBuffer->GetVertexBuffer(), &vertexBufferOffset); + // Bind Index Buffer + vkCmdBindIndexBuffer(*currentCommandBuffer, *buffer->indexBuffer->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); + // Dynamic Viewport & Scissor (set above per camera, scissor is same) + // vkCmdSetViewport(*currentCommandBuffer, 0, 1, &viewport); vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); + + // Bind Vertex DescriptorSet + size_t alignment = vkInit->GetMinUniformBufferOffsetAlignment(); + size_t uniformSize = sizeof(TwoDimension::VertexUniform); + uint32_t dynamicOffset = static_cast(subMeshIndex2D * ((uniformSize + alignment - 1) & ~(alignment - 1))); + + TwoDimension::VertexUniform* vertexDest = (TwoDimension::VertexUniform*)((uint8_t*)vertexMappedMemory2D + dynamicOffset); + *vertexDest = spriteData->vertexUniform; + vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline2D->GetPipeLineLayout(), 0, 1, currentVertexDescriptorSet, 1, &dynamicOffset); - // Bind Vertex DescriptorSet (for bone matrices) - // Recalculate the vertex uniform dynamic offset (dynamicOffset was overwritten by the fragment uniform calc above) - size_t normalAlignment = vkInit->GetMinUniformBufferOffsetAlignment(); - size_t normalUniformSize = sizeof(ThreeDimension::VertexUniform); - uint32_t normalDynamicOffset = static_cast(subMeshIndex * ((normalUniformSize + normalAlignment - 1) & ~(normalAlignment - 1))); - VkDescriptorSet* normalVertexDescriptorSet = &(*vkDescriptor3DNormal->GetVertexDescriptorSets())[frameIndex]; - vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DNormal->GetPipeLineLayout(), 0, 1, normalVertexDescriptorSet, 1, &normalDynamicOffset); - - //Push Constant Model-To_NDC - auto& vertexUniform = spriteData->vertexUniform; - glm::mat4 modelToNDC = vertexUniform.projection * vertexUniform.view * vertexUniform.model; - vkCmdPushConstants(*currentCommandBuffer, *vkPipeline3DNormal->GetPipeLineLayout(), VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &modelToNDC); + // Bind Fragment DescriptorSet + uniformSize = sizeof(TwoDimension::FragmentUniform); + dynamicOffset = static_cast(subMeshIndex2D * ((uniformSize + alignment - 1) & ~(alignment - 1))); + + TwoDimension::FragmentUniform* fragmentDest = (TwoDimension::FragmentUniform*)((uint8_t*)fragmentMappedMemory2D + dynamicOffset); + *fragmentDest = spriteData->fragmentUniform; + + vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline2D->GetPipeLineLayout(), 1, 1, currentFragmentDescriptorSet, 1, &dynamicOffset); //Draw - vkCmdDraw(*currentCommandBuffer, static_cast(spriteData->normalVertices.size()), 1, 0, 0); + vkCmdDrawIndexed(*currentCommandBuffer, static_cast(spriteData->indices.size()), 1, 0, 0, 0); + + subMeshIndex2D++; } + } + } + break; + case RenderType::ThreeDimension: + for (const auto& sprite : sprites) + { + for (auto& subMesh : sprite->GetSubMeshes()) + { + auto* spriteData = subMesh->GetData(); + auto* buffer = subMesh->GetBuffer(); + + spriteData->vertexUniform.view = cam->GetViewMatrix(); + spriteData->vertexUniform.projection = cam->GetProjectionMatrix(); + glm::mat4 inverseView = glm::inverse(spriteData->vertexUniform.view); + spriteData->vertexUniform.viewPosition = glm::vec4(inverseView[3].x, inverseView[3].y, inverseView[3].z, 1.0f); + + // Bind Pipeline + auto* pipeline = pMode == PolygonType::FILL ? vkPipeline3D : vkPipeline3DLine; + vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline->GetPipeLine()); + // Bind Vertex Buffer + vkCmdBindVertexBuffers(*currentCommandBuffer, 0, 1, buffer->vertexBuffer->GetVertexBuffer(), &vertexBufferOffset); + // Bind Index Buffer + vkCmdBindIndexBuffer(*currentCommandBuffer, *buffer->indexBuffer->GetIndexBuffer(), 0, VK_INDEX_TYPE_UINT32); + // Dynamic Viewport & Scissor (viewport set above) + vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); + + // Bind Vertex DescriptorSet + size_t alignment = vkInit->GetMinUniformBufferOffsetAlignment(); + size_t uniformSize = sizeof(ThreeDimension::VertexUniform); + uint32_t dynamicOffset = static_cast(subMeshIndex3D * ((uniformSize + alignment - 1) & ~(alignment - 1))); + + ThreeDimension::VertexUniform* vertexDest = (ThreeDimension::VertexUniform*)((uint8_t*)vertexMappedMemory3D + dynamicOffset); + *vertexDest = spriteData->vertexUniform; + vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline->GetPipeLineLayout(), 0, 1, currentVertexDescriptorSet, 1, &dynamicOffset); + + // Bind Fragment DescriptorSet + uint32_t dynamicOffsets[2]; + // Fragment Uniform Offset + uniformSize = sizeof(ThreeDimension::FragmentUniform); + dynamicOffset = static_cast(subMeshIndex3D * ((uniformSize + alignment - 1) & ~(alignment - 1))); + dynamicOffsets[0] = dynamicOffset; + + ThreeDimension::FragmentUniform* fragmentDest = (ThreeDimension::FragmentUniform*)((uint8_t*)fragmentMappedMemory3D + dynamicOffset); + *fragmentDest = spriteData->fragmentUniform; + + // Material Uniform Offset + uniformSize = sizeof(ThreeDimension::Material); + dynamicOffset = static_cast(subMeshIndex3D * ((uniformSize + alignment - 1) & ~(alignment - 1))); + dynamicOffsets[1] = dynamicOffset; + + ThreeDimension::Material* materialDest = (ThreeDimension::Material*)((uint8_t*)materialMappedMemory3D + dynamicOffset); + *materialDest = spriteData->material; + + vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline->GetPipeLineLayout(), 1, 1, currentFragmentDescriptorSet, 2, dynamicOffsets); + + pushConstants.activeDirectionalLight = static_cast(directionalLightUniforms.size()); + pushConstants.activePointLight = static_cast(pointLightUniforms.size()); + vkCmdPushConstants(*currentCommandBuffer, *pipeline->GetPipeLineLayout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstants), &pushConstants); + // Draw + vkCmdDrawIndexed(*currentCommandBuffer, static_cast(spriteData->indices.size()), 1, 0, 0, 0); + +#ifdef _DEBUG + if (m_normalVectorVisualization) + { + VkBuffer* normalVertexBuffer = buffer->normalVertexBuffer->GetVertexBuffer(); + //Bind Pipeline + vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DNormal->GetPipeLine()); + //Bind Vertex Buffer + vkCmdBindVertexBuffers(*currentCommandBuffer, 0, 1, normalVertexBuffer, &vertexBufferOffset); + //Dynamic Viewport & Scissor (viewport set above) + vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); + + // Bind Vertex DescriptorSet (for bone matrices) + size_t normalAlignment = vkInit->GetMinUniformBufferOffsetAlignment(); + size_t normalUniformSize = sizeof(ThreeDimension::VertexUniform); + uint32_t normalDynamicOffset = static_cast(subMeshIndex3D * ((normalUniformSize + normalAlignment - 1) & ~(normalAlignment - 1))); + VkDescriptorSet* normalVertexDescriptorSet = &(*vkDescriptor3DNormal->GetVertexDescriptorSets())[frameIndex]; + vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DNormal->GetPipeLineLayout(), 0, 1, normalVertexDescriptorSet, 1, &normalDynamicOffset); + + //Push Constant Model-To_NDC + auto& vertexUniform = spriteData->vertexUniform; + glm::mat4 modelToNDC = vertexUniform.projection * vertexUniform.view * vertexUniform.model; + vkCmdPushConstants(*currentCommandBuffer, *vkPipeline3DNormal->GetPipeLineLayout(), VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &modelToNDC); + //Draw + vkCmdDraw(*currentCommandBuffer, static_cast(spriteData->normalVertices.size()), 1, 0, 0); + } #endif - subMeshIndex++; + subMeshIndex3D++; + } } + + if (m_skyboxEnabled) + { + //Skybox + //Bind Vertex Buffer + vkCmdBindVertexBuffers(*currentCommandBuffer, 0, 1, skyboxVertexBuffer->GetVertexBuffer(), &vertexBufferOffset); + //Bind Pipeline + vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DSkybox->GetPipeLine()); + //Dynamic Viewport & Scissor (viewport set above) + vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); + //Bind Vertex DescriptorSet + vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DSkybox->GetPipeLineLayout(), 0, 1, currentFragmentSkyboxDescriptorSet, 0, nullptr); + //Push Constant World-To_NDC + glm::mat4 transform[2] = { cam->GetViewMatrix(), cam->GetProjectionMatrix() }; + vkCmdPushConstants(*currentCommandBuffer, *vkPipeline3DSkybox->GetPipeLineLayout(), VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4) * 2, &transform[0]); + //Draw + vkCmdDraw(*currentCommandBuffer, 36, 1, 0, 0); + } + break; } + } + if (rMode == RenderType::TwoDimension) + { + uniformBuffer2D.vertexUniformBuffer->UnmapMemory(frameIndex); + uniformBuffer2D.fragmentUniformBuffer->UnmapMemory(frameIndex); + } + else if (rMode == RenderType::ThreeDimension) + { uniformBuffer3D.vertexUniformBuffer->UnmapMemory(frameIndex); uniformBuffer3D.fragmentUniformBuffer->UnmapMemory(frameIndex); uniformBuffer3D.materialUniformBuffer->UnmapMemory(frameIndex); - - if (m_skyboxEnabled) - { - //Skybox - //Bind Vertex Buffer - vkCmdBindVertexBuffers(*currentCommandBuffer, 0, 1, skyboxVertexBuffer->GetVertexBuffer(), &vertexBufferOffset); - //Bind Pipeline - vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DSkybox->GetPipeLine()); - //Dynamic Viewport & Scissor - vkCmdSetViewport(*currentCommandBuffer, 0, 1, &viewport); - vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); - //Bind Vertex DescriptorSet - vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DSkybox->GetPipeLineLayout(), 0, 1, currentFragmentSkyboxDescriptorSet, 0, nullptr); - //Change Primitive Topology - //vkCmdSetPrimitiveTopology(*currentCommandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); - //Push Constant World-To_NDC - glm::mat4 transform[2] = { Engine::GetCameraManager().GetViewMatrix(), Engine::GetCameraManager().GetProjectionMatrix() }; - vkCmdPushConstants(*currentCommandBuffer, *vkPipeline3DSkybox->GetPipeLineLayout(), VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4) * 2, &transform[0]); - //Draw - //vkCmdDrawIndexed(*currentCommandBuffer, static_cast(indices.size()), 1, 0, 0, 0); - vkCmdDraw(*currentCommandBuffer, 36, 1, 0, 0); - } - - break; } imguiManager->Begin(); diff --git a/Game/source/3DPhysicsDemo.cpp b/Game/source/3DPhysicsDemo.cpp index 3bd389dd..7f973e92 100644 --- a/Game/source/3DPhysicsDemo.cpp +++ b/Game/source/3DPhysicsDemo.cpp @@ -26,12 +26,12 @@ void PhysicsDemo::Init3D() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); - Engine::GetCameraManager().SetNear(0.001f); - Engine::GetCameraManager().SetFar(1000.f); - Engine::GetCameraManager().SetBaseFov(22.5f); - Engine::GetCameraManager().SetCameraSensitivity(10.f); - Engine::GetCameraManager().SetCameraPosition({ 0.f,2.f,13.f }); - Engine::GetCameraManager().SetTarget(glm::vec3{ 0.f, 0.f,0.f }); + Engine::GetCameraManager().GetCamera()->SetNear(0.001f); + Engine::GetCameraManager().GetCamera()->SetFar(1000.f); + Engine::GetCameraManager().GetCamera()->SetBaseFov(22.5f); + Engine::GetCameraManager().GetCamera()->SetCameraSensitivity(10.f); + Engine::GetCameraManager().GetCamera()->SetCameraPosition({ 0.f,2.f,13.f }); + Engine::GetCameraManager().GetCamera()->SetTarget(glm::vec3{ 0.f, 0.f,0.f }); Engine::GetObjectManager().AddObject(glm::vec3{ 0.f,-2.f,0.f }, glm::vec3{ 20.f,20.f,1.f }, "PLANE", ObjectType::NONE); Engine::GetObjectManager().GetLastObject()->SetXRotate(90.f); @@ -95,19 +95,19 @@ void PhysicsDemo::Update(float dt) if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::UP)) { - movement += Engine::GetCameraManager().GetBackVector() * speed; + movement += Engine::GetCameraManager().GetCamera()->GetBackVector() * speed; } if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::DOWN)) { - movement -= Engine::GetCameraManager().GetBackVector() * speed; + movement -= Engine::GetCameraManager().GetCamera()->GetBackVector() * speed; } if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::RIGHT)) { - movement += Engine::GetCameraManager().GetRightVector() * speed; + movement += Engine::GetCameraManager().GetCamera()->GetRightVector() * speed; } if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::LEFT)) { - movement -= Engine::GetCameraManager().GetRightVector() * speed; + movement -= Engine::GetCameraManager().GetCamera()->GetRightVector() * speed; } diff --git a/Game/source/Background.cpp b/Game/source/Background.cpp index 7a1188ad..b4a397fb 100644 --- a/Game/source/Background.cpp +++ b/Game/source/Background.cpp @@ -4,10 +4,10 @@ bool isInOfCamera(Background& back) { - glm::vec2 windowSize = { static_cast(Engine::GetCameraManager().GetViewSize().x), - static_cast(Engine::GetCameraManager().GetViewSize().y) }; - windowSize = (windowSize / Engine::GetCameraManager().GetZoom()); - glm::vec2 cameraCenter = Engine::GetCameraManager().GetCenter(); + glm::vec2 windowSize = { static_cast(Engine::GetCameraManager().GetCamera()->GetViewSize().x), + static_cast(Engine::GetCameraManager().GetCamera()->GetViewSize().y) }; + windowSize = (windowSize / Engine::GetCameraManager().GetCamera()->GetZoom()); + glm::vec2 cameraCenter = Engine::GetCameraManager().GetCamera()->GetCenter(); if ((back.position.x - (back.size.x)) < (windowSize.x / 2.f + cameraCenter.x) && (back.position.x + (back.size.x)) > -(windowSize.x / 2.f - cameraCenter.x) && (back.position.y - (back.size.y)) < (windowSize.y / 2.f + cameraCenter.y) && (back.position.y + (back.size.y)) > -(windowSize.y / 2.f - cameraCenter.y)) { @@ -18,10 +18,10 @@ bool isInOfCamera(Background& back) bool isInOfCameraE(Background& back) { - glm::vec2 windowSize = { static_cast(Engine::GetCameraManager().GetViewSize().x), - static_cast(Engine::GetCameraManager().GetViewSize().y) }; - windowSize = (windowSize / Engine::GetCameraManager().GetZoom()); - glm::vec2 cameraCenter = Engine::GetCameraManager().GetCenter(); + glm::vec2 windowSize = { static_cast(Engine::GetCameraManager().GetCamera()->GetViewSize().x), + static_cast(Engine::GetCameraManager().GetCamera()->GetViewSize().y) }; + windowSize = (windowSize / Engine::GetCameraManager().GetCamera()->GetZoom()); + glm::vec2 cameraCenter = Engine::GetCameraManager().GetCamera()->GetCenter(); if (back.position.x - (back.size.x) < (windowSize.x / 2.f + cameraCenter.x) && back.position.x + (back.size.x) > -(windowSize.x / 2.f - cameraCenter.x) && back.position.y - (back.size.y) < (windowSize.y / 2.f + cameraCenter.y) && back.position.y + (back.size.y) > -(windowSize.y / 2.f - cameraCenter.y)) @@ -80,7 +80,7 @@ void BackgroundManager::AddVerticalParallexBackground(std::string spriteName_, s saveb.isAnimation = isAnimated; //saveBackgroundList[groupName].push_back(saveb); - glm::vec2 windowSize = { static_cast(Engine::GetCameraManager().GetViewSize().x) , static_cast(Engine::GetCameraManager().GetViewSize().y) }; + glm::vec2 windowSize = { static_cast(Engine::GetCameraManager().GetCamera()->GetViewSize().x) , static_cast(Engine::GetCameraManager().GetCamera()->GetViewSize().y) }; int amount = static_cast(windowSize.y / (size_.y * 2.f)) + 1; if (windowSize.y < (size_.y * 2.f)) { @@ -136,7 +136,7 @@ void BackgroundManager::AddHorizonParallexBackground(std::string spriteName_, st saveb.type = BackgroundType::VPARALLEX; // ʿ //saveBackgroundList[groupName].push_back(saveb); - glm::vec2 windowSize = { static_cast(Engine::GetCameraManager().GetViewSize().x) , static_cast(Engine::GetCameraManager().GetViewSize().y) }; + glm::vec2 windowSize = { static_cast(Engine::GetCameraManager().GetCamera()->GetViewSize().x) , static_cast(Engine::GetCameraManager().GetCamera()->GetViewSize().y) }; int amount = static_cast(windowSize.x / (size_.x )) + 1; if (windowSize.x < (size_.x)) { @@ -235,10 +235,10 @@ void BackgroundManager::Update(float dt) } parallax.position.x -= parallax.speed.x * dt; - if (parallax.position.x <= -(Engine::GetCameraManager().GetViewSize().x / 2.f) - + Engine::GetCameraManager().GetCenter().x - parallax.size.x / 2.f) + if (parallax.position.x <= -(Engine::GetCameraManager().GetCamera()->GetViewSize().x / 2.f) + + Engine::GetCameraManager().GetCamera()->GetCenter().x - parallax.size.x / 2.f) { - parallax.position.x = Engine::GetCameraManager().GetViewSize().x / 2.f + parallax.size.x / 2.f + Engine::GetCameraManager().GetCenter().x; + parallax.position.x = Engine::GetCameraManager().GetCamera()->GetViewSize().x / 2.f + parallax.size.x / 2.f + Engine::GetCameraManager().GetCamera()->GetCenter().x; } } } diff --git a/Game/source/BeatEmUpDemo/BEUPlayer.cpp b/Game/source/BeatEmUpDemo/BEUPlayer.cpp index c9a46630..c2a10df7 100644 --- a/Game/source/BeatEmUpDemo/BEUPlayer.cpp +++ b/Game/source/BeatEmUpDemo/BEUPlayer.cpp @@ -112,7 +112,7 @@ void BEUPlayer::Update(float dt) invincibleDelay = 0.f; } } - glm::vec3 cameraPos = Engine::GetCameraManager().GetCameraPosition(); + glm::vec3 cameraPos = Engine::GetCameraManager().GetCamera()->GetCameraPosition(); if (position.x < cameraPos.x - 11.f) { position.x = -11.f; @@ -318,9 +318,9 @@ void BEUPlayer::Control(float dt) SetStateOn(BEUObjectStates::MOVEFOWARD); SetXPosition(GetPosition().x + 10.f * dt); /* - if (beatEmUpDemoSystem->GetIsCameraMoveAble() == true && Engine::GetCameraManager().GetCameraPosition().x < position.x) + if (beatEmUpDemoSystem->GetIsCameraMoveAble() == true && Engine::GetCameraManager().GetCamera()->GetCameraPosition().x < position.x) { - Engine::GetCameraManager().SetCameraPosition({ position.x , Engine::GetCameraManager().GetCameraPosition().y, Engine::GetCameraManager().GetCameraPosition().z }); + Engine::GetCameraManager().GetCamera()->SetCameraPosition({ position.x , Engine::GetCameraManager().GetCamera()->GetCameraPosition().y, Engine::GetCameraManager().GetCamera()->GetCameraPosition().z }); }*/ } if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::X) diff --git a/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp b/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp index 20a5e3e3..2cd20f88 100644 --- a/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp +++ b/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp @@ -19,10 +19,10 @@ void BeatEmUpDemo::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::TwoDimension); Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); - Engine::GetCameraManager().SetFar(91.f); - Engine::GetCameraManager().SetBaseFov(45.f); - Engine::GetCameraManager().SetCameraPosition({ 0.f,10.f, 30.f }); - Engine::GetCameraManager().UpdaetCameraDirectrion({ 0.f, 10.f }); + Engine::GetCameraManager().GetCamera()->SetFar(91.f); + Engine::GetCameraManager().GetCamera()->SetBaseFov(45.f); + Engine::GetCameraManager().GetCamera()->SetCameraPosition({ 0.f,10.f, 30.f }); + Engine::GetCameraManager().GetCamera()->UpdateCameraDirection({ 0.f, 10.f }); Engine::GetRenderManager()->LoadTexture("../Game/assets/BeatEmUpDemo/road.png", "road", true); Engine::GetRenderManager()->LoadTexture("../Game/assets/BeatEmUpDemo/road1.png", "road1", true); @@ -121,19 +121,19 @@ void BeatEmUpDemo::Update(float dt) } if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::W)) { - Engine::GetCameraManager().MoveCameraPos(CameraMoveDir::FOWARD, 10.f * dt); + Engine::GetCameraManager().GetCamera()->MoveCameraPos(CameraMoveDir::FOWARD, 10.f * dt); } if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::S)) { - Engine::GetCameraManager().MoveCameraPos(CameraMoveDir::BACKWARD, 10.f * dt); + Engine::GetCameraManager().GetCamera()->MoveCameraPos(CameraMoveDir::BACKWARD, 10.f * dt); } if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::A)) { - Engine::GetCameraManager().MoveCameraPos(CameraMoveDir::LEFT, 10.f * dt); + Engine::GetCameraManager().GetCamera()->MoveCameraPos(CameraMoveDir::LEFT, 10.f * dt); } if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::D)) { - Engine::GetCameraManager().MoveCameraPos(CameraMoveDir::RIGHT, 10.f * dt); + Engine::GetCameraManager().GetCamera()->MoveCameraPos(CameraMoveDir::RIGHT, 10.f * dt); } if (Engine::GetInputSnapshot().IsKeyPressOnce(KEYBOARDKEYS::Q)) { diff --git a/Game/source/BeatEmUpDemo/BeatEmUpDemoSystem.cpp b/Game/source/BeatEmUpDemo/BeatEmUpDemoSystem.cpp index fa7b3af5..ed8174f4 100644 --- a/Game/source/BeatEmUpDemo/BeatEmUpDemoSystem.cpp +++ b/Game/source/BeatEmUpDemo/BeatEmUpDemoSystem.cpp @@ -30,8 +30,8 @@ void BeatEmUpDemoSystem::Init() void BeatEmUpDemoSystem::Update(float dt) { - glm::vec2 viewSize = Engine::GetCameraManager().GetViewSize(); - glm::vec2 center = Engine::GetCameraManager().GetCenter(); + glm::vec2 viewSize = Engine::GetCameraManager().GetCamera()->GetViewSize(); + glm::vec2 center = Engine::GetCameraManager().GetCamera()->GetCenter(); healthBar->UpdateModel({ (-viewSize.x / 2.f + 320.f) + center.x + 32.f - (320.f - (320.f * (1.f / maxHp * hp)) / 2.f) , (viewSize.y / 2.f - 64.f) + center.y, 0.f }, { 320.f * (1.f / maxHp * hp), 32.f, 0.f }, 0.f); healthBar->UpdateProjection(); healthBar->UpdateView(); diff --git a/Game/source/MultipleLights.cpp b/Game/source/MultipleLights.cpp index 3efa1490..2e33da0d 100644 --- a/Game/source/MultipleLights.cpp +++ b/Game/source/MultipleLights.cpp @@ -10,12 +10,12 @@ void MultipleLights::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); - Engine::GetCameraManager().SetNear(0.01f); - Engine::GetCameraManager().SetFar(1000.f); - Engine::GetCameraManager().SetBaseFov(22.5f); - Engine::GetCameraManager().SetCameraSensitivity(10.f); - Engine::GetCameraManager().SetCameraPosition({ 20.f,15.f,20.f }); - Engine::GetCameraManager().SetTarget(glm::vec3{ 0.f, 5.f,0.f }); + Engine::GetCameraManager().GetCamera()->SetNear(0.01f); + Engine::GetCameraManager().GetCamera()->SetFar(1000.f); + Engine::GetCameraManager().GetCamera()->SetBaseFov(22.5f); + Engine::GetCameraManager().GetCamera()->SetCameraSensitivity(10.f); + Engine::GetCameraManager().GetCamera()->SetCameraPosition({ 20.f,15.f,20.f }); + Engine::GetCameraManager().GetCamera()->SetTarget(glm::vec3{ 0.f, 5.f,0.f }); // Plane Engine::GetObjectManager().AddObject(glm::vec3{ 0.f,0.f,0.f }, glm::vec3{ 20.f,20.f,1.f }, "PLANE", ObjectType::NONE); diff --git a/Game/source/PBR.cpp b/Game/source/PBR.cpp index 3f8fc757..8c09f0b7 100644 --- a/Game/source/PBR.cpp +++ b/Game/source/PBR.cpp @@ -11,12 +11,12 @@ void PBR::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); - Engine::GetCameraManager().SetNear(cNear); - Engine::GetCameraManager().SetFar(cFar); - Engine::GetCameraManager().SetBaseFov(cFov); - Engine::GetCameraManager().SetCameraSensitivity(10.f); - Engine::GetCameraManager().SetCameraPosition({ 0.f,0.f,13.f }); - //Engine::GetCameraManager().SetCameraPosition(glm::vec3{ 2.f, 2.f, 2.f }); + Engine::GetCameraManager().GetCamera()->SetNear(cNear); + Engine::GetCameraManager().GetCamera()->SetFar(cFar); + Engine::GetCameraManager().GetCamera()->SetBaseFov(cFov); + Engine::GetCameraManager().GetCamera()->SetCameraSensitivity(10.f); + Engine::GetCameraManager().GetCamera()->SetCameraPosition({ 0.f,0.f,13.f }); + //Engine::GetCameraManager().GetCamera()->SetCameraPosition(glm::vec3{ 2.f, 2.f, 2.f }); //Engine::GetCameraManager().SetCenter(glm::vec3{ 0.f, 0.f, 0.f }); //Debug Lighting diff --git a/Game/source/PlatformDemo/PlatformDemo.cpp b/Game/source/PlatformDemo/PlatformDemo.cpp index 79aa3565..cdc2e5e8 100644 --- a/Game/source/PlatformDemo/PlatformDemo.cpp +++ b/Game/source/PlatformDemo/PlatformDemo.cpp @@ -57,6 +57,7 @@ void PlatformDemo::Update(float dt) void PlatformDemo::ImGuiDraw(float /*dt*/) { Engine::GetSoundManager().MusicPlayerForImGui(0); + Engine::GetCameraManager().CameraControllerImGui(); //platformDemoSystem->UpdateMapEditorImGui(); } diff --git a/Game/source/PlatformDemo/PlatformDemoSystem.cpp b/Game/source/PlatformDemo/PlatformDemoSystem.cpp index 60901a29..2c73f404 100644 --- a/Game/source/PlatformDemo/PlatformDemoSystem.cpp +++ b/Game/source/PlatformDemo/PlatformDemoSystem.cpp @@ -620,8 +620,8 @@ void PlatformDemoSystem::Init() void PlatformDemoSystem::Update(float dt) { - glm::vec2 viewSize = Engine::GetCameraManager().GetViewSize(); - glm::vec2 center = Engine::GetCameraManager().GetCenter(); + glm::vec2 viewSize = Engine::GetCameraManager().GetCamera()->GetViewSize(); + glm::vec2 center = Engine::GetCameraManager().GetCamera()->GetCenter(); healthBar->UpdateModel({ (-viewSize.x / 2.f + 320.f) + center.x - (320.f - (320.f * (1.f / maxHp * hp)) / 2.f) , (viewSize.y / 2.f - 128.f) + center.y, 0.f }, { 320.f * (1.f / maxHp * hp), 64.f, 0.f }, 0.f); healthBar->UpdateProjection(); healthBar->UpdateView(); diff --git a/Game/source/ProceduralMeshes.cpp b/Game/source/ProceduralMeshes.cpp index 9b7d19ac..41554dfd 100644 --- a/Game/source/ProceduralMeshes.cpp +++ b/Game/source/ProceduralMeshes.cpp @@ -15,12 +15,12 @@ void ProceduralMeshes::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); - Engine::GetCameraManager().SetNear(cNear); - Engine::GetCameraManager().SetFar(cFar); - Engine::GetCameraManager().SetBaseFov(cFov); - Engine::GetCameraManager().SetCameraSensitivity(10.f); - Engine::GetCameraManager().SetCameraPosition({ 0.f,0.f,10.f }); - //Engine::GetCameraManager().SetCameraPosition(glm::vec3{ 2.f, 2.f, 2.f }); + Engine::GetCameraManager().GetCamera()->SetNear(cNear); + Engine::GetCameraManager().GetCamera()->SetFar(cFar); + Engine::GetCameraManager().GetCamera()->SetBaseFov(cFov); + Engine::GetCameraManager().GetCamera()->SetCameraSensitivity(10.f); + Engine::GetCameraManager().GetCamera()->SetCameraPosition({ 0.f,0.f,10.f }); + //Engine::GetCameraManager().GetCamera()->SetCameraPosition(glm::vec3{ 2.f, 2.f, 2.f }); //Engine::GetCameraManager().SetCenter(glm::vec3{ 0.f, 0.f, 0.f }); //Debug Lighting diff --git a/Game/source/SkeletalAnimationDemo.cpp b/Game/source/SkeletalAnimationDemo.cpp index 55d13d4e..6f8fb361 100644 --- a/Game/source/SkeletalAnimationDemo.cpp +++ b/Game/source/SkeletalAnimationDemo.cpp @@ -15,11 +15,11 @@ void SkeletalAnimationDemo::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); - Engine::GetCameraManager().SetNear(cNear); - Engine::GetCameraManager().SetFar(cFar); - Engine::GetCameraManager().SetBaseFov(cFov); - Engine::GetCameraManager().SetCameraSensitivity(10.f); - Engine::GetCameraManager().SetCameraPosition({ 0.f,2.f,10.f }); + Engine::GetCameraManager().GetCamera()->SetNear(cNear); + Engine::GetCameraManager().GetCamera()->SetFar(cFar); + Engine::GetCameraManager().GetCamera()->SetBaseFov(cFov); + Engine::GetCameraManager().GetCamera()->SetCameraSensitivity(10.f); + Engine::GetCameraManager().GetCamera()->SetCameraPosition({ 0.f,2.f,10.f }); // Player with Skeletal Animation Engine::GetObjectManager().AddObject(glm::vec3(0, 0, 0), glm::vec3(0.01f, 0.01f, 0.01f), "Player"); diff --git a/Game/source/VerticesDemo.cpp b/Game/source/VerticesDemo.cpp index 7736f45e..78f59f22 100644 --- a/Game/source/VerticesDemo.cpp +++ b/Game/source/VerticesDemo.cpp @@ -11,9 +11,9 @@ void VerticesDemo::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::TwoDimension); Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); - Engine::GetCameraManager().SetFar(45.f); - Engine::GetCameraManager().SetBaseFov(22.5f); - Engine::GetCameraManager().SetCameraSensitivity(10.f); + Engine::GetCameraManager().GetCamera()->SetFar(45.f); + Engine::GetCameraManager().GetCamera()->SetBaseFov(22.5f); + Engine::GetCameraManager().GetCamera()->SetCameraSensitivity(10.f); Engine::GetRenderManager()->LoadTexture("../Game/assets/texture_sample2.jpg", "1", true); Engine::GetRenderManager()->LoadTexture("../Game/assets/texture_sample.jpg", "2", true); From 762655f42ed4e8bedfbc760cc2be9c8f7bbd90ae Mon Sep 17 00:00:00 2001 From: Doyeong Lee Date: Mon, 4 May 2026 15:24:41 +0900 Subject: [PATCH 5/5] Refactoring Renamed Camera Projections - Transitioned the camera system terminology from "2D/3D" to "Orthographic/Perspective". This clarifies that a camera's projection method is an independent property from the scene's spatial dimension (2D or 3D). DirectX 12 Multi-Camera Rendering Fix - Resolved a critical DX12 issue where multiple cameras shared the same m_frameIndex offset, causing view/projection data corruption. Implemented WaitForGPU() synchronization and command list flushing per camera loop for strict context isolation. - Removed redundant resource transition barriers during command list resets within camera loops, replacing them with simple OMSetRenderTargets rebinds to prevent validation errors. Debug Rendering Infrastructure Enhancements - Added RenderManager::DrawClippedLine to handle debug line rendering across multiple viewports. This function was introduced to support Picture-in-Picture (PiP) and complex multi-camera layouts. It mathematically clips debug line segments against overlapping camera boundaries, preventing debug graphics (like frustums or bone lines) from "bleeding" into other cameras' viewports and ensuring visual clarity for each independent view. Camera Management & Debug Utilities - Refactored DeleteCamera to correctly shift indices when preceding cameras are removed, and fixed a double-decrement bug in the ImGui interface. - Added a "Show Camera Debug" checkbox to the Camera Manager's ImGui panel to easily toggle frustum and position indicators. - Updated the CameraController to allow adding any camera type regardless of the current demo's dimension. The control logic now dynamically adapts based on the camera's projection type rather than the scene type: Orthographic cameras utilize 2D-style pan/zoom, while Perspective cameras utilize 3D-style WASD movement and mouse rotation, regardless of the environment. Debug Rendering Clipping Optimizations - Commented out aggressive Z-clipping and strict off-screen discard conditions in DrawClippedLine to ensure debug lines remain visible even when vertices extend outside the visible viewport. 2D Physics Stability and Tunneling Fixes - Implemented velocity-aware collision normal selection in the SAT resolution logic for Physics2D. This prevents fast-moving objects from being ejected to the wrong side of a wall when they penetrate past its midpoint in a single frame, ensuring billiard balls always bounce back correctly even at maximum speeds. Performance Consideration - Future optimization for the multi-camera pipeline is required, as the current structure executes a full render loop per camera, leading to linear increases in CPU/GPU load as camera counts grow. --- Engine/include/Camera.hpp | 30 +-- Engine/include/CameraManager.hpp | 3 +- Engine/include/RenderManager.hpp | 1 + Engine/source/BasicComponents/Physics2D.cpp | 34 ++- Engine/source/Camera.cpp | 84 ++++--- Engine/source/CameraManager.cpp | 207 +++++++++++------- Engine/source/DXRenderManager.cpp | 46 ++++ Engine/source/DXShadowMapContext.cpp | 12 +- Engine/source/DXSkyboxRenderContext.cpp | 9 +- Engine/source/GLRenderManager.cpp | 12 +- Engine/source/ObjectManager.cpp | 45 ++-- Engine/source/PhysicsManager.cpp | 46 ++-- Engine/source/RenderManager.cpp | 89 +++++++- Engine/source/VKRenderManager.cpp | 10 +- Game/source/3DPhysicsDemo.cpp | 4 +- Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp | 2 +- Game/source/MultipleLights.cpp | 2 +- Game/source/PBR.cpp | 2 +- Game/source/PlatformDemo/PlatformDemo.cpp | 2 +- Game/source/PocketBallDemo/PocketBallDemo.cpp | 2 +- Game/source/ProceduralMeshes.cpp | 2 +- Game/source/SkeletalAnimationDemo.cpp | 2 +- Game/source/VerticesDemo.cpp | 2 +- 23 files changed, 454 insertions(+), 194 deletions(-) diff --git a/Engine/include/Camera.hpp b/Engine/include/Camera.hpp index 146cf041..51ef8a7a 100644 --- a/Engine/include/Camera.hpp +++ b/Engine/include/Camera.hpp @@ -21,8 +21,8 @@ enum class CameraMoveDir enum class CameraType { - TwoDimension, - ThreeDimension, + Orthographic, + Perspective, NONE }; @@ -50,7 +50,7 @@ class Camera void Update(); void Reset(); - //2D, 3D + //Orthographic, Perspective void SetTarget(glm::vec3 pos); void SetCameraPosition(glm::vec3 cameraPosition_) noexcept; void SetViewSize(int width, int height) noexcept; @@ -68,14 +68,14 @@ class Camera void MoveCameraPos(CameraMoveDir dir, float speed); - //2D - void Rotate2D(float angle) noexcept; - float GetRotate2D() { return rotate2D; } - + //Orthographic + void RotateOrthographic(float angle) noexcept; + float GetRotateOrthographic() { return roll; } + void SetRotationOrthographic(float angle) { roll = angle; } void SetCameraCenterMode(CameraCenterMode mode) noexcept { cameraCenterMode = mode; }; //(TBD) constexpr CameraCenterMode GetCameraCenterMode() const noexcept { return cameraCenterMode; } //(TBD) - //3D + //Perspective void LookAt(glm::vec3 pos); float GetCameraSensitivity() { return cameraSensitivity; } @@ -85,6 +85,7 @@ class Camera void SetFar(float amount) noexcept { farClip = amount; } void SetPitch(float amount) noexcept { pitch = amount; } void SetYaw(float amount) noexcept { yaw = amount; } + void SetRoll(float amount) noexcept { roll = amount; } void SetBaseFov(float amount) noexcept { baseFov = amount; } void SetCameraSensitivity(float amount) noexcept { cameraSensitivity = amount; } void UpdateCameraDirection(glm::vec2 dir); @@ -99,6 +100,7 @@ class Camera float GetFar() { return farClip; } float GetPitch() { return pitch; } float GetYaw() { return yaw; } + float GetRoll() { return roll; } float GetBaseFov() { return baseFov; } float GetIsThirdPersonView() { return isThirdPersonView; } float GetCameraDistance() { return cameraDistance; } @@ -114,7 +116,7 @@ class Camera Ray CalculateRayFrom2DPosition(glm::vec2 pos); private: - //2D, 3D + //Orthographic, Perspective glm::vec3 cameraPosition{ 0.0f, 0.0f, 0.0f }; glm::vec3 up{ 0.0f, 1.0f, 0.0f }; glm::vec3 right{ 1.0f, 0.0f, 0.0f }; @@ -126,21 +128,21 @@ class Camera glm::vec2 cameraViewSize = glm::vec2(0.f); CameraType cameraType = CameraType::NONE; - //2D - float rotate2D = 0.f; + //Orthographic CameraCenterMode cameraCenterMode = RightOriginCenter; - //3D + //Perspective glm::vec3 cameraCenter{ 0.0f, 0.0f, 0.0f }; glm::vec3 worldUp{ 0.0f, 1.0f, 0.0f }; glm::vec3 back{ 0.0f, 0.0f, -1.0f }; glm::vec3 cameraOffset{ 0.0f, 0.0f, 0.0f }; //In ThirdPersonView float aspectRatio = 1.f; //(TBD) - float nearClip = 1.f; - float farClip = 1000.f; + float nearClip = 0.001f; + float farClip = 45.f; float pitch = 0.0f; float yaw = -90.0f; + float roll = 0.0f; // rotationOrthographic float cameraSensitivity = 1.f; float cameraDistance = 5.0f; //In ThirdPersonView bool isThirdPersonView = false; //In ThirdPersonView diff --git a/Engine/include/CameraManager.hpp b/Engine/include/CameraManager.hpp index ef6c09a7..e5fb6078 100644 --- a/Engine/include/CameraManager.hpp +++ b/Engine/include/CameraManager.hpp @@ -14,7 +14,7 @@ class CameraManager CameraManager() {}; ~CameraManager(); - void Init(glm::vec2 viewSize, CameraType type = CameraType::TwoDimension, float zoom = 45.f, float angle = 0.f); + void Init(glm::vec2 viewSize, CameraType type = CameraType::Orthographic, float zoom = 1.0f, float angle = 0.f); void Update(); void DrawCameraDebug(); void Reset(); @@ -34,6 +34,7 @@ class CameraManager void CameraControllerImGui(); int currentObjIndex = 0; + bool showCameraDebug = true; private: std::vector> cameras; int mainCameraIndex = 0; diff --git a/Engine/include/RenderManager.hpp b/Engine/include/RenderManager.hpp index 6bebe1c3..050f2abe 100644 --- a/Engine/include/RenderManager.hpp +++ b/Engine/include/RenderManager.hpp @@ -148,6 +148,7 @@ class RenderManager // Helper function to world-to-screen transform for ImGui drawing glm::vec2 WorldToScreen(glm::vec3 worldPos, const glm::mat4& view, const glm::mat4& proj, class Camera* camera = nullptr); + void DrawClippedLine(struct ImDrawList* drawList, glm::vec2 p1, glm::vec2 p2, unsigned int color, float thickness = 1.0f, int mainCameraIndex = 0); void RenderingControllerForImGui(); //Skybox diff --git a/Engine/source/BasicComponents/Physics2D.cpp b/Engine/source/BasicComponents/Physics2D.cpp index 3fb9f449..bfd7eac1 100644 --- a/Engine/source/BasicComponents/Physics2D.cpp +++ b/Engine/source/BasicComponents/Physics2D.cpp @@ -267,10 +267,21 @@ bool Physics2D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) glm::vec2 obj2Center = glm::vec2(obj2->GetPosition()); glm::vec2 direction = obj2Center - objCenter; - // Ensure the normal points from obj to obj2 for consistent collision response - if (glm::dot(direction, normal) < 0.f) + // Use relative velocity to determine the correct normal if objects are moving fast enough to cross midpoints + glm::vec2 relativeVel = obj2->GetComponent()->GetVelocity() - obj->GetComponent()->GetVelocity(); + if (std::abs(glm::dot(relativeVel, normal)) > 0.1f) + { + if (glm::dot(relativeVel, normal) > 0.f) + { + normal = -normal; + } + } + else { - normal = -normal; + if (glm::dot(direction, normal) < 0.f) + { + normal = -normal; + } } // Apply Baumgarte stabilization technique (Slop) @@ -479,9 +490,22 @@ bool Physics2D::CollisionPC(Object* poly, Object* cir, CollisionMode mode) // Ensure collision normal points from Polygon (A) to Circle (B) glm::vec2 polyCenter = FindSATCenter(rotatedPoints); glm::vec2 direction = circleCenter - polyCenter; - if (glm::dot(direction, normal) < 0.f) + + // Use relative velocity to determine the correct normal if objects are moving fast enough to cross midpoints + glm::vec2 relativeVel = cir->GetComponent()->GetVelocity() - poly->GetComponent()->GetVelocity(); + if (std::abs(glm::dot(relativeVel, normal)) > 0.1f) + { + if (glm::dot(relativeVel, normal) > 0.f) + { + normal = -normal; + } + } + else { - normal = -normal; + if (glm::dot(direction, normal) < 0.f) + { + normal = -normal; + } } if (mode == CollisionMode::COLLIDE && diff --git a/Engine/source/Camera.cpp b/Engine/source/Camera.cpp index 08e7a3b1..47145966 100644 --- a/Engine/source/Camera.cpp +++ b/Engine/source/Camera.cpp @@ -20,36 +20,34 @@ void Camera::Update() glm::vec2 wSize = Engine::GetWindow().GetWindowSize(); switch (cameraType) { - case CameraType::TwoDimension: + case CameraType::Orthographic: if (isThirdPersonView == true) { - view = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraCenter.x * 2.f, -cameraCenter.y * 2.f, 0.0f)) * - glm::rotate(glm::mat4(1.0f), glm::radians(rotate2D), glm::vec3(0.0f, 0.0f, 1.0f)) * - glm::scale(glm::mat4(1.0f), glm::vec3(zoom, zoom, 1.0f)); + view = glm::rotate(glm::mat4(1.0f), -glm::radians(roll), glm::vec3(0.0f, 0.0f, 1.0f)) * + glm::translate(glm::mat4(1.0f), -cameraCenter); } else { - view = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraPosition.x * 2.f, -cameraPosition.y * 2.f, 0.0f)) * - glm::rotate(glm::mat4(1.0f), glm::radians(rotate2D), glm::vec3(0.0f, 0.0f, 1.0f)) * - glm::scale(glm::mat4(1.0f), glm::vec3(zoom, zoom, 1.0f)); + view = glm::rotate(glm::mat4(1.0f), -glm::radians(roll), glm::vec3(0.0f, 0.0f, 1.0f)) * + glm::translate(glm::mat4(1.0f), -cameraPosition); } switch (Engine::GetRenderManager()->GetGraphicsMode()) { case GraphicsMode::GL: - projection = glm::orthoRH_NO(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); + projection = glm::orthoRH_NO(-cameraViewSize.x / zoom, cameraViewSize.x / zoom, -cameraViewSize.y / zoom, cameraViewSize.y / zoom, nearClip, farClip); break; case GraphicsMode::VK: - projection = glm::orthoRH_ZO(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); + projection = glm::orthoRH_ZO(-cameraViewSize.x / zoom, cameraViewSize.x / zoom, -cameraViewSize.y / zoom, cameraViewSize.y / zoom, nearClip, farClip); // Flip y-axis for Vulkan projection[1][1] *= -1.0f; break; case GraphicsMode::DX: - projection = glm::orthoRH_ZO(-cameraViewSize.x, cameraViewSize.x, -cameraViewSize.y, cameraViewSize.y, -1.f, 1.f); + projection = glm::orthoRH_ZO(-cameraViewSize.x / zoom, cameraViewSize.x / zoom, -cameraViewSize.y / zoom, cameraViewSize.y / zoom, nearClip, farClip); break; } break; - case CameraType::ThreeDimension: + case CameraType::Perspective: glm::vec3 direction; glm::vec3 desiredPosition = cameraCenter + cameraOffset; @@ -75,12 +73,27 @@ void Camera::Update() { right = glm::normalize(glm::cross(back, worldUp)); up = glm::normalize(glm::cross(right, back)); + + if (roll != 0.0f) + { + glm::mat4 rollMat = glm::rotate(glm::mat4(1.0f), glm::radians(roll), back); + up = glm::vec3(rollMat * glm::vec4(up, 0.0f)); + right = glm::vec3(rollMat * glm::vec4(right, 0.0f)); + } + cameraPosition = desiredPosition - back * cameraDistance; } else { right = glm::normalize(glm::cross(direction, worldUp)); up = glm::normalize(glm::cross(right, back)); + + if (roll != 0.0f) + { + glm::mat4 rollMat = glm::rotate(glm::mat4(1.0f), glm::radians(roll), back); + up = glm::vec3(rollMat * glm::vec4(up, 0.0f)); + right = glm::vec3(rollMat * glm::vec4(right, 0.0f)); + } } switch (Engine::GetRenderManager()->GetGraphicsMode()) @@ -141,11 +154,11 @@ void Camera::SetTarget(glm::vec3 pos) { switch (cameraType) { - case CameraType::TwoDimension: + case CameraType::Orthographic: cameraPosition = pos; cameraCenter = pos; break; - case CameraType::ThreeDimension: + case CameraType::Perspective: if (isThirdPersonView) { cameraCenter = pos; @@ -168,24 +181,32 @@ void Camera::MoveCameraPos(CameraMoveDir dir, float speed) switch (dir) { case CameraMoveDir::FOWARD: - if (cameraType == CameraType::ThreeDimension) + if (cameraType == CameraType::Perspective) { cameraPosition += back * speed; } + else if (cameraType == CameraType::Orthographic) + { + cameraPosition.z += speed; + } break; case CameraMoveDir::BACKWARD: - if (cameraType == CameraType::ThreeDimension) + if (cameraType == CameraType::Perspective) { cameraPosition -= back * speed; } + else if (cameraType == CameraType::Orthographic) + { + cameraPosition.z -= speed; + } break; case CameraMoveDir::UP: switch (cameraType) { - case CameraType::TwoDimension: + case CameraType::Orthographic: cameraPosition += normalize(up) * speed; break; - case CameraType::ThreeDimension: + case CameraType::Perspective: cameraPosition += up * speed; break; } @@ -193,10 +214,10 @@ void Camera::MoveCameraPos(CameraMoveDir dir, float speed) case CameraMoveDir::DOWN: switch (cameraType) { - case CameraType::TwoDimension: + case CameraType::Orthographic: cameraPosition -= normalize(up) * speed; break; - case CameraType::ThreeDimension: + case CameraType::Perspective: cameraPosition -= up * speed; break; } @@ -204,10 +225,10 @@ void Camera::MoveCameraPos(CameraMoveDir dir, float speed) case CameraMoveDir::LEFT: switch (cameraType) { - case CameraType::TwoDimension: + case CameraType::Orthographic: cameraPosition -= normalize(right) * speed; break; - case CameraType::ThreeDimension: + case CameraType::Perspective: cameraPosition -= right * speed; break; } @@ -215,10 +236,10 @@ void Camera::MoveCameraPos(CameraMoveDir dir, float speed) case CameraMoveDir::RIGHT: switch (cameraType) { - case CameraType::TwoDimension: + case CameraType::Orthographic: cameraPosition += normalize(right) * speed; break; - case CameraType::ThreeDimension: + case CameraType::Perspective: cameraPosition += right * speed; break; } @@ -257,14 +278,14 @@ Ray Camera::CalculateRayFrom2DPosition(glm::vec2 pos) return Ray{ cameraPos, rayDirection }; } -void Camera::Rotate2D(float angle) noexcept +void Camera::RotateOrthographic(float angle) noexcept { switch (cameraType) { - case CameraType::TwoDimension: - rotate2D = angle; + case CameraType::Orthographic: + roll = angle; break; - case CameraType::ThreeDimension: + case CameraType::Perspective: break; default: break; @@ -273,7 +294,7 @@ void Camera::Rotate2D(float angle) noexcept void Camera::LookAt(glm::vec3 pos) { - if (cameraType == CameraType::ThreeDimension) + if (cameraType == CameraType::Perspective) { cameraCenter = pos; glm::vec3 direction = glm::normalize(cameraCenter - cameraPosition); @@ -333,8 +354,9 @@ void Camera::Reset() SetZoom(1.f); pitch = 00.f; yaw = -90.f; - nearClip = 1.f; - farClip = 45.0f; + roll = 0.0f; + nearClip = 0.1f; + farClip = 1000.0f; baseFov = 45.f; cameraSensitivity = 1.f; isThirdPersonView = false; @@ -349,5 +371,5 @@ void Camera::SetViewSize(int width, int height) noexcept void Camera::SetZoom(float amount) noexcept { - zoom = glm::clamp(amount, nearClip, farClip); + zoom = glm::clamp(amount, 0.001f, 10000.0f); } diff --git a/Engine/source/CameraManager.cpp b/Engine/source/CameraManager.cpp index f4535f83..b370cf5f 100644 --- a/Engine/source/CameraManager.cpp +++ b/Engine/source/CameraManager.cpp @@ -24,6 +24,38 @@ Camera* CameraManager::AddCamera(CameraType type, std::string name) glm::vec2 wSize = Engine::GetWindow().GetWindowSize(); newCamera->SetViewSize(static_cast(wSize.x), static_cast(wSize.y)); + RenderType rType = Engine::GetRenderManager()->GetRenderType(); + + if (newCamera->GetCameraType() == CameraType::Orthographic) + { + if (rType == RenderType::ThreeDimension) + { + // In 3D, 1 unit is large. Default ortho bounds are window pixels. + // Set zoom to make 3D objects visible (e.g., zoom=200 means visible height is around 4 units) + newCamera->SetZoom(200.0f); + newCamera->SetCameraPosition(glm::vec3(0.0f, 0.0f, 20.0f)); + } + else + { + newCamera->SetZoom(1.0f); + newCamera->SetCameraPosition(glm::vec3(0.0f, 0.0f, 1.0f)); + } + } + else if (newCamera->GetCameraType() == CameraType::Perspective) + { + if (rType == RenderType::TwoDimension) + { + // In 2D, coordinates are in pixels (e.g., 400, 300). + // Position camera far enough to see the 2D plane. + float dist = wSize.y; + newCamera->SetCameraPosition(glm::vec3(0.0f, 0.0f, dist)); + } + else + { + newCamera->SetCameraPosition(glm::vec3(0.0f, 0.0f, 5.0f)); + } + } + cameras.push_back(std::move(newCamera)); return cameras.back().get(); @@ -35,14 +67,24 @@ void CameraManager::DeleteCamera(int index) { cameras.erase(cameras.begin() + index); - if (mainCameraIndex >= cameras.size()) + // Adjust mainCameraIndex + if (index < mainCameraIndex) + { + mainCameraIndex--; + } + if (mainCameraIndex >= static_cast(cameras.size())) { - mainCameraIndex = static_cast(cameras.size()) - 1; + mainCameraIndex = std::max(0, static_cast(cameras.size()) - 1); } - if (selectedCameraIndex >= cameras.size()) + // Adjust selectedCameraIndex + if (index < selectedCameraIndex) { - selectedCameraIndex = static_cast(cameras.size()) - 1; + selectedCameraIndex--; + } + if (selectedCameraIndex >= static_cast(cameras.size())) + { + selectedCameraIndex = std::max(0, static_cast(cameras.size()) - 1); } } } @@ -78,18 +120,9 @@ void CameraManager::Init(glm::vec2 viewSize, CameraType type, float zoom, float { mainCam->SetViewSize(static_cast(viewSize.x), static_cast(viewSize.y)); mainCam->SetZoom(zoom); - mainCam->Rotate2D(angle); + mainCam->RotateOrthographic(angle); } - switch (type) - { - case CameraType::TwoDimension: - break; - case CameraType::ThreeDimension: - break; - default: - break; - } Engine::GetLogger().LogDebug(LogCategory::Engine, "Camera Manager Initialized"); } @@ -220,18 +253,16 @@ void CameraManager::CameraControllerImGui() ImGui::SameLine(); } - if (ImGui::Button("Add")) + if (ImGui::Button("Add Ortho")) { - if(Engine::GetRenderManager()->GetRenderType() == RenderType::ThreeDimension) - { - AddCamera(CameraType::ThreeDimension); - selectedCameraIndex = cameras.size() - 1; - } - else - { - AddCamera(CameraType::TwoDimension); - selectedCameraIndex = cameras.size() - 1; - } + AddCamera(CameraType::Orthographic); + selectedCameraIndex = cameras.size() - 1; + } + ImGui::SameLine(); + if (ImGui::Button("Add Persp")) + { + AddCamera(CameraType::Perspective); + selectedCameraIndex = cameras.size() - 1; } if (cameras.empty() == false && cameras.size() > 1) @@ -240,16 +271,10 @@ void CameraManager::CameraControllerImGui() if (ImGui::Button("Remove")) { DeleteCamera(selectedCameraIndex); - if (selectedCameraIndex > 0) - { - selectedCameraIndex = selectedCameraIndex - 1; - } - else if(selectedCameraIndex == 0) - { - selectedCameraIndex = 0; - } } } + + ImGui::Checkbox("Show Camera Debug", &showCameraDebug); ImGui::Separator(); @@ -276,12 +301,13 @@ void CameraManager::CameraControllerImGui() glm::vec3 position = selectedCam->GetCameraPosition(); float zoom = selectedCam->GetZoom(); - if (selectedCam->GetCameraType() == CameraType::ThreeDimension) + if (selectedCam->GetCameraType() == CameraType::Perspective) { float nearClip = selectedCam->GetNear(); float farClip = selectedCam->GetFar(); float pitch = selectedCam->GetPitch(); float yaw = selectedCam->GetYaw(); + float roll = selectedCam->GetRoll(); glm::vec3 cameraOffset = selectedCam->GetCameraOffset(); float cameraDistance = selectedCam->GetCameraDistance(); @@ -316,6 +342,9 @@ void CameraManager::CameraControllerImGui() ImGui::DragFloat("Yaw", &yaw, 0.5f); selectedCam->SetYaw(yaw); + ImGui::DragFloat("Roll", &roll, 0.5f); + selectedCam->SetRoll(roll); + if (isThirdPersonView == true && !Engine::GetObjectManager().GetObjectMap().empty()) { if (ImGui::CollapsingHeader("Third Person View Option", ImGuiTreeNodeFlags_DefaultOpen)) @@ -347,23 +376,26 @@ void CameraManager::CameraControllerImGui() } } } - else if (selectedCam->GetCameraType() == CameraType::TwoDimension) + else if (selectedCam->GetCameraType() == CameraType::Orthographic) { - float rotate2D = selectedCam->GetRotate2D(); + float rotation = selectedCam->GetRotateOrthographic(); - ImGui::DragFloat2("Position", &position.x, 0.1f); + ImGui::DragFloat3("Position", &position.x, 0.1f); selectedCam->SetCameraPosition(position); ImGui::DragFloat("Zoom", &zoom, 0.1f); selectedCam->SetZoom(zoom); - ImGui::DragFloat("Rotation", &rotate2D, 0.5f); - selectedCam->Rotate2D(rotate2D); + ImGui::DragFloat("Rotation", &rotation, 0.5f); + selectedCam->RotateOrthographic(rotation); } } } - DrawCameraDebug(); + if (showCameraDebug) + { + DrawCameraDebug(); + } ImGui::End(); } @@ -391,7 +423,7 @@ void CameraManager::DrawCameraDebug() for (int i = 0; i < cameras.size(); ++i) { Camera* cam = cameras[i].get(); - if (cam == nullptr || cam->GetCameraType() != CameraType::ThreeDimension || i == mainCameraIndex) continue; + if (cam == nullptr || i == mainCameraIndex) continue; // Color: Blue for active, Red for inactive ImU32 color = cam->GetIsActive() ? IM_COL32(0, 120, 255, 255) : IM_COL32(255, 0, 0, 255); @@ -422,29 +454,23 @@ void CameraManager::DrawCameraDebug() }; glm::vec2 screenCorners[8]; - bool hasValidPoint = false; for (int j = 0; j < 8; ++j) { glm::vec4 worldPos = invVP * ndc[j]; glm::vec3 p = glm::vec3(worldPos) / worldPos.w; screenCorners[j] = Engine::GetRenderManager()->WorldToScreen(p, mainView, mainProj, mainCam); - if (screenCorners[j].x != -1) hasValidPoint = true; } - if (hasValidPoint) - { - // Connect Near plane - for (int j = 0; j < 4; ++j) - drawList->AddLine(ImVec2(screenCorners[j].x, screenCorners[j].y), ImVec2(screenCorners[(j + 1) % 4].x, screenCorners[(j + 1) % 4].y), color, 1.5f); - - // Connect Far plane - for (int j = 0; j < 4; ++j) - drawList->AddLine(ImVec2(screenCorners[j + 4].x, screenCorners[j + 4].y), ImVec2(screenCorners[(j + 1) % 4 + 4].x, screenCorners[(j + 1) % 4 + 4].y), color, 1.5f); + auto drawClippedLine = [&](glm::vec2 p1, glm::vec2 p2) { + Engine::GetRenderManager()->DrawClippedLine(drawList, p1, p2, color, 1.5f, mainCameraIndex); + }; - // Connect Near to Far - for (int j = 0; j < 4; ++j) - drawList->AddLine(ImVec2(screenCorners[j].x, screenCorners[j].y), ImVec2(screenCorners[j + 4].x, screenCorners[j + 4].y), color, 1.5f); - } + // Connect Near plane + for (int j = 0; j < 4; ++j) drawClippedLine(screenCorners[j], screenCorners[(j + 1) % 4]); + // Connect Far plane + for (int j = 0; j < 4; ++j) drawClippedLine(screenCorners[j + 4], screenCorners[(j + 1) % 4 + 4]); + // Connect Near to Far + for (int j = 0; j < 4; ++j) drawClippedLine(screenCorners[j], screenCorners[j + 4]); } drawList->PopClipRect(); @@ -459,30 +485,53 @@ void CameraManager::ControlCamera(float dt) return; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::W)) - { - selectedCam->MoveCameraPos(CameraMoveDir::FOWARD, 5.f * dt); - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::S)) - { - selectedCam->MoveCameraPos(CameraMoveDir::BACKWARD, 5.f * dt); - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::A)) + if (selectedCam->GetCameraType() == CameraType::Perspective) { - selectedCam->MoveCameraPos(CameraMoveDir::LEFT, 5.f * dt); - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::D)) - { - selectedCam->MoveCameraPos(CameraMoveDir::RIGHT, 5.f * dt); - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::SPACE)) - { - selectedCam->MoveCameraPos(CameraMoveDir::UP, 5.f * dt); + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::W)) + { + selectedCam->MoveCameraPos(CameraMoveDir::FOWARD, 5.f * dt); + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::S)) + { + selectedCam->MoveCameraPos(CameraMoveDir::BACKWARD, 5.f * dt); + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::A)) + { + selectedCam->MoveCameraPos(CameraMoveDir::LEFT, 5.f * dt); + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::D)) + { + selectedCam->MoveCameraPos(CameraMoveDir::RIGHT, 5.f * dt); + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::SPACE)) + { + selectedCam->MoveCameraPos(CameraMoveDir::UP, 5.f * dt); + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LSHIFT)) + { + selectedCam->MoveCameraPos(CameraMoveDir::DOWN, 5.f * dt); + } } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LSHIFT)) + else if (selectedCam->GetCameraType() == CameraType::Orthographic) { - selectedCam->MoveCameraPos(CameraMoveDir::DOWN, 5.f * dt); + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::W)) + { + selectedCam->MoveCameraPos(CameraMoveDir::UP, 50.f * dt); + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::S)) + { + selectedCam->MoveCameraPos(CameraMoveDir::DOWN, 50.f * dt); + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::A)) + { + selectedCam->MoveCameraPos(CameraMoveDir::LEFT, 50.f * dt); + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::D)) + { + selectedCam->MoveCameraPos(CameraMoveDir::RIGHT, 50.f * dt); + } } + if (Engine::GetInputManager().GetMouseWheelMotion().y != 0.f) { selectedCam->SetZoom(selectedCam->GetZoom() + Engine::GetInputManager().GetMouseWheelMotion().y); @@ -492,7 +541,9 @@ void CameraManager::ControlCamera(float dt) if (Engine::GetInputManager().IsMouseButtonPressed(MOUSEBUTTON::RIGHT) || SDL_GetWindowRelativeMouseMode(window) == true) { - selectedCam->UpdateCameraDirection(Engine::Instance().GetInputManager().GetRelativeMouseState() * dt); + if (selectedCam->GetCameraType() == CameraType::Perspective) + { + selectedCam->UpdateCameraDirection(Engine::Instance().GetInputManager().GetRelativeMouseState() * dt); + } } - //TBD } \ No newline at end of file diff --git a/Engine/source/DXRenderManager.cpp b/Engine/source/DXRenderManager.cpp index 35373b59..2aba3d9e 100644 --- a/Engine/source/DXRenderManager.cpp +++ b/Engine/source/DXRenderManager.cpp @@ -413,6 +413,22 @@ bool DXRenderManager::BeginRender(glm::vec3 bgColor) m_commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 1, &rect); m_2dRenderContext->Execute(&wrapper, cam); + + if (c < cameraCount - 1) + { + // @TODO: This WaitForGPU() is currently required because the VertexUniform (model/view/proj) is shared per object per frame. + // Without synchronization, multiple camera updates will overwrite the same constant buffer memory before the GPU can finish rendering previous views. + // To optimize this, separate camera-specific constants (View/Projection) from object-specific constants (Model) or use a per-camera constant buffer array. + DXHelper::ThrowIfFailed(m_commandList->Close()); + ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() }; + m_commandQueue->ExecuteCommandLists(1, ppCommandLists); + WaitForGPU(); + DXHelper::ThrowIfFailed(m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), nullptr)); + + // Rebind Render Targets for next camera + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandleRebind(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), static_cast(m_frameIndex), m_rtvDescriptorSize); + m_commandList->OMSetRenderTargets(1, &rtvHandleRebind, FALSE, &dsvHandle); + } } } break; @@ -453,6 +469,21 @@ bool DXRenderManager::BeginRender(glm::vec3 bgColor) m_forwardRenderContext->Execute(&wrapper, cam); } if (m_skyboxEnabled) m_skyboxRenderContext->Execute(&wrapper, cam); + + if (c < cameraCount - 1) + { + // @TODO: This WaitForGPU() is currently required because the VertexUniform (model/view/proj) is shared per object per frame. + // Without synchronization, multiple camera updates will overwrite the same constant buffer memory before the GPU can finish rendering previous views. + // To optimize this, separate camera-specific constants (View/Projection) from object-specific constants (Model) or use a per-camera constant buffer array. + DXHelper::ThrowIfFailed(m_commandList->Close()); + ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() }; + m_commandQueue->ExecuteCommandLists(1, ppCommandLists); + WaitForGPU(); + DXHelper::ThrowIfFailed(m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), nullptr)); + + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_renderTarget->GetMSAARtvHeap()->GetCPUDescriptorHandleForHeapStart(); + m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle); + } } } // Deferred Rendering @@ -495,6 +526,21 @@ bool DXRenderManager::BeginRender(glm::vec3 bgColor) m_globalLightingContext->Execute(&wrapper, cam); if (!m_meshletVisualization) m_localLightingContext->Execute(&wrapper, cam); if (m_skyboxEnabled) m_skyboxRenderContext->Execute(&wrapper, cam); + + if (c < cameraCount - 1) + { + // @TODO: This WaitForGPU() is currently required because the VertexUniform (model/view/proj) is shared per object per frame. + // Without synchronization, multiple camera updates will overwrite the same constant buffer memory before the GPU can finish rendering previous views. + // To optimize this, separate camera-specific constants (View/Projection) from object-specific constants (Model) or use a per-camera constant buffer array. + DXHelper::ThrowIfFailed(m_commandList->Close()); + ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() }; + m_commandQueue->ExecuteCommandLists(1, ppCommandLists); + WaitForGPU(); + DXHelper::ThrowIfFailed(m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), nullptr)); + + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_renderTarget->GetHDRRtvHeap()->GetCPUDescriptorHandleForHeapStart(); + m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle); + } } } m_postProcessContext->Execute(&wrapper); diff --git a/Engine/source/DXShadowMapContext.cpp b/Engine/source/DXShadowMapContext.cpp index 437dd38e..6dcc92bd 100644 --- a/Engine/source/DXShadowMapContext.cpp +++ b/Engine/source/DXShadowMapContext.cpp @@ -402,8 +402,8 @@ void DXShadowMapContext::DrawImGui() drawList->PushClipRect(clipMin, clipMax); // Draw light position and target on the screen for debugging - glm::vec2 lightScreenPos = Engine::GetRenderManager()->WorldToScreen(m_lightPosition, cameraView, cameraProj); - glm::vec2 targetScreenPos = Engine::GetRenderManager()->WorldToScreen(m_lightTarget, cameraView, cameraProj); + glm::vec2 lightScreenPos = Engine::GetRenderManager()->WorldToScreen(m_lightPosition, cameraView, cameraProj, mainCam); + glm::vec2 targetScreenPos = Engine::GetRenderManager()->WorldToScreen(m_lightTarget, cameraView, cameraProj, mainCam); if (lightScreenPos.x != -1.0f && targetScreenPos.x != -1.0f) { // Draw light position if not occluded @@ -436,18 +436,16 @@ void DXShadowMapContext::DrawImGui() glm::vec4 worldPos = invLightVP * glm::vec4(ndcCorners[i], 1.0f); glm::vec3 worldPos3D = glm::vec3(worldPos) / worldPos.w; - glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(worldPos3D, cameraView, cameraProj); + glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(worldPos3D, cameraView, cameraProj, mainCam); screenCorners[i] = ImVec2(screenPos.x, screenPos.y); valid[i] = screenPos.x != -1.0f && screenPos.y != -1.0f; } auto DrawLine = [&](int index1, int index2) { - if (valid[index1] && valid[index2] && - !camManager.IsScreenPointOccluded(glm::vec2(screenCorners[index1].x, screenCorners[index1].y), mainCamIdx) && - !camManager.IsScreenPointOccluded(glm::vec2(screenCorners[index2].x, screenCorners[index2].y), mainCamIdx)) + if (valid[index1] && valid[index2]) { - drawList->AddLine(screenCorners[index1], screenCorners[index2], IM_COL32(0, 255, 255, 255), 2.0f); + Engine::GetRenderManager()->DrawClippedLine(drawList, glm::vec2(screenCorners[index1].x, screenCorners[index1].y), glm::vec2(screenCorners[index2].x, screenCorners[index2].y), IM_COL32(0, 255, 255, 255), 2.0f, mainCamIdx); } }; diff --git a/Engine/source/DXSkyboxRenderContext.cpp b/Engine/source/DXSkyboxRenderContext.cpp index 853128d2..6f0d4eef 100644 --- a/Engine/source/DXSkyboxRenderContext.cpp +++ b/Engine/source/DXSkyboxRenderContext.cpp @@ -82,7 +82,14 @@ void DXSkyboxRenderContext::Execute(ICommandListWrapper* commandListWrapper, Cam commandList->RSSetViewports(1, &viewport); commandList->RSSetScissorRects(1, &scissorRect); - glm::mat4 worldToNDC[2] = { activeCamera->GetViewMatrix(), activeCamera->GetProjectionMatrix() }; + glm::mat4 projection = activeCamera->GetProjectionMatrix(); + if (activeCamera->GetCameraType() == CameraType::Orthographic) + { + float aspect = (float)renderWidth / (float)renderHeight; + projection = glm::perspective(glm::radians(activeCamera->GetBaseFov()), aspect, 0.1f, 10.f); + } + + glm::mat4 worldToNDC[2] = { activeCamera->GetViewMatrix(), projection }; commandList->SetGraphicsRoot32BitConstants(0, 32, &worldToNDC, 0); commandList->SetGraphicsRootDescriptorTable(1, m_skybox->GetCubemapSrv()); diff --git a/Engine/source/GLRenderManager.cpp b/Engine/source/GLRenderManager.cpp index 1399b353..297f5039 100644 --- a/Engine/source/GLRenderManager.cpp +++ b/Engine/source/GLRenderManager.cpp @@ -283,7 +283,17 @@ bool GLRenderManager::BeginRender(glm::vec3 bgColor) std::span spanView(&cam->GetViewMatrix()[0][0], 16); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, spanView.data()); - std::span spanProjection(&cam->GetProjectionMatrix()[0][0], 16); + + glm::mat4 projection = cam->GetProjectionMatrix(); + if (cam->GetCameraType() == CameraType::Orthographic) + { + GLsizei w, h; + SDL_GetWindowSizeInPixels(Engine::GetWindow().GetWindow(), &w, &h); + float aspect = (float)w / (float)h; + projection = glm::perspective(glm::radians(cam->GetBaseFov()), aspect, 0.1f, 10.f); + } + + std::span spanProjection(&projection[0][0], 16); glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, spanProjection.data()); GLint skyboxLoc = glCheck(glGetUniformLocation(skyboxShader.GetProgramHandle(), "skybox")); diff --git a/Engine/source/ObjectManager.cpp b/Engine/source/ObjectManager.cpp index 8fa9af11..cfefc256 100644 --- a/Engine/source/ObjectManager.cpp +++ b/Engine/source/ObjectManager.cpp @@ -1081,7 +1081,7 @@ void ObjectManager::RenderBoneHierarchy(const AssimpNodeData* node, const std::m ImDrawList* drawList = ImGui::GetBackgroundDrawList(); glm::vec3 currentPos = glm::vec3(nodeWorldMatrix[3]); // Extract translation - glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(currentPos, view, proj); + glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(currentPos, view, proj, mainCam); // Draw joint point if (screenPos.x != -1 && screenPos.y != -1 && !camManager.IsScreenPointOccluded(screenPos, mainCamIdx)) @@ -1101,19 +1101,12 @@ void ObjectManager::RenderBoneHierarchy(const AssimpNodeData* node, const std::m glm::mat4 childWorldMatrix = objectTransform * childGlobalMatrix; glm::vec3 childPos = glm::vec3(childWorldMatrix[3]); - glm::vec2 childScreenPos = Engine::GetRenderManager()->WorldToScreen(childPos, view, proj); + glm::vec2 childScreenPos = Engine::GetRenderManager()->WorldToScreen(childPos, view, proj, mainCam); - // Draw line if both positions are valid and not occluded - if (screenPos.x >= 0 && screenPos.y >= 0 && - childScreenPos.x >= 0 && childScreenPos.y >= 0 && - !camManager.IsScreenPointOccluded(screenPos, mainCamIdx) && - !camManager.IsScreenPointOccluded(childScreenPos, mainCamIdx)) + // Draw line with natural clipping against other viewports + if (screenPos.x >= 0 && screenPos.y >= 0 && childScreenPos.x >= 0 && childScreenPos.y >= 0) { - drawList->AddLine( - ImVec2(screenPos.x, screenPos.y), - ImVec2(childScreenPos.x, childScreenPos.y), - IM_COL32(255, 255, 0, 255), - 2.0f); + Engine::GetRenderManager()->DrawClippedLine(drawList, screenPos, childScreenPos, IM_COL32(255, 255, 0, 255), 2.0f, mainCamIdx); } } RenderBoneHierarchy(&child, animatedTransforms, objectTransform); @@ -1480,15 +1473,13 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) { float theta = (2.0f * PI * i) / segments; glm::vec3 worldPos = center + (right * cos(theta) + up * sin(theta)) * radius; - glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(worldPos, view, proj); + glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(worldPos, view, proj, mainCam); if (screenPos.x >= 0 && screenPos.y >= 0) { - if (!first && prevScreenPos.x >= 0 && prevScreenPos.y >= 0 && - !camManager.IsScreenPointOccluded(prevScreenPos, mainCamIdx) && - !camManager.IsScreenPointOccluded(screenPos, mainCamIdx)) + if (!first && prevScreenPos.x >= 0 && prevScreenPos.y >= 0) { - drawList->AddLine(ImVec2(prevScreenPos.x, prevScreenPos.y), ImVec2(screenPos.x, screenPos.y), color, 2.0f); + Engine::GetRenderManager()->DrawClippedLine(drawList, prevScreenPos, screenPos, color, 2.0f, mainCamIdx); } else if (first) { @@ -1528,17 +1519,15 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) }; std::vector screenPoints; - for(auto& wp : worldPoints) screenPoints.push_back(Engine::GetRenderManager()->WorldToScreen(wp, view, proj)); + for(auto& wp : worldPoints) screenPoints.push_back(Engine::GetRenderManager()->WorldToScreen(wp, view, proj, mainCam)); for (int i = 0; i < 12; ++i) { glm::vec2 p1 = screenPoints[edges[i][0]]; glm::vec2 p2 = screenPoints[edges[i][1]]; - if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0 && - !camManager.IsScreenPointOccluded(p1, mainCamIdx) && - !camManager.IsScreenPointOccluded(p2, mainCamIdx)) + if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0) { - drawList->AddLine(ImVec2(p1.x, p1.y), ImVec2(p2.x, p2.y), color, 2.0f); + Engine::GetRenderManager()->DrawClippedLine(drawList, p1, p2, color, 2.0f, mainCamIdx); } } } @@ -1615,7 +1604,7 @@ void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) float sinTheta = sin(objRotRad); glm::vec2 worldPt = scaledPos + glm::vec2(scaledLocalPt.x * cosTheta - scaledLocalPt.y * sinTheta, scaledLocalPt.x * sinTheta + scaledLocalPt.y * cosTheta); - glm::vec2 screenPt = Engine::GetRenderManager()->WorldToScreen(glm::vec3(worldPt, 0.f), view, proj); + glm::vec2 screenPt = Engine::GetRenderManager()->WorldToScreen(glm::vec3(worldPt, 0.f), view, proj, mainCam); screenPoints.push_back(screenPt); } @@ -1623,11 +1612,9 @@ void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) { glm::vec2 p1 = screenPoints[i]; glm::vec2 p2 = screenPoints[(i + 1) % screenPoints.size()]; - if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0 && - !camManager.IsScreenPointOccluded(p1, mainCamIdx) && - !camManager.IsScreenPointOccluded(p2, mainCamIdx)) + if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0) { - drawList->AddLine(ImVec2(p1.x, p1.y), ImVec2(p2.x, p2.y), color, 2.0f); + Engine::GetRenderManager()->DrawClippedLine(drawList, p1, p2, color, 2.0f, mainCamIdx); } } } @@ -1635,8 +1622,8 @@ void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) { // Scale radius by 2 to match the engine's 2D rendering scale float radius = phy->GetCircleCollideRadius() * 2.0f; - glm::vec2 screenCenter = Engine::GetRenderManager()->WorldToScreen(glm::vec3(scaledPos, 0.f), view, proj); - glm::vec2 screenEdge = Engine::GetRenderManager()->WorldToScreen(glm::vec3(scaledPos.x + radius, scaledPos.y, 0.f), view, proj); + glm::vec2 screenCenter = Engine::GetRenderManager()->WorldToScreen(glm::vec3(scaledPos, 0.f), view, proj, mainCam); + glm::vec2 screenEdge = Engine::GetRenderManager()->WorldToScreen(glm::vec3(scaledPos.x + radius, scaledPos.y, 0.f), view, proj, mainCam); float screenRadius = glm::distance(screenCenter, screenEdge); if (screenCenter.x >= 0 && screenCenter.y >= 0 && !camManager.IsScreenPointOccluded(screenCenter, mainCamIdx)) diff --git a/Engine/source/PhysicsManager.cpp b/Engine/source/PhysicsManager.cpp index 36e3c34c..1b3fa7ea 100644 --- a/Engine/source/PhysicsManager.cpp +++ b/Engine/source/PhysicsManager.cpp @@ -969,35 +969,53 @@ AABB2D PhysicsManager::ComputeAABB2D(Physics2D* body) { AABB2D resultAabb; Object* currentOwner = body->GetOwner(); - glm::vec2 position = currentOwner->GetPosition(); + if (!currentOwner) return resultAabb; - // Default half extent - glm::vec2 halfExtent(0.5f); - const auto& polygon = body->GetCollidePolygon(); + glm::vec2 position = currentOwner->GetPosition(); + float angle = glm::radians(currentOwner->GetRotate()); + float cosA = std::cos(angle); + float sinA = std::sin(angle); - // Calculate extent based on collider type - if (body->GetCollideType() == CollideType::POLYGON && polygon.size() >= 3) + if (body->GetCollideType() == CollideType::POLYGON) { + const auto& polygon = body->GetCollidePolygon(); + if (polygon.empty()) + { + resultAabb.minExtent = position - glm::vec2(0.5f); + resultAabb.maxExtent = position + glm::vec2(0.5f); + return resultAabb; + } + float minX = FLT_MAX, maxX = -FLT_MAX; float minY = FLT_MAX, maxY = -FLT_MAX; for (const auto& vertex : polygon) { - minX = std::min(minX, vertex.x); - maxX = std::max(maxX, vertex.x); - minY = std::min(minY, vertex.y); - maxY = std::max(maxY, vertex.y); + // Apply 2D rotation and translation to each vertex to calculate correct world-space AABB + float rx = vertex.x * cosA - vertex.y * sinA; + float ry = vertex.x * sinA + vertex.y * cosA; + glm::vec2 worldVertex = position + glm::vec2(rx, ry); + + minX = std::min(minX, worldVertex.x); + maxX = std::max(maxX, worldVertex.x); + minY = std::min(minY, worldVertex.y); + maxY = std::max(maxY, worldVertex.y); } - halfExtent = glm::vec2((maxX - minX) * 0.5f, (maxY - minY) * 0.5f); + resultAabb.minExtent = { minX, minY }; + resultAabb.maxExtent = { maxX, maxY }; } else if (body->GetCollideType() == CollideType::CIRCLE) { float radius = body->GetCircleCollideRadius(); - halfExtent = glm::vec2(radius); + resultAabb.minExtent = position - glm::vec2(radius); + resultAabb.maxExtent = position + glm::vec2(radius); + } + else + { + resultAabb.minExtent = position - glm::vec2(0.5f); + resultAabb.maxExtent = position + glm::vec2(0.5f); } - resultAabb.minExtent = position - halfExtent; - resultAabb.maxExtent = position + halfExtent; return resultAabb; } diff --git a/Engine/source/RenderManager.cpp b/Engine/source/RenderManager.cpp index 485ce752..e1fdcd42 100644 --- a/Engine/source/RenderManager.cpp +++ b/Engine/source/RenderManager.cpp @@ -12,6 +12,11 @@ #include "Engine.hpp" +// Debug Imgui Line Clipping +#include "imgui.h" +#include "CameraManager.hpp" +#include + // Helper function to populate bone data into vertices // This function finds the first empty slot in the vertex's bone array and fills it. void SetVertexBoneData(ThreeDimension::Vertex& vertex, int boneID, float weight) @@ -1258,12 +1263,23 @@ glm::vec2 RenderManager::WorldToScreen(glm::vec3 worldPos, const glm::mat4& view // Transform world space to clip space glm::vec4 clipSpace = proj * view * glm::vec4(worldPos, 1.0f); - // Discard points behind the camera + // Discard points behind the camera for Perspective (w <= 0) + // For Orthographic, w is usually 1.0, so we rely on NDC Z clipping if (clipSpace.w <= 0.0f) return glm::vec2{ -1, -1 }; // Perspective divide to get Normalized Device Coordinates (NDC) glm::vec3 ndc = glm::vec3(clipSpace) / clipSpace.w; + // NDC Z clipping: Discards points outside the near/far clipping planes + /*if (gMode == GraphicsMode::GL) + { + if (ndc.z < -1.0f || ndc.z > 1.0f) return glm::vec2{ -1, -1 }; + } + else // DX or VK + { + if (ndc.z < 0.0f || ndc.z > 1.0f) return glm::vec2{ -1, -1 }; + }*/ + // Map NDC to screen coordinates using ImGui viewport data ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; @@ -1276,7 +1292,7 @@ glm::vec2 RenderManager::WorldToScreen(glm::vec3 worldPos, const glm::mat4& view float screenX = (ndc.x + 1.0f) * 0.5f * (windowSize.x * vp.width) + (windowSize.x * vp.x) + windowPos.x; float screenY = 0.0f; - if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::VK) + if (gMode == GraphicsMode::VK) { screenY = (ndc.y + 1.0f) * 0.5f * (windowSize.y * vp.height) + (windowSize.y * vp.y) + windowPos.y; } @@ -1288,6 +1304,75 @@ glm::vec2 RenderManager::WorldToScreen(glm::vec3 worldPos, const glm::mat4& view return glm::vec2{ screenX, screenY }; } +void RenderManager::DrawClippedLine(ImDrawList* drawList, glm::vec2 p1, glm::vec2 p2, unsigned int color, float thickness, int mainCameraIndex) +{ + if (drawList == nullptr) return; + // Discard the entire line if any endpoint is off-screen or behind the camera + //if (p1.x <= -1.0f || p1.y <= -1.0f || p2.x <= -1.0f || p2.y <= -1.0f) return; + + struct Interval { float t0, t1; }; + std::vector intervals = { {0.0f, 1.0f} }; + + CameraManager& camManager = Engine::GetCameraManager(); + ImGuiViewport* imguiViewport = ImGui::GetMainViewport(); + glm::vec2 windowPos = { imguiViewport->Pos.x, imguiViewport->Pos.y }; + glm::vec2 windowSize = { imguiViewport->Size.x, imguiViewport->Size.y }; + + // Subtract all later camera viewports from this line segment + for (int j = mainCameraIndex + 1; j < static_cast(camManager.GetCameraCount()); ++j) + { + Camera* cam = camManager.GetCamera(j); + if (cam == nullptr || !cam->GetIsActive()) continue; + + ViewportRect vp = cam->GetViewport(); + float L = vp.x * windowSize.x + windowPos.x; + float T = vp.y * windowSize.y + windowPos.y; + float R = (vp.x + vp.width) * windowSize.x + windowPos.x; + float B = (vp.y + vp.height) * windowSize.y + windowPos.y; + + std::vector nextIntervals; + for (const auto& iv : intervals) + { + float dx = p2.x - p1.x; + float dy = p2.y - p1.y; + + float txMin = (L - p1.x) / (abs(dx) < 1e-6f ? 1e-6f : dx); + float txMax = (R - p1.x) / (abs(dx) < 1e-6f ? 1e-6f : dx); + if (txMin > txMax) std::swap(txMin, txMax); + + float tyMin = (T - p1.y) / (abs(dy) < 1e-6f ? 1e-6f : dy); + float tyMax = (B - p1.y) / (abs(dy) < 1e-6f ? 1e-6f : dy); + if (tyMin > tyMax) std::swap(tyMin, tyMax); + + float t_near = std::max(txMin, tyMin); + float t_far = std::min(txMax, tyMax); + + float overlap_t0 = std::max(iv.t0, t_near); + float overlap_t1 = std::min(iv.t1, t_far); + + if (overlap_t0 < overlap_t1 && t_near < t_far) // Overlap exists + { + if (overlap_t0 > iv.t0) nextIntervals.push_back({ iv.t0, overlap_t0 }); + if (overlap_t1 < iv.t1) nextIntervals.push_back({ overlap_t1, iv.t1 }); + } + else + { + nextIntervals.push_back(iv); + } + } + intervals = std::move(nextIntervals); + if (intervals.empty()) break; + } + + for (const auto& iv : intervals) + { + drawList->AddLine( + ImVec2(p1.x + (p2.x - p1.x) * iv.t0, p1.y + (p2.y - p1.y) * iv.t0), + ImVec2(p1.x + (p2.x - p1.x) * iv.t1, p1.y + (p2.y - p1.y) * iv.t1), + color, thickness); + } +} + void RenderManager::RenderingControllerForImGui() { // @TODO Might need to make ImGui UI remember state (FSR1/CAS) of FFX effect even if FFX is turned off diff --git a/Engine/source/VKRenderManager.cpp b/Engine/source/VKRenderManager.cpp index 97be52be..e63f7c2d 100644 --- a/Engine/source/VKRenderManager.cpp +++ b/Engine/source/VKRenderManager.cpp @@ -1139,7 +1139,15 @@ bool VKRenderManager::BeginRender(glm::vec3 bgColor) //Bind Vertex DescriptorSet vkCmdBindDescriptorSets(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline3DSkybox->GetPipeLineLayout(), 0, 1, currentFragmentSkyboxDescriptorSet, 0, nullptr); //Push Constant World-To_NDC - glm::mat4 transform[2] = { cam->GetViewMatrix(), cam->GetProjectionMatrix() }; + glm::mat4 projection = cam->GetProjectionMatrix(); + if (cam->GetCameraType() == CameraType::Orthographic) + { + float aspect = (scissor.extent.width) / (float)(scissor.extent.height); + projection = glm::perspective(glm::radians(cam->GetBaseFov()), aspect, 0.1f, 10.f); + projection[1][1] *= -1; // Vulkan Y-flip + } + + glm::mat4 transform[2] = { cam->GetViewMatrix(), projection }; vkCmdPushConstants(*currentCommandBuffer, *vkPipeline3DSkybox->GetPipeLineLayout(), VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4) * 2, &transform[0]); //Draw vkCmdDraw(*currentCommandBuffer, 36, 1, 0, 0); diff --git a/Game/source/3DPhysicsDemo.cpp b/Game/source/3DPhysicsDemo.cpp index 7f973e92..43150ac8 100644 --- a/Game/source/3DPhysicsDemo.cpp +++ b/Game/source/3DPhysicsDemo.cpp @@ -25,7 +25,7 @@ void PhysicsDemo::Init() void PhysicsDemo::Init3D() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); - Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.f); Engine::GetCameraManager().GetCamera()->SetNear(0.001f); Engine::GetCameraManager().GetCamera()->SetFar(1000.f); Engine::GetCameraManager().GetCamera()->SetBaseFov(22.5f); @@ -60,7 +60,7 @@ void PhysicsDemo::Init3D() void PhysicsDemo::Init2D() { Engine::GetRenderManager()->SetRenderType(RenderType::TwoDimension); - Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::TwoDimension, 1.f); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::Orthographic, 1.f); // Create 2D Floor Engine::GetObjectManager().AddObject(glm::vec3(0.f, -300.f, 0.f), glm::vec3(1280.f, 50.f, 1.f), "Floor2D", ObjectType::NONE); diff --git a/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp b/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp index 2cd20f88..930cbc9e 100644 --- a/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp +++ b/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp @@ -18,7 +18,7 @@ void BeatEmUpDemo::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::TwoDimension); - Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.f); Engine::GetCameraManager().GetCamera()->SetFar(91.f); Engine::GetCameraManager().GetCamera()->SetBaseFov(45.f); Engine::GetCameraManager().GetCamera()->SetCameraPosition({ 0.f,10.f, 30.f }); diff --git a/Game/source/MultipleLights.cpp b/Game/source/MultipleLights.cpp index 2e33da0d..1048fd90 100644 --- a/Game/source/MultipleLights.cpp +++ b/Game/source/MultipleLights.cpp @@ -9,7 +9,7 @@ void MultipleLights::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); - Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.f); Engine::GetCameraManager().GetCamera()->SetNear(0.01f); Engine::GetCameraManager().GetCamera()->SetFar(1000.f); Engine::GetCameraManager().GetCamera()->SetBaseFov(22.5f); diff --git a/Game/source/PBR.cpp b/Game/source/PBR.cpp index 8c09f0b7..860abfca 100644 --- a/Game/source/PBR.cpp +++ b/Game/source/PBR.cpp @@ -10,7 +10,7 @@ void PBR::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); - Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.f); Engine::GetCameraManager().GetCamera()->SetNear(cNear); Engine::GetCameraManager().GetCamera()->SetFar(cFar); Engine::GetCameraManager().GetCamera()->SetBaseFov(cFov); diff --git a/Game/source/PlatformDemo/PlatformDemo.cpp b/Game/source/PlatformDemo/PlatformDemo.cpp index cdc2e5e8..0df29e6e 100644 --- a/Game/source/PlatformDemo/PlatformDemo.cpp +++ b/Game/source/PlatformDemo/PlatformDemo.cpp @@ -18,7 +18,7 @@ void PlatformDemo::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::TwoDimension); - Engine::Instance().GetCameraManager().Init(Engine::Instance().GetWindow().GetWindowSize(), CameraType::TwoDimension, 1.f); + Engine::Instance().GetCameraManager().Init(Engine::Instance().GetWindow().GetWindowSize(), CameraType::Orthographic, 1.f); platformDemoSystem = new PlatformDemoSystem(); platformDemoSystem->Init(); diff --git a/Game/source/PocketBallDemo/PocketBallDemo.cpp b/Game/source/PocketBallDemo/PocketBallDemo.cpp index fcf36d44..ce00d544 100644 --- a/Game/source/PocketBallDemo/PocketBallDemo.cpp +++ b/Game/source/PocketBallDemo/PocketBallDemo.cpp @@ -15,7 +15,7 @@ void PocketBallDemo::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::TwoDimension); - Engine::Instance().GetCameraManager().Init(Engine::Instance().GetWindow().GetWindowSize(), CameraType::TwoDimension, 1.f); + Engine::Instance().GetCameraManager().Init(Engine::Instance().GetWindow().GetWindowSize(), CameraType::Orthographic, 1.f); Engine::GetRenderManager()->LoadTexture("../Game/assets/PocketBall/White.png", "White", true); Engine::GetRenderManager()->LoadTexture("../Game/assets/PocketBall/1.png", "1", true); diff --git a/Game/source/ProceduralMeshes.cpp b/Game/source/ProceduralMeshes.cpp index 41554dfd..7f62b71c 100644 --- a/Game/source/ProceduralMeshes.cpp +++ b/Game/source/ProceduralMeshes.cpp @@ -14,7 +14,7 @@ void ProceduralMeshes::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); - Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.f); Engine::GetCameraManager().GetCamera()->SetNear(cNear); Engine::GetCameraManager().GetCamera()->SetFar(cFar); Engine::GetCameraManager().GetCamera()->SetBaseFov(cFov); diff --git a/Game/source/SkeletalAnimationDemo.cpp b/Game/source/SkeletalAnimationDemo.cpp index 6f8fb361..7ac7feb8 100644 --- a/Game/source/SkeletalAnimationDemo.cpp +++ b/Game/source/SkeletalAnimationDemo.cpp @@ -14,7 +14,7 @@ void SkeletalAnimationDemo::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); - Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.f); Engine::GetCameraManager().GetCamera()->SetNear(cNear); Engine::GetCameraManager().GetCamera()->SetFar(cFar); Engine::GetCameraManager().GetCamera()->SetBaseFov(cFov); diff --git a/Game/source/VerticesDemo.cpp b/Game/source/VerticesDemo.cpp index 78f59f22..f91eec80 100644 --- a/Game/source/VerticesDemo.cpp +++ b/Game/source/VerticesDemo.cpp @@ -10,7 +10,7 @@ void VerticesDemo::Init() { Engine::GetRenderManager()->SetRenderType(RenderType::TwoDimension); - Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.f); Engine::GetCameraManager().GetCamera()->SetFar(45.f); Engine::GetCameraManager().GetCamera()->SetBaseFov(22.5f); Engine::GetCameraManager().GetCamera()->SetCameraSensitivity(10.f);