diff --git a/Engine/include/BasicComponents/DynamicSprite.hpp b/Engine/include/BasicComponents/DynamicSprite.hpp index e6bfe52b..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; @@ -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/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..0af70f9d 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,15 +44,38 @@ 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 ; + void Init() override; void Update(float dt) override; void UpdatePhysics(float dt); void End() override {}; @@ -94,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; } @@ -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 }; @@ -168,33 +203,46 @@ 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; - //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 ========// + //@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/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/Camera.hpp b/Engine/include/Camera.hpp index 3b7276df..51ef8a7a 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, @@ -19,8 +21,8 @@ enum class CameraMoveDir enum class CameraType { - TwoDimension, - ThreeDimension, + Orthographic, + Perspective, NONE }; @@ -32,6 +34,14 @@ enum CameraCenterMode NormalizedDeviceCoordinates }; +struct ViewportRect +{ + float x; + float y; + float width; + float height; +}; + class Camera { public: @@ -40,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; @@ -58,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; } @@ -75,21 +85,30 @@ 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 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; } float GetPitch() { return pitch; } float GetYaw() { return yaw; } + float GetRoll() { return roll; } float GetBaseFov() { return baseFov; } 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; } @@ -97,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 }; @@ -109,22 +128,26 @@ 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 + + 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..e5fb6078 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 { @@ -11,66 +14,30 @@ 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(); - 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; + bool showCameraDebug = true; 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/Engine.hpp b/Engine/include/Engine.hpp index 4e353278..37a8b985 100644 --- a/Engine/include/Engine.hpp +++ b/Engine/include/Engine.hpp @@ -11,10 +11,11 @@ #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" +#include "SkeletalAnimationManager.hpp" class Engine { @@ -35,6 +36,9 @@ 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; } + static SkeletalAnimationManager& GetSkeletalAnimationManager() { return Instance().skeletalAnimationManager; } void Init(const char* title, int windowWidth, int windowHeight, bool fullScreen, WindowMode mode); void Update(); @@ -61,6 +65,8 @@ class Engine SpriteManager* spriteManager; ParticleManager particleManager; PhysicsManager physicsManager; - ThreadManager threadManager; + JobSystem jobSystem; + InputSnapshot inputSnapshot; + SkeletalAnimationManager skeletalAnimationManager; 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/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 e212960a..792c7e77 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 @@ -46,24 +49,28 @@ 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; + 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/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/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/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/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/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/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/include/RenderManager.hpp b/Engine/include/RenderManager.hpp index eca4b993..050f2abe 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, @@ -124,7 +147,8 @@ 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 DrawClippedLine(struct ImDrawList* drawList, glm::vec2 p1, glm::vec2 p2, unsigned int color, float thickness = 1.0f, int mainCameraIndex = 0); void RenderingControllerForImGui(); //Skybox @@ -211,6 +235,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/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/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 a47f2fc5..f43042e2 100644 Binary files a/Engine/shaders/spirv/Normal3D.vert.spv and b/Engine/shaders/spirv/Normal3D.vert.spv differ diff --git a/Engine/source/BasicComponents/DynamicSprite.cpp b/Engine/source/BasicComponents/DynamicSprite.cpp index 0af6ee31..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; } @@ -323,7 +326,7 @@ void DynamicSprite::UpdateProjection() } } -void DynamicSprite::AddQuad(glm::vec4 color_) +void DynamicSprite::CreateQuad(glm::vec4 color_) { SubMesh subMesh; @@ -380,7 +383,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 +468,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 +490,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..cf679a66 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, @@ -20,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; @@ -36,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; @@ -52,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()) { @@ -194,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()) @@ -253,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/Physics2D.cpp b/Engine/source/BasicComponents/Physics2D.cpp index 0bcbc63f..bfd7eac1 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)) - { - return false; // Gap found, no collision - } - if (axisDepth < depth) + if (IsSeparatingAxis(cachedAxis, rotatedPoints1, rotatedPoints2, &dummyDepth, &dummyMin1, &dummyMax1, &dummyMin2, &dummyMax2)) { - 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 && @@ -235,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) @@ -389,9 +432,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; @@ -445,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) { - normal = -normal; + if (glm::dot(relativeVel, normal) > 0.f) + { + normal = -normal; + } + } + else + { + if (glm::dot(direction, normal) < 0.f) + { + normal = -normal; + } } if (mode == CollisionMode::COLLIDE && @@ -487,8 +545,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 +772,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/BasicComponents/SkeletalAnimator.cpp b/Engine/source/BasicComponents/SkeletalAnimator.cpp index 6d4dc889..ff75ec03 100644 --- a/Engine/source/BasicComponents/SkeletalAnimator.cpp +++ b/Engine/source/BasicComponents/SkeletalAnimator.cpp @@ -1,4 +1,4 @@ -//Author: DOYEONG LEE +//Author: DOYEONG LEE //Project: CubeEngine //File: SkeletalAnimator.cpp @@ -8,6 +8,7 @@ #include "BasicComponents/DynamicSprite.hpp" #include "BufferWrapper.hpp" #include "Object.hpp" +#include "Engine.hpp" #include "SkeletalAnimation/SkeletalAnimation.hpp" #define GLM_ENABLE_EXPERIMENTAL @@ -30,10 +31,30 @@ SkeletalAnimator::SkeletalAnimator() finalBoneMatrices.push_back(glm::mat4(1.0f)); } -void SkeletalAnimator::Init() {} -void SkeletalAnimator::End() {} +SkeletalAnimator::~SkeletalAnimator() +{ + End(); +} + +void SkeletalAnimator::Init() +{ + Engine::GetSkeletalAnimationManager().AddAnimator(this); +} + +void SkeletalAnimator::End() +{ + Engine::GetSkeletalAnimationManager().RemoveAnimator(this); +} + void SkeletalAnimator::Update(float dt) +{ + // Already updated by SkeletalAnimationManager in parallel worker thread — just return + //UpdateBoneTransforms(dt); + //QueueGPUBoneUpload(); +} + +void SkeletalAnimator::UpdateBoneTransforms(float dt) { if (!currentAnimation) return; @@ -142,8 +163,6 @@ void SkeletalAnimator::Update(float dt) glm::vec3 objRotE = owner->GetRotate3D(); 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/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..47145966 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() @@ -20,39 +20,39 @@ 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; direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); + switch (Engine::GetRenderManager()->GetGraphicsMode()) { case GraphicsMode::GL: @@ -65,6 +65,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); @@ -72,18 +73,32 @@ 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()) { case GraphicsMode::GL: - if (isThirdPersonView == true) { view = glm::lookAt(cameraPosition, cameraCenter, up); @@ -95,8 +110,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 +126,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 +141,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; } @@ -140,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; @@ -167,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; } @@ -192,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; } @@ -203,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; } @@ -214,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; } @@ -225,7 +247,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; @@ -256,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; @@ -272,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); @@ -332,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; @@ -348,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 a25c1844..b370cf5f 100644 --- a/Engine/source/CameraManager.cpp +++ b/Engine/source/CameraManager.cpp @@ -9,42 +9,178 @@ 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)); + + 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(); +} + +void CameraManager::DeleteCamera(int index) +{ + if (index >= 0 && index < cameras.size()) + { + cameras.erase(cameras.begin() + index); + + // Adjust mainCameraIndex + if (index < mainCameraIndex) + { + mainCameraIndex--; + } + if (mainCameraIndex >= static_cast(cameras.size())) + { + mainCameraIndex = std::max(0, static_cast(cameras.size()) - 1); + } + + // Adjust selectedCameraIndex + if (index < selectedCameraIndex) + { + selectedCameraIndex--; + } + if (selectedCameraIndex >= static_cast(cameras.size())) + { + selectedCameraIndex = std::max(0, 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); - switch (type) + if (mainCam != nullptr) { - case CameraType::TwoDimension: - break; - case CameraType::ThreeDimension: - break; - default: - break; + mainCam->SetViewSize(static_cast(viewSize.x), static_cast(viewSize.y)); + mainCam->SetZoom(zoom); + mainCam->RotateOrthographic(angle); } + Engine::GetLogger().LogDebug(LogCategory::Engine, "Camera Manager Initialized"); } 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 +190,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 +235,315 @@ 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 (GetCameraType() == CameraType::ThreeDimension) + if (ImGui::Button("Add Ortho")) { - float nearClip = GetNear(); - float farClip = GetFar(); - float pitch = GetPitch(); - float yaw = GetYaw(); + 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) + { + ImGui::SameLine(); + if (ImGui::Button("Remove")) + { + DeleteCamera(selectedCameraIndex); + } + } + + ImGui::Checkbox("Show Camera Debug", &showCameraDebug); + + ImGui::Separator(); + + if (cameras.empty() == false) + { + Camera* selectedCam = cameras[selectedCameraIndex].get(); + + 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]); + } - glm::vec3 cameraOffset = GetCameraOffset(); - float cameraDistance = GetCameraDistance(); - bool isThirdPersonView = GetIsThirdPersonView(); + glm::vec3 position = selectedCam->GetCameraPosition(); + float zoom = selectedCam->GetZoom(); - bool isRelativeOn = Engine::GetInputManager().GetRelativeMouseMode(); - ImGui::Checkbox("Relative Mouse Mod (Press Q)", &isRelativeOn); - Engine::GetInputManager().SetRelativeMouseMode(isRelativeOn); + 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(); - ImGui::Checkbox("Third Person View Mod", &isThirdPersonView); - SetIsThirdPersonViewMod(isThirdPersonView); + glm::vec3 cameraOffset = selectedCam->GetCameraOffset(); + float cameraDistance = selectedCam->GetCameraDistance(); + bool isThirdPersonView = selectedCam->GetIsThirdPersonView(); - float cameraSensitivity = GetCameraSensitivity(); - ImGui::SliderFloat("CameraSensitivity", &cameraSensitivity, 0.1f, 100.f); - SetCameraSensitivity(cameraSensitivity); + bool isRelativeOn = Engine::GetInputManager().GetRelativeMouseMode(); + ImGui::Checkbox("Relative Mouse Mod (Press Q)", &isRelativeOn); + Engine::GetInputManager().SetRelativeMouseMode(isRelativeOn); - ImGui::DragFloat3("Position", &position.x, 0.01f); - SetCameraPosition(position); + ImGui::Checkbox("Third Person View Mod", &isThirdPersonView); + selectedCam->SetIsThirdPersonViewMod(isThirdPersonView); - ImGui::DragFloat("Zoom", &zoom, 0.5f); - SetZoom(zoom); + float cameraSensitivity = selectedCam->GetCameraSensitivity(); + ImGui::SliderFloat("CameraSensitivity", &cameraSensitivity, 0.1f, 100.f); + selectedCam->SetCameraSensitivity(cameraSensitivity); - ImGui::DragFloat("Near", &nearClip, 0.05f); - SetNear(nearClip); + ImGui::DragFloat3("Position", &position.x, 0.01f); + selectedCam->SetCameraPosition(position); - ImGui::DragFloat("Far", &farClip, 0.05f); - SetFar(farClip); + ImGui::DragFloat("Zoom", &zoom, 0.5f); + selectedCam->SetZoom(zoom); - ImGui::DragFloat("Pitch", &pitch, 0.5f); - SetPitch(pitch); + ImGui::DragFloat("Near", &nearClip, 0.05f); + selectedCam->SetNear(nearClip); - ImGui::DragFloat("Yaw", &yaw, 0.5f); - SetYaw(yaw); + ImGui::DragFloat("Far", &farClip, 0.05f); + selectedCam->SetFar(farClip); - 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("Pitch", &pitch, 0.5f); + selectedCam->SetPitch(pitch); + + 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()) { - 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::Orthographic) + { + float rotation = selectedCam->GetRotateOrthographic(); - ImGui::DragFloat("Distance", &cameraDistance, 0.05f); - SetCameraDistance(cameraDistance); - ImGui::DragFloat3("Offset", &cameraOffset.x, 0.01f); - SetCameraOffset(cameraOffset); + ImGui::DragFloat3("Position", &position.x, 0.1f); + selectedCam->SetCameraPosition(position); + + ImGui::DragFloat("Zoom", &zoom, 0.1f); + selectedCam->SetZoom(zoom); + + ImGui::DragFloat("Rotation", &rotation, 0.5f); + selectedCam->RotateOrthographic(rotation); } } } - else if (GetCameraType() == CameraType::TwoDimension) + + if (showCameraDebug) + { + 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 || 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); + + // 1. Draw camera position and name + glm::vec3 camPos = cam->GetCameraPosition(); + glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(camPos, mainView, mainProj, mainCam); + + 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()); - ImGui::DragFloat2("Position", &position.x, 0.1f); - SetCameraPosition(position); + // 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]; + 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); + } - ImGui::DragFloat("Zoom", &zoom, 0.1f); - SetZoom(zoom); + auto drawClippedLine = [&](glm::vec2 p1, glm::vec2 p2) { + Engine::GetRenderManager()->DrawClippedLine(drawList, p1, p2, color, 1.5f, mainCameraIndex); + }; - ImGui::DragFloat("Rotation", &rotate2D, 0.5f); - SetRotate2D(rotate2D); + // 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]); } - ImGui::End(); + drawList->PopClipRect(); } void CameraManager::ControlCamera(float dt) { - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::W)) - { - MoveCameraPos(CameraMoveDir::FOWARD, 5.f * dt); - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::S)) - { - MoveCameraPos(CameraMoveDir::BACKWARD, 5.f * dt); - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::A)) - { - MoveCameraPos(CameraMoveDir::LEFT, 5.f * dt); - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::D)) + Camera* selectedCam = GetCamera(selectedCameraIndex); + + if (selectedCam == nullptr || selectedCam->GetIsActive() == false) { - MoveCameraPos(CameraMoveDir::RIGHT, 5.f * dt); + return; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::SPACE)) + + if (selectedCam->GetCameraType() == CameraType::Perspective) { - 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) { - 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) { - 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); + if (selectedCam->GetCameraType() == CameraType::Perspective) + { + 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 172738e2..483b8519 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) @@ -130,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(); @@ -148,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); @@ -166,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()); @@ -283,9 +308,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/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..2aba3d9e 100644 --- a/Engine/source/DXRenderManager.cpp +++ b/Engine/source/DXRenderManager.cpp @@ -396,7 +396,40 @@ 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); + + 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; case RenderType::ThreeDimension: @@ -411,11 +444,46 @@ 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); + + 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 @@ -429,13 +497,52 @@ 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 (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); + } + } } - 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..6dcc92bd 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); + 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 - 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 @@ -413,14 +436,17 @@ 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]) drawList->AddLine(screenCorners[index1], screenCorners[index2], IM_COL32(0, 255, 255, 255), 2.0f); + if (valid[index1] && valid[index2]) + { + 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); + } }; // Near @@ -429,5 +455,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..6f0d4eef 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,36 @@ 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 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/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 6c59f1dc..0a361ebb 100644 --- a/Engine/source/Engine.cpp +++ b/Engine/source/Engine.cpp @@ -38,12 +38,11 @@ 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; - threadManager.Start(); + jobSystem.Start(); } void Engine::Update() @@ -60,7 +59,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 +99,7 @@ void Engine::End() break; } - threadManager.Stop(); + jobSystem.Stop(); } void Engine::SetFPS(FrameRate fps) diff --git a/Engine/source/GLRenderManager.cpp b/Engine/source/GLRenderManager.cpp index 3474d82f..297f5039 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 @@ -62,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)); @@ -129,156 +135,180 @@ 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) + for (auto& subMesh : sprite->GetSubMeshes()) { - case RenderType::TwoDimension: - { - 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); + spriteData->GetVertexUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::VertexUniform), &spriteData->vertexUniform); + spriteData->GetFragmentUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::FragmentUniform), &spriteData->fragmentUniform); + spriteData->GetMaterialUniformBuffer>()->UpdateUniform(sizeof(ThreeDimension::Material), &spriteData->material); - //auto& materialUniformBuffer = std::get*>(sprite->GetMaterialUniformBuffer()->buffer); - 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); - buffer->normalVertexArray->Use(true); - GLsizei size = static_cast(spriteData->normalVertices.size()); - glDrawArrays(GL_LINES, 0, size); - buffer->normalVertexArray->Use(false); + // Bind VertexUniform (binding 2) so Normal3D.vert can access finalBones[] + glCheck(glBindBufferBase(GL_UNIFORM_BUFFER, 2, spriteData->GetVertexUniformBuffer>()->GetHandle())); - 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()); + + 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")); + 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 05466446..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()); @@ -172,15 +174,47 @@ void GameStateManager::UpdateGameLogic(float dt) } else { + // Main thread — level logic (may call PlayAnimation, queue object ops) levelList.at(static_cast(currentLevel))->Update(dt); - Engine::GetObjectManager().Update(dt); - Engine::GetParticleManager().Update(dt); + + auto& js = Engine::GetJobSystem(); + + // Parallel — object and particle updates + auto objectsHandle = js.QueueWork([dt]() + { + Engine::GetObjectManager().Update(dt); + }); + + auto particleHandle = js.QueueWork([dt]() + { + Engine::GetParticleManager().Update(dt); + }); + + js.WaitForAll({ objectsHandle, particleHandle }); + + // Bone matrix calculation + auto animHandle = js.QueueWork([dt]() + { + Engine::GetSkeletalAnimationManager().Update(dt); + }); + js.WaitForWork(animHandle); + + // GPU upload + Engine::GetRenderManager()->ProcessGPUCommands(); + + // Physics — after object updates (objects may modify velocities) + auto physicsHandle = js.QueueWork([dt]() + { + Engine::GetPhysicsManager().Update(dt); + }); + js.WaitForWork(physicsHandle); + + // Main thread — camera (uses InputManager directly for mouse mode) Engine::GetCameraManager().Update(); - Engine::GetPhysicsManager().Update(dt); - Engine::GetSpriteManager().Update(dt); } } + void GameStateManager::UpdateDraw(float dt) { if (!(SDL_GetWindowFlags(Engine::GetWindow().GetWindow()) & SDL_WINDOW_MINIMIZED)) diff --git a/Engine/source/InputManager.cpp b/Engine/source/InputManager.cpp index 5b0201a7..c8a44be9 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().GetCamera()->GetViewSize(); + int w = 0; + int h = 0; + SDL_GetWindowSizeInPixels(Engine::Instance().GetWindow().GetWindow(), &w, &h); + 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; +} + +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..cfefc256 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() @@ -974,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(); } } @@ -1037,29 +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); - - // 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); - } + glm::vec2 screenPos = Engine::GetRenderManager()->WorldToScreen(currentPos, view, proj, mainCam); // 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); @@ -1076,17 +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 - if (screenPos.x >= 0 && screenPos.y >= 0 && - childScreenPos.x >= 0 && childScreenPos.y >= 0) + // 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); @@ -1098,7 +1118,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; @@ -1141,8 +1161,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; @@ -1407,10 +1427,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()) { @@ -1436,13 +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) { - 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) { @@ -1482,7 +1519,7 @@ 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) { @@ -1490,11 +1527,12 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) glm::vec2 p2 = screenPoints[edges[i][1]]; 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); } } } } + drawList->PopClipRect(); } void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) @@ -1503,10 +1541,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()) { @@ -1549,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); } @@ -1559,7 +1614,7 @@ void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) glm::vec2 p2 = screenPoints[(i + 1) % screenPoints.size()]; 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); } } } @@ -1567,13 +1622,14 @@ 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) + 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/PhysicsManager.cpp b/Engine/source/PhysicsManager.cpp index c60e7261..1b3fa7ea 100644 --- a/Engine/source/PhysicsManager.cpp +++ b/Engine/source/PhysicsManager.cpp @@ -41,39 +41,112 @@ 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 (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 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 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 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 +157,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(); @@ -109,87 +182,89 @@ 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); } -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; } @@ -261,13 +336,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) @@ -285,81 +360,158 @@ 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) { - 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) @@ -384,7 +536,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); @@ -394,7 +546,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); @@ -419,27 +571,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) { @@ -447,8 +599,615 @@ 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(); + if (!currentOwner) return resultAabb; + + glm::vec2 position = currentOwner->GetPosition(); + float angle = glm::radians(currentOwner->GetRotate()); + float cosA = std::cos(angle); + float sinA = std::sin(angle); + + 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) + { + // 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); + } + resultAabb.minExtent = { minX, minY }; + resultAabb.maxExtent = { maxX, maxY }; + } + else if (body->GetCollideType() == CollideType::CIRCLE) + { + float radius = body->GetCircleCollideRadius(); + 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); + } + + 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/Engine/source/RenderManager.cpp b/Engine/source/RenderManager.cpp index 7e818a64..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) @@ -503,10 +508,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; + + 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]; + } - normalVertices.push_back(ThreeDimension::NormalVertex{ start }); - normalVertices.push_back(ThreeDimension::NormalVertex{ end }); + 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 +672,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 +943,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]; + } - normalVertices.push_back(ThreeDimension::NormalVertex{ start }); - normalVertices.push_back(ThreeDimension::NormalVertex{ end }); + 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 } @@ -1060,7 +1116,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 } } @@ -1184,38 +1258,121 @@ 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); - // 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* 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 }; + + Camera* cam = camera ? camera : Engine::GetCameraManager().GetCamera(); + ViewportRect vp = cam->GetViewport(); - // 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; + // 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) + if (gMode == 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 }; } +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/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/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/Engine/source/VKRenderManager.cpp b/Engine/source/VKRenderManager.cpp index 50adb7e9..e63f7c2d 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; } @@ -891,174 +927,245 @@ 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); + vertexMappedMemory3D = uniformBuffer3D.vertexUniformBuffer->GetMappedMemory(frameIndex); + fragmentMappedMemory3D = uniformBuffer3D.fragmentUniformBuffer->GetMappedMemory(frameIndex); + materialMappedMemory3D = uniformBuffer3D.materialUniformBuffer->GetMappedMemory(frameIndex); + } - // Bind Pipeline - vkCmdBindPipeline(*currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, *vkPipeline2D->GetPipeLine()); - for (size_t i = 0; i < sprites.size(); ++i) + 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) { - for (auto& subMesh : sprites[i]->GetSubMeshes()) + case RenderType::TwoDimension: + { + // 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 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); + for (auto& subMesh : sprites[i]->GetSubMeshes()) + { + 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 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 + vkCmdDrawIndexed(*currentCommandBuffer, static_cast(spriteData->indices.size()), 1, 0, 0, 0); + + subMeshIndex2D++; + } } } - uniformBuffer2D.vertexUniformBuffer->UnmapMemory(frameIndex); - uniformBuffer2D.fragmentUniformBuffer->UnmapMemory(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 (auto& subMesh : sprite->GetSubMeshes()) + break; + case RenderType::ThreeDimension: + for (const auto& sprite : sprites) { - 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); + 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) + 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 + + 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 projection = cam->GetProjectionMatrix(); + if (cam->GetCameraType() == CameraType::Orthographic) { - 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); - vkCmdSetScissor(*currentCommandBuffer, 0, 1, &scissor); - //Change Primitive Topology - //vkCmdSetPrimitiveTopology(*currentCommandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); - //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); + 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 } -#endif - subMeshIndex++; + 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); } + 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 598b7143..43150ac8 100644 --- a/Game/source/3DPhysicsDemo.cpp +++ b/Game/source/3DPhysicsDemo.cpp @@ -25,13 +25,13 @@ void PhysicsDemo::Init() 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().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); + 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); @@ -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); @@ -93,21 +93,21 @@ 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; + movement += Engine::GetCameraManager().GetCamera()->GetBackVector() * speed; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::DOWN)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::DOWN)) { - movement -= Engine::GetCameraManager().GetBackVector() * speed; + movement -= Engine::GetCameraManager().GetCamera()->GetBackVector() * speed; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::RIGHT)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::RIGHT)) { - movement += Engine::GetCameraManager().GetRightVector() * speed; + movement += Engine::GetCameraManager().GetCamera()->GetRightVector() * speed; } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LEFT)) + if (Engine::GetInputSnapshot().IsKeyPressed(KEYBOARDKEYS::LEFT)) { - movement -= Engine::GetCameraManager().GetRightVector() * speed; + movement -= Engine::GetCameraManager().GetCamera()->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); } @@ -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")) { 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 c3805331..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; @@ -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) @@ -318,12 +318,12 @@ 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::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..930cbc9e 100644 --- a/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp +++ b/Game/source/BeatEmUpDemo/BeatEmUpDemo.cpp @@ -18,11 +18,11 @@ 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().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 }); + 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); @@ -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); + Engine::GetCameraManager().GetCamera()->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); + Engine::GetCameraManager().GetCamera()->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); + Engine::GetCameraManager().GetCamera()->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); + Engine::GetCameraManager().GetCamera()->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/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..1048fd90 100644 --- a/Game/source/MultipleLights.cpp +++ b/Game/source/MultipleLights.cpp @@ -9,13 +9,13 @@ 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().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); + 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..860abfca 100644 --- a/Game/source/PBR.cpp +++ b/Game/source/PBR.cpp @@ -10,13 +10,13 @@ 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().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.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/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..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(); @@ -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); } @@ -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 509d89f2..2c73f404 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) { @@ -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/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/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; diff --git a/Game/source/ProceduralMeshes.cpp b/Game/source/ProceduralMeshes.cpp index 9b7d19ac..7f62b71c 100644 --- a/Game/source/ProceduralMeshes.cpp +++ b/Game/source/ProceduralMeshes.cpp @@ -14,13 +14,13 @@ 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().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.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..7ac7feb8 100644 --- a/Game/source/SkeletalAnimationDemo.cpp +++ b/Game/source/SkeletalAnimationDemo.cpp @@ -14,12 +14,12 @@ 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().Init(Engine::GetWindow().GetWindowSize(), CameraType::Perspective, 1.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..f91eec80 100644 --- a/Game/source/VerticesDemo.cpp +++ b/Game/source/VerticesDemo.cpp @@ -10,10 +10,10 @@ 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().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); Engine::GetRenderManager()->LoadTexture("../Game/assets/texture_sample2.jpg", "1", true); Engine::GetRenderManager()->LoadTexture("../Game/assets/texture_sample.jpg", "2", true);