diff --git a/Engine/include/BasicComponents/Physics2D.hpp b/Engine/include/BasicComponents/Physics2D.hpp index 92d2233f..07056cec 100644 --- a/Engine/include/BasicComponents/Physics2D.hpp +++ b/Engine/include/BasicComponents/Physics2D.hpp @@ -7,6 +7,7 @@ #include #include "Interface/IComponent.hpp" +#include "CollisionMode.hpp" #ifdef _DEBUG #include "BasicComponents/DynamicSprite.hpp" #endif @@ -45,7 +46,6 @@ class Physics2D : public IComponent void Init() override ; void Update(float dt) override; - void UpdateForParticle(float dt, glm::vec3& pos); void End() override {}; void SetVelocity(glm::vec2 v) { velocity = v; } @@ -63,25 +63,45 @@ class Physics2D : public IComponent void AddForceX(float amount) { force.x = amount; } void AddForceY(float amount) { force.y = amount; } + glm::vec2 GetAngularVelocity() const { return angularVelocity; } + float GetMomentOfInertia() const { return momentOfInertia; } + float GetInverseInertia() const { return inverseInertia; } + + void SetAngularVelocity(glm::vec2 v) { angularVelocity = v; } + void AddTorque(glm::vec2 t) { torque += t; } + void SetMomentOfInertia(float i); + void SetFriction(float f) { friction = f; } - void SetGravity(float g, bool isGravityOn_ = true) { gravity = g; isGravityOn = isGravityOn_; } - void SetIsGravityOn(bool isGravityOn_) {isGravityOn = isGravityOn_; } + void SetGravity(float g, bool isGravityOnParam = true) { gravity = g; isGravityOn = isGravityOnParam; } + void SetIsGravityOn(bool isGravityOnParam) {isGravityOn = isGravityOnParam; } void SetMass(float amount) { mass = amount; } void SetRestitution(float amount) { restitution = amount; } void SetBodyType(BodyType type) { bodyType = type; }; void SetIsGhostCollision(bool state) { isGhostCollision = state; } + // Getters BodyType GetBodyType() { return bodyType; }; CollideType GetCollideType() { return collideType; }; float GetCircleCollideRadius() { return circle.radius; } std::vector GetCollidePolygon() { return collidePolygon; } float GetRestitution() { return restitution; } + bool GetIsGravityOn() const { return isGravityOn; } bool GetIsGhostCollision() { return isGhostCollision; } + bool GetEnableRotationalPhysics() const { return enableRotationalPhysics; } + + void SetEnableRotationalPhysics(bool v) { enableRotationalPhysics = v; } + + float GetGravity() const { return gravity; } + float GetFriction() const { return friction; } + float GetMass() const { return mass; } + glm::vec2 GetMinVelocity() const { return velocityMin; } + glm::vec2 GetMaxVelocity() const { return velocityMax; } + bool CheckCollision(Object* obj); - bool CollisionPP(Object* obj, Object* obj2); - bool CollisionCC(Object* obj, Object* obj2); - bool CollisionPC(Object* poly, Object* cir); + bool CollisionPP(Object* obj, Object* obj2, CollisionMode mode = static_cast(0)); + bool CollisionCC(Object* obj, Object* obj2, CollisionMode mode = static_cast(0)); + bool CollisionPC(Object* poly, Object* cir, CollisionMode mode = static_cast(0)); bool CollisionPPWithoutPhysics(Object* obj, Object* obj2); void AddCollideCircle(float r); @@ -89,16 +109,20 @@ class Physics2D : public IComponent void AddCollidePolygonAABB(glm::vec2 min, glm::vec2 max); void AddCollidePolygonAABB(glm::vec2 size); private: - glm::vec2 FindSATCenter(const std::vector& points_); + // Mathematical helper methods for collision detection + glm::vec2 FindSATCenter(const std::vector& points); glm::vec2 FindClosestPointOnSegment(const glm::vec2& circleCenter, std::vector& vertices); float calculatePolygonRadius(const std::vector& vertices); float DegreesToRadians(float degrees); glm::vec2 RotatePoint(const glm::vec2 point, const glm::vec2 size, float angle); - 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_); - void CalculateLinearVelocity(Physics2D& body, Physics2D& body2, glm::vec2 normal, float* axisDepth); + // 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); + + // Calculates and applies impulses due to collision + void CalculateLinearVelocity(Physics2D& body, Physics2D& body2, glm::vec2 normal, float* axisDepth, glm::vec2 contactPoint); float Length(glm::vec2 a) { @@ -108,10 +132,20 @@ class Physics2D : public IComponent { return static_cast(sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y))); } + + float LengthSquared(glm::vec2 a) + { + return (a.x * a.x) + (a.y * a.y); + } + float DistanceSquared(glm::vec2 a, glm::vec2 b) + { + return ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); + } Circle circle; std::vector collidePolygon; + // Physical properties of the 2D body glm::vec2 velocity = { 0.0f, 0.0f }; glm::vec2 velocityMin = { 0.f, 0.f }; glm::vec2 velocityMax = { 4.f, 4.f }; @@ -121,13 +155,18 @@ class Physics2D : public IComponent float friction = 0.9f; float gravity = 9.8f; float mass = 1.f; - float restitution = 0.f; - //float rotateVelocity = 0.f; + float restitution = 0.f; // Bounciness factor + + glm::vec2 angularVelocity = { 0.f, 0.f }; + glm::vec2 torque = { 0.f, 0.f }; + float momentOfInertia = 1.0f; + float inverseInertia = 1.0f; CollideType collideType = CollideType::NONE; BodyType bodyType = BodyType::RIGID; bool isGhostCollision = false; bool isGravityOn = false; + bool enableRotationalPhysics = false; #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 b0890ea4..e95cf3df 100644 --- a/Engine/include/BasicComponents/Physics3D.hpp +++ b/Engine/include/BasicComponents/Physics3D.hpp @@ -27,7 +27,6 @@ struct CollisionResult }; - enum class BodyType3D { RIGID = 0, @@ -45,16 +44,17 @@ struct Sphere float radius = 0; }; +#include "CollisionMode.hpp" + class Physics3D : public IComponent { public: - Physics3D() : IComponent(ComponentTypes::PHYSICS3D) {/* Init(); */}; + Physics3D() : IComponent(ComponentTypes::PHYSICS3D) { Init(); }; ~Physics3D() override; void Init() override ; void Update(float dt) override; void UpdatePhysics(float dt); - void UpdateForParticle(float dt, glm::vec3& pos); void End() override {}; void SetVelocity(glm::vec3 v) { velocity = v; } @@ -66,7 +66,15 @@ class Physics3D : public IComponent void SetMinVelocity(glm::vec3 v) { velocityMin = v; } void SetMaxVelocity(glm::vec3 v) { velocityMax = v; } - glm::vec3 GetVelocity() const { return velocity; } + glm::vec3 GetVelocity() const { return velocity; } + glm::vec3 GetAngularVelocity() const { return angularVelocity; } + float GetMomentOfInertia() const { return momentOfInertia; } + float GetInverseInertia() const { return inverseInertia; } + + void SetAngularVelocity(glm::vec3 v) { angularVelocity = v; } + void AddTorque(glm::vec3 t) { torque += t; } + void SetMomentOfInertia(float i); + glm::vec3 GetMinVelocity() const { return velocityMin; } glm::vec3 GetMaxVelocity() const { return velocityMax; } float GetGravity() const { return gravity; } @@ -77,16 +85,26 @@ class Physics3D : public IComponent glm::vec3 GetPosition() const { return GetOwner()->GetPosition(); } float GetRestitution() const { return restitution; } - void SetAcceleration(glm::vec3 v) { acceleration = v; }; - void AddForce(glm::vec3 v) { force = v; } - void AddForceX(float amount) { force.x = amount; } - void AddForceY(float amount) { force.y = amount; } - void AddForceZ(float amount) { force.z = amount; } + void Awake(); + void SetAcceleration(glm::vec3 v); + void AddForce(glm::vec3 v); + void AddForceX(float amount); + void AddForceY(float amount); + void AddForceZ(float amount); void Teleport(glm::vec3 newPosition); void SetFriction(float f) { friction = f; } - void SetGravity(float g, bool isGravityOn_ = true) { gravity = g; isGravityOn = isGravityOn_; } - void SetIsGravityOn(bool state) { isGravityOn = state; } + void SetGravity(float g, bool isGravityOnParam = true) + { + gravity = g; + isGravityOn = isGravityOnParam; + if (isGravityOnParam) Awake(); + } + void SetIsGravityOn(bool state) + { + isGravityOn = state; + if (state) Awake(); + } void SetMass(float m); void SetRestitution(float amount) { restitution = amount; } @@ -101,21 +119,30 @@ class Physics3D : public IComponent void SetIsGhostCollision(bool state) { isGhostCollision = state; } void SetBodyType(BodyType3D type) { bodyType = type; }; void SetCollisionDetectionMode(CollisionDetectionMode mode) { collisionMode = mode; } + bool GetEnableRotationalPhysics() const { return enableRotationalPhysics; } + void SetEnableRotationalPhysics(bool v); //2d->3d std::vector GetCollidePolyhedron() { return collidePolyhedron; } float GetSphereRadius() const { return sphere.radius; } + bool CheckCollision(Object* obj); - bool CollisionPP(Object* obj, Object* obj2); - bool CollisionSS(Object* obj, Object* obj2); - bool CollisionPS(Object* poly, Object* sph); + bool CollisionPP(Object* obj, Object* obj2, CollisionMode mode = static_cast(0)); + bool CollisionSS(Object* obj, Object* obj2, CollisionMode mode = static_cast(0)); + bool CollisionPS(Object* poly, Object* sph, CollisionMode mode = static_cast(0)); void AddCollidePolyhedron(glm::vec3 position); void AddCollidePolyhedronAABB(glm::vec3 min, glm::vec3 max); void AddCollidePolyhedronAABB(glm::vec3 size); void AddCollideSphere(float r); //2d->3d + + // 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); + private: + // Linear Physical Properties glm::vec3 velocity = { 0.0f, 0.0f, 0.0f }; glm::vec3 velocityMin = { 0.f, 0.f, 0.0f }; glm::vec3 velocityMax = { 4.f, 4.f, 4.f }; @@ -125,26 +152,36 @@ class Physics3D : public IComponent float friction = 0.9f; float gravity = 9.8f; float mass = 1.f; - float restitution = 0.f; - glm::quat orientation = glm::quat{ 1.0f, 0.0f, 0.0f, 0.0f }; + float restitution = 0.f; // Bounciness factor + glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + // Physics States bool isGhostCollision = false; bool isGravityOn = false; + bool enableRotationalPhysics = false; + bool isSleeping = false; + float sleepTimer = 0.0f; + float angularDamping = 0.5f; + + glm::vec3 angularVelocity = { 0.f, 0.f, 0.f }; + glm::vec3 torque = { 0.f, 0.f, 0.f }; + float momentOfInertia = 1.0f; + float inverseInertia = 1.0f; ColliderType3D colliderType = ColliderType3D::BOX; BodyType3D bodyType = BodyType3D::RIGID; CollisionDetectionMode collisionMode = CollisionDetectionMode::DISCRETE; //2d->3d - glm::vec3 FindSATCenter(const std::vector& points_); + // 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_); - void CalculateLinearVelocity(Physics3D& body, Physics3D& body2, glm::vec3 normal, float* axisDepth); + 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_); + 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); - //CollisionDetectionMode : Continuous + // 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, @@ -153,7 +190,6 @@ class Physics3D : public IComponent bool SweptSATOBB(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult); bool SweptSpheres(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult); bool SweptSphereVsOBB(Physics3D* boxBody, float dt, CollisionResult& outResult); - CollisionResult FindClosestCollision(float dt); //CollisionDetectionMode : Continuous std::vector collidePolyhedron; diff --git a/Engine/include/Camera.hpp b/Engine/include/Camera.hpp index e3e20dd3..3b7276df 100644 --- a/Engine/include/Camera.hpp +++ b/Engine/include/Camera.hpp @@ -70,6 +70,7 @@ class Camera float GetCameraSensitivity() { return cameraSensitivity; } void SetCameraType(CameraType type) { cameraType = type; } + CameraType GetCameraType() const { return cameraType; } void SetNear(float amount) noexcept { nearClip = amount; } void SetFar(float amount) noexcept { farClip = amount; } void SetPitch(float amount) noexcept { pitch = amount; } diff --git a/Engine/include/CameraManager.hpp b/Engine/include/CameraManager.hpp index 41399a1b..e15e676d 100644 --- a/Engine/include/CameraManager.hpp +++ b/Engine/include/CameraManager.hpp @@ -14,6 +14,7 @@ class CameraManager void Init(glm::vec2 viewSize, CameraType type = CameraType::TwoDimension, float zoom = 45.f, float angle = 0.f); void Update(); void Reset(); + CameraType GetCameraType() const { return camera.GetCameraType(); } // 2D, 3D void SetZoom(float zoom) noexcept { camera.SetZoom(zoom); } diff --git a/Engine/include/CollisionMode.hpp b/Engine/include/CollisionMode.hpp new file mode 100644 index 00000000..bbaf6e3e --- /dev/null +++ b/Engine/include/CollisionMode.hpp @@ -0,0 +1,8 @@ +#pragma once + +enum class CollisionMode +{ + COLLIDE, + DETECT, + IGNORED +}; diff --git a/Engine/include/Engine.hpp b/Engine/include/Engine.hpp index 7f343a6e..4e353278 100644 --- a/Engine/include/Engine.hpp +++ b/Engine/include/Engine.hpp @@ -14,6 +14,7 @@ #include "ThreadManager.hpp" #include "Logger.hpp" #include "Particle/ParticleManager.hpp" +#include "PhysicsManager.hpp" class Engine { @@ -31,6 +32,7 @@ class Engine static SoundManager& GetSoundManager() { return Instance().soundManager; } static SpriteManager& GetSpriteManager() { return *Instance().spriteManager; } static ParticleManager& GetParticleManager() { return Instance().particleManager; } + static PhysicsManager& GetPhysicsManager() { return Instance().physicsManager; } static Timer& GetTimer() { return Instance().timer; } static Logger& GetLogger() { return *Instance().logger; } @@ -58,6 +60,7 @@ class Engine SoundManager soundManager; SpriteManager* spriteManager; ParticleManager particleManager; + PhysicsManager physicsManager; ThreadManager threadManager; Logger* logger; }; \ No newline at end of file diff --git a/Engine/include/GameStateManager.hpp b/Engine/include/GameStateManager.hpp index 5cbe0089..916eed45 100644 --- a/Engine/include/GameStateManager.hpp +++ b/Engine/include/GameStateManager.hpp @@ -45,7 +45,6 @@ class GameStateManager private: const char* GameLevelTypeEnumToChar(GameLevel type); void LevelInit(); - void CollideObjects(); GameLevel currentLevel = GameLevel::NONE; GameLevel levelSelected = GameLevel::NONE; diff --git a/Engine/include/ObjectManager.hpp b/Engine/include/ObjectManager.hpp index 120bf4f5..efa48f7e 100644 --- a/Engine/include/ObjectManager.hpp +++ b/Engine/include/ObjectManager.hpp @@ -15,6 +15,7 @@ class DynamicSprite; class Physics3D; +class Physics2D; class Light; class SkeletalAnimator; @@ -95,7 +96,9 @@ class ObjectManager void ProcessFunctionQueue(); private: void Physics3DControllerForImGui(Physics3D* phy); + void Physics2DControllerForImGui(Physics2D* phy); void RenderPhysics3DDebug(Physics3D* phy); + void RenderPhysics2DDebug(Physics2D* phy); void SpriteControllerForImGui(DynamicSprite* sprite); void LightControllerForImGui(Light* light); diff --git a/Engine/include/Particle/Particle.hpp b/Engine/include/Particle/Particle.hpp index 328b0742..202d5ce7 100644 --- a/Engine/include/Particle/Particle.hpp +++ b/Engine/include/Particle/Particle.hpp @@ -4,7 +4,6 @@ #pragma once #include #include -#include "BasicComponents/Physics2D.hpp" #include "BasicComponents/DynamicSprite.hpp" enum class ParticleType @@ -34,12 +33,19 @@ class Particle void SetFadeOutAmount(float amount) { fadeOutAmount = amount; } DynamicSprite* GetSprite() { return sprite; } float GetLifetime() { return lifeTime; } - Physics2D* GetPhysics() { return &pPhysics; } + + void SetGravity(float g, bool on = true) { gravity = g; useGravity = on; } + void SetDrag(float d) { drag = d; } private: glm::vec3 position = { 0.f, 0.f, 0.f }; - glm::vec3 speed = { 0.f, 0.f, 0.f }; - glm::vec3 size = { 0.f, 0.f, 0.f }; - glm::vec3 angle = { 0.f, 0.f, 0.f }; + glm::vec3 velocity = { 0.f, 0.f, 0.f }; + glm::vec3 size = { 0.f, 0.f, 0.f }; + glm::vec3 angle = { 0.f, 0.f, 0.f }; + + // Self-contained physics state. + float gravity = 9.8f; + bool useGravity = false; + float drag = 0.f; // velocity damping per second (0 = no drag) float fadeOutAmount = 0.1f; @@ -51,5 +57,4 @@ class Particle DynamicSprite* sprite = nullptr; ParticleEffect effect; - Physics2D pPhysics; }; \ No newline at end of file diff --git a/Engine/include/PhysicsManager.hpp b/Engine/include/PhysicsManager.hpp new file mode 100644 index 00000000..c1cd1ffa --- /dev/null +++ b/Engine/include/PhysicsManager.hpp @@ -0,0 +1,65 @@ +//Author: DOYEONG LEE +//Project: CubeEngine +//File: PhysicsManager.hpp +#pragma once + +#include +#include +#include + +#include "BasicComponents/Physics2D.hpp" +#include "BasicComponents/Physics3D.hpp" +#include "ObjectType.hpp" +#include "CollisionMode.hpp" + +// A pair of physics bodies that may collide this frame. +struct CollisionPair2D +{ + Physics2D* bodyA = nullptr; + Physics2D* bodyB = nullptr; +}; + +struct CollisionPair3D +{ + Physics3D* bodyA = nullptr; + Physics3D* bodyB = nullptr; +}; + +class PhysicsManager +{ +public: + PhysicsManager() = default; + ~PhysicsManager() = default; + + void Update(float dt); + + void AddBody2D(Physics2D* body); + void RemoveBody2D(Physics2D* body); + + void AddBody3D(Physics3D* body); + void RemoveBody3D(Physics3D* body); + + void SetCollisionMode(ObjectType typeA, ObjectType typeB, CollisionMode mode); + CollisionMode GetCollisionMode(ObjectType typeA, ObjectType typeB); + +private: + void UpdatePhysics2D(float dt); + std::vector BroadPhase2D(); + void ApplyMovement2D(float dt); + void NarrowPhase2D(std::vector& pairs, float dt); + + void UpdatePhysics3D(float dt); + void Integrate3D(float dt); + std::vector BroadPhase3D(float dt); + void NarrowPhase3D(std::vector& pairs, float dt); + + void SolveContinuous(Physics3D* body, float dt); + + std::vector bodies2D; + std::vector bodies3D; + + std::map, CollisionMode> collisionMaskMap; + + static constexpr int MAX_CCD_ITERATIONS = 4; + static constexpr float SKIN_WIDTH = 0.005f; +}; diff --git a/Engine/include/Timer.hpp b/Engine/include/Timer.hpp index af9b7781..9f181cb5 100644 --- a/Engine/include/Timer.hpp +++ b/Engine/include/Timer.hpp @@ -141,6 +141,15 @@ class Timer private: void UpdateHistory(float fps) { + if (frame != FrameRate::UNLIMIT) + { + float limit = static_cast(frame); + if (fps > limit) + { + fps = limit; + } + } + if (fpsHistory.empty()) { minFps = fps; diff --git a/Engine/source/BasicComponents/Physics2D.cpp b/Engine/source/BasicComponents/Physics2D.cpp index 3c4d8f93..0bcbc63f 100644 --- a/Engine/source/BasicComponents/Physics2D.cpp +++ b/Engine/source/BasicComponents/Physics2D.cpp @@ -1,756 +1,873 @@ -//Author: DOYEONG LEE +//Author: DOYEONG LEE //Project: CubeEngine //File: Physics2D.cpp + #include "BasicComponents/Physics2D.hpp" #include "BasicComponents/DynamicSprite.hpp" #include - #include "Engine.hpp" Physics2D::~Physics2D() { + // Remove this body from the physics manager list + Engine::GetPhysicsManager().RemoveBody2D(this); + #ifdef _DEBUG - for (auto& v : points) - { - delete v.sprite; - } - points.clear(); + // Clean up debug point sprites + for (auto& v : points) + { + delete v.sprite; + } + points.clear(); #endif } void Physics2D::Init() { + // Register the component to the engine's physics manager + Engine::GetPhysicsManager().AddBody2D(this); } -void Physics2D::Update(float dt) +void Physics2D::SetMomentOfInertia(float i) { - if (isGravityOn) - { - Gravity(dt); - } - - acceleration = force / mass; - velocity += acceleration * dt; - - force = { 0.f, 0.f }; - - - if (friction > 0.f) - { - if (isGravityOn) - { - velocity.x *= (1.f - friction * dt); - } - else - { - velocity *= (1.f - friction * dt); - } - } - - if (std::abs(velocity.y) > velocityMax.y) - { - velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); - } - if (std::abs(velocity.x) > velocityMax.x) - { - velocity.x = velocityMax.x * ((velocity.x < 0.f) ? -1.f : 1.f); - } - - if (std::abs(velocity.x) < velocityMin.x) - { - velocity.x = 0.f; - } - if (std::abs(velocity.y) < velocityMin.y) - { - velocity.y = 0.f; - } - - IComponent::GetOwner()->SetXPosition(IComponent::GetOwner()->GetPosition().x + velocity.x * dt); - IComponent::GetOwner()->SetYPosition(IComponent::GetOwner()->GetPosition().y + velocity.y * dt); - -#ifdef _DEBUG - /*std::vector rotatedPoints1; - int i = 0; - for (auto& v : points) - { - if (i == 0) - { - i++; - } - else - { - rotatedPoints1.push_back(RotatePoint(Component::GetOwner()->GetPosition(), v.pos, DegreesToRadians(Component::GetOwner()->GetRotate()))); - } - } - for (auto& v : rotatedPoints1) - { - glm::vec2 A = rotatedPoints1[i - 1]; - glm::vec2 B = { 0.f,0.f }; - if (points.size() > i + 1) - { - B = rotatedPoints1[i]; - } - else - { - B = rotatedPoints1[0]; - } - glm::vec2 midPoint = { (A.x + B.x) / 2.f, (A.y + B.y) / 2.f }; - float angle = std::atan2(B.y - A.y, B.x - A.x); - float distance = Distance(A, B); - - points[i].sprite->UpdateModel({ midPoint.x, midPoint.y , 0.f }, { distance, 1.f ,0.f }, -angle * 180 / 3.14); - points[i].sprite->UpdateProjection(); - points[i].sprite->UpdateView(); - i++; - } - glm::vec2 centerP = FindSATCenter(rotatedPoints1); - points[0].sprite->UpdateModel({ centerP.x, centerP.y , 0.f }, { 2.f,2.f,0.f }, 0.f); - points[0].sprite->UpdateProjection(); - points[0].sprite->UpdateView();*/ -#endif // _DEBUG - + momentOfInertia = i; + // Pre-calculate inverse inertia to avoid division during simulation + if (i > 0.0f) + { + inverseInertia = 1.0f / i; + } + else + { + // Infinite inertia represents an object that cannot rotate + inverseInertia = 0.0f; + } } -void Physics2D::UpdateForParticle(float dt, glm::vec3& pos) +void Physics2D::Update(float dt) { - if (isGravityOn) - { - Gravity(dt); - } - - acceleration = force / mass; - velocity += acceleration * dt; - - force = { 0.f, 0.f }; - - if (friction > 0.f) - { - if (isGravityOn) - { - velocity.x *= (1.f - friction * dt); - } - else - { - velocity *= (1.f - friction * dt); - } - } - - if (std::abs(velocity.y) > velocityMax.y) - { - velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); - } - if (std::abs(velocity.x) > velocityMax.x) - { - velocity.x = velocityMax.x * ((velocity.x < 0.f) ? -1.f : 1.f); - } - - if (std::abs(velocity.x) < velocityMin.x) - { - velocity.x = 0.f; - } - if (std::abs(velocity.y) < velocityMin.y) - { - velocity.y = 0.f; - } - - pos.x += velocity.x * dt; - pos.y += velocity.y * dt; + if (isGravityOn) + { + Gravity(dt); + } + + // Integrate force into velocity: a = F/m, v = v + a*dt + acceleration = force / mass; + velocity += acceleration * dt; + + // Reset accumulated force after integration + force = { 0.f, 0.f }; + + if (friction > 0.f) + { + if (isGravityOn) + { + // Apply horizontal friction only when gravity is pulling the object down + velocity.x *= (1.f - friction * dt); + } + else + { + // Apply full linear damping + velocity *= (1.f - friction * dt); + } + } + + // Clamp velocity to maximum allowed speed + if (std::abs(velocity.y) > velocityMax.y) + { + velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); + } + if (std::abs(velocity.x) > velocityMax.x) + { + velocity.x = velocityMax.x * ((velocity.x < 0.f) ? -1.f : 1.f); + } + + // Clamp velocity to zero if below minimum threshold + if (std::abs(velocity.x) < velocityMin.x) + { + velocity.x = 0.f; + } + if (std::abs(velocity.y) < velocityMin.y) + { + velocity.y = 0.f; + } + + if (enableRotationalPhysics && inverseInertia > 0.0f) + { + // Calculate angular acceleration: alpha = torque / I + glm::vec2 angularAcceleration = torque * inverseInertia; + angularVelocity += angularAcceleration * dt; + + if (friction > 0.f) + { + // Apply angular damping + angularVelocity *= (1.f - friction * dt); + } + torque = { 0.f, 0.f }; + + // Update the owner's rotation based on angular velocity + float currentRot = GetOwner()->GetRotate(); + GetOwner()->SetRotate(currentRot + angularVelocity.x * dt); + } } void Physics2D::Gravity(float dt) { - if(isGravityOn == true) - { - velocity.y -= gravity * dt; - if (std::abs(velocity.y) > velocityMax.y) - { - velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); - } - } + if (isGravityOn == true) + { + // Add gravitational acceleration to vertical velocity + velocity.y -= gravity * dt; + if (std::abs(velocity.y) > velocityMax.y) + { + velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); + } + } } bool Physics2D::CheckCollision(Object* obj) { - switch (collideType) - { - case CollideType::POLYGON: - if (obj->GetComponent()->GetCollideType() == CollideType::POLYGON) - { - return CollisionPP(GetOwner(), obj); - } - else if (obj->GetComponent()->GetCollideType() == CollideType::CIRCLE) - { - return CollisionPC(GetOwner(), obj); - } - break; - case CollideType::CIRCLE: - if (obj->GetComponent()->GetCollideType() == CollideType::POLYGON) - { - return CollisionPC(obj, GetOwner()); - } - else if (obj->GetComponent()->GetCollideType() == CollideType::CIRCLE) - { - return CollisionCC(GetOwner(), obj); - } - break; - default: - return false; - break; - } - return false; + // Dispatch to the correct collision detection routine based on collider types + switch (collideType) + { + case CollideType::POLYGON: + { + if (obj->GetComponent()->GetCollideType() == CollideType::POLYGON) + { + return CollisionPP(GetOwner(), obj); + } + else if (obj->GetComponent()->GetCollideType() == CollideType::CIRCLE) + { + return CollisionPC(GetOwner(), obj); + } + break; + } + case CollideType::CIRCLE: + { + if (obj->GetComponent()->GetCollideType() == CollideType::POLYGON) + { + return CollisionPC(obj, GetOwner()); + } + else if (obj->GetComponent()->GetCollideType() == CollideType::CIRCLE) + { + return CollisionCC(GetOwner(), obj); + } + break; + } + default: + { + return false; + } + } + return false; } -bool Physics2D::CollisionPP(Object* obj, Object* obj2) +bool Physics2D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) { - 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 }; - - float min1 = INFINITY; - float min2 = INFINITY; - float max1 = -INFINITY; - float max2 = -INFINITY; - - // 첫 번째 다각형 회전 - for (const glm::vec2 point : collidePolygon) - { - rotatedPoints1.push_back(RotatePoint(obj->GetPosition(), point, DegreesToRadians(obj->GetRotate()))); - } - - // 두 번째 다각형 회전 - for (const glm::vec2 point : obj2->GetComponent()->GetCollidePolygon()) - { - rotatedPoints2.push_back(RotatePoint(obj2->GetPosition(), point, DegreesToRadians(obj2->GetRotate()))); - } - - for (size_t i = 0; i < rotatedPoints1.size(); ++i) - { - float axisDepth = 0.f; - glm::vec2 edge = rotatedPoints1[(i + 1) % rotatedPoints1.size()] - rotatedPoints1[i]; - glm::vec2 axis = glm::vec2(-edge.y, edge.x); // 수직인 축 - axis = normalize(axis); - if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) - { - return false; // 충돌이 없음 - } - if (axisDepth < depth) - { - depth = axisDepth; - normal = axis; - } - } - - 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); - if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) - { - return false; // 충돌이 없음 - } - if (axisDepth < depth) - { - depth = axisDepth; - normal = axis; - } - } - - glm::vec2 direction = FindSATCenter(rotatedPoints2) - FindSATCenter(rotatedPoints1); - if (glm::dot(direction, normal) < 0.f) - { - normal = -normal; - } - - if (obj->GetComponent()->GetIsGhostCollision() == false && - obj2->GetComponent()->GetIsGhostCollision() == false) - { - if (obj->GetComponent()->GetBodyType() == BodyType::RIGID && - obj2->GetComponent()->GetBodyType() == BodyType::RIGID) - { - obj->SetXPosition(obj->GetPosition().x + (-normal * depth / 2.f).x); - obj->SetYPosition(obj->GetPosition().y + (-normal * depth / 2.f).y); - - obj2->SetXPosition(obj2->GetPosition().x + (normal * depth / 2.f).x); - obj2->SetYPosition(obj2->GetPosition().y + (normal * depth / 2.f).y); - } - else if (obj->GetComponent()->GetBodyType() == BodyType::RIGID && - obj2->GetComponent()->GetBodyType() == BodyType::BLOCK) - { - obj->SetXPosition(obj->GetPosition().x + (-normal * depth / 2.f).x); - obj->SetYPosition(obj->GetPosition().y + (-normal * depth / 2.f).y); - } - else if (obj->GetComponent()->GetBodyType() == BodyType::BLOCK && - obj2->GetComponent()->GetBodyType() == BodyType::RIGID) - { - obj2->SetXPosition(obj2->GetPosition().x + (normal * depth / 2.f).x); - obj2->SetYPosition(obj2->GetPosition().y + (normal * depth / 2.f).y); - } - CalculateLinearVelocity(*obj->GetComponent(), *obj2->GetComponent(), normal, &depth); - } - return true; // 모든 축에서 겹침이 없음 - } - return false; + // 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 }; + + float min1 = INFINITY; + float min2 = INFINITY; + float max1 = -INFINITY; + float max2 = -INFINITY; + + // Transform local vertices to world space based on object transform + for (const glm::vec2 point : collidePolygon) + { + rotatedPoints1.push_back(RotatePoint(glm::vec2(obj->GetPosition()), point, DegreesToRadians(obj->GetRotate()))); + } + + for (const glm::vec2 point : obj2->GetComponent()->GetCollidePolygon()) + { + rotatedPoints2.push_back(RotatePoint(glm::vec2(obj2->GetPosition()), point, DegreesToRadians(obj2->GetRotate()))); + } + + // Test separating axes for the first polygon's edges + for (size_t i = 0; i < rotatedPoints1.size(); ++i) + { + 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); + + if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) + { + return false; // Gap found, no collision + } + if (axisDepth < depth) + { + depth = axisDepth; + normal = ((max1 - min2) < (max2 - min1)) ? axis : -axis; + } + } + + // Test separating axes for the second polygon's edges + 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); + + if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) + { + return false; + } + if (axisDepth < depth) + { + depth = axisDepth; + normal = ((max1 - min2) < (max2 - min1)) ? axis : -axis; + } + } + + // Penetration resolution and collision response + if (mode == CollisionMode::COLLIDE && + obj->GetComponent()->GetIsGhostCollision() == false && + obj2->GetComponent()->GetIsGhostCollision() == false) + { + // Ensure the normal points from obj to obj2 for consistent collision response + glm::vec2 objCenter = glm::vec2(obj->GetPosition()); + 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) + { + normal = -normal; + } + + // Apply Baumgarte stabilization technique (Slop) + const float slop = 0.01f; + const float correctionPercent = 0.2f; + float penetrationAmt = std::max(depth - slop, 0.0f); + + // Calculate the actual amount to push + glm::vec2 moveVector = normal * penetrationAmt * correctionPercent; + + // Position correction (applying moveVector) + if (obj->GetComponent()->GetBodyType() == BodyType::RIGID && + obj2->GetComponent()->GetBodyType() == BodyType::RIGID) + { + obj->SetXPosition(obj->GetPosition().x - moveVector.x * 0.5f); + obj->SetYPosition(obj->GetPosition().y - moveVector.y * 0.5f); + obj2->SetXPosition(obj2->GetPosition().x + moveVector.x * 0.5f); + obj2->SetYPosition(obj2->GetPosition().y + moveVector.y * 0.5f); + } + else if (obj->GetComponent()->GetBodyType() == BodyType::RIGID) + { + obj->SetXPosition(obj->GetPosition().x - moveVector.x); + obj->SetYPosition(obj->GetPosition().y - moveVector.y); + } + else if (obj2->GetComponent()->GetBodyType() == BodyType::RIGID) + { + obj2->SetXPosition(obj2->GetPosition().x + moveVector.x); + obj2->SetYPosition(obj2->GetPosition().y + moveVector.y); + } + + // Find contact points to apply rotational impulses + float maxDot1 = -FLT_MAX; + for (auto& p : rotatedPoints1) + { + maxDot1 = std::max(maxDot1, glm::dot(p, normal)); + } + + glm::vec2 deepest1(0.0f); + int count1 = 0; + for (auto& p : rotatedPoints1) + { + if (glm::dot(p, normal) > maxDot1 - 0.01f) + { + deepest1 += p; + count1++; + } + } + deepest1 /= static_cast(count1); + + float maxDot2 = -FLT_MAX; + for (auto& p : rotatedPoints2) + { + maxDot2 = std::max(maxDot2, glm::dot(p, -normal)); + } + + glm::vec2 deepest2(0.0f); + int count2 = 0; + for (auto& p : rotatedPoints2) + { + if (glm::dot(p, -normal) > maxDot2 - 0.01f) + { + deepest2 += p; + count2++; + } + } + deepest2 /= static_cast(count2); + + // Determine the final contact point + glm::vec2 contactPoint; + if (count1 < count2) + { + contactPoint = deepest1; + } + else if (count2 < count1) + { + contactPoint = deepest2; + } + else + { + contactPoint = (deepest1 + deepest2) * 0.5f; + } + + // Calculate and apply linear/angular impulses + CalculateLinearVelocity(*obj->GetComponent(), *obj2->GetComponent(), normal, &depth, contactPoint); + } + return true; + } + return false; } -bool Physics2D::CollisionCC(Object* obj, Object* obj2) +bool Physics2D::CollisionCC(Object* obj, Object* obj2, CollisionMode mode) { - // 두 원의 중심 사이의 거리 계산 - float distanceX = obj->GetPosition().x - obj2->GetPosition().x; - float distanceY = obj->GetPosition().y - obj2->GetPosition().y; - float distance = std::sqrt(distanceX * distanceX + distanceY * distanceY); - - float depth = INFINITY; - glm::vec2 normal = { 0.f, 0.f }; - - // 두 원의 반지름의 합과 거리 비교 - if (distance <= obj->GetComponent()->GetCircleCollideRadius() + obj2->GetComponent()->GetCircleCollideRadius()) - { - // 두 원의 중심 사이의 단위 벡터 계산 - float unitX = distanceX / distance; - float unitY = distanceY / distance; - - // 겹친 양 계산 - float overlap = (obj->GetComponent()->GetCircleCollideRadius() + obj2->GetComponent()->GetCircleCollideRadius() - distance) / 2.0f; - - normal = normalize(obj2->GetPosition() - obj->GetPosition()); - depth = overlap - distance; - - // 각 원의 위치 조정 - if (obj->GetComponent()->GetIsGhostCollision() == false && - obj2->GetComponent()->GetIsGhostCollision() == false) - { - if (obj->GetComponent()->GetBodyType() == BodyType::RIGID && - obj2->GetComponent()->GetBodyType() == BodyType::RIGID) - { - obj->SetXPosition(obj->GetPosition().x + (overlap * unitX)); - obj->SetYPosition(obj->GetPosition().y + (overlap * unitY)); - - obj2->SetXPosition(obj2->GetPosition().x - (overlap * unitX)); - obj2->SetYPosition(obj2->GetPosition().y - (overlap * unitY)); - } - else if (obj->GetComponent()->GetBodyType() == BodyType::RIGID && - obj2->GetComponent()->GetBodyType() == BodyType::BLOCK) - { - obj->SetXPosition(obj->GetPosition().x + (overlap * unitX)); - obj->SetYPosition(obj->GetPosition().y + (overlap * unitY)); - } - else if (obj->GetComponent()->GetBodyType() == BodyType::BLOCK && - obj2->GetComponent()->GetBodyType() == BodyType::RIGID) - { - obj2->SetXPosition(obj2->GetPosition().x - (overlap * unitX)); - obj2->SetYPosition(obj2->GetPosition().y - (overlap * unitY)); - } - CalculateLinearVelocity(*obj->GetComponent(), *obj2->GetComponent(), -normal, &depth); - } - return true; // 충돌 발생 - } - return false; // 충돌 없음 + // Circle vs Circle distance-based collision check + float distanceX = obj->GetPosition().x - obj2->GetPosition().x; + float distanceY = obj->GetPosition().y - obj2->GetPosition().y; + float distance = std::sqrt(distanceX * distanceX + distanceY * distanceY); + + float depth = INFINITY; + glm::vec2 normal = { 0.f, 0.f }; + + float radiusSum = obj->GetComponent()->GetCircleCollideRadius() + obj2->GetComponent()->GetCircleCollideRadius(); + + if (distance <= radiusSum) + { + float unitX = distanceX / distance; + float unitY = distanceY / distance; + + // Calculate overlap amount + float overlap = (radiusSum - distance) / 2.0f; + normal = normalize(glm::vec2(obj2->GetPosition()) - glm::vec2(obj->GetPosition())); + depth = overlap - distance; + + if (mode == CollisionMode::COLLIDE && + obj->GetComponent()->GetIsGhostCollision() == false && + obj2->GetComponent()->GetIsGhostCollision() == false) + { + // Position correction + if (obj->GetComponent()->GetBodyType() == BodyType::RIGID && + obj2->GetComponent()->GetBodyType() == BodyType::RIGID) + { + obj->SetXPosition(obj->GetPosition().x + (overlap * unitX)); + obj->SetYPosition(obj->GetPosition().y + (overlap * unitY)); + obj2->SetXPosition(obj2->GetPosition().x - (overlap * unitX)); + obj2->SetYPosition(obj2->GetPosition().y - (overlap * unitY)); + } + else if (obj->GetComponent()->GetBodyType() == BodyType::RIGID) + { + obj->SetXPosition(obj->GetPosition().x + (overlap * 2.0f * unitX)); + obj->SetYPosition(obj->GetPosition().y + (overlap * 2.0f * unitY)); + } + else if (obj2->GetComponent()->GetBodyType() == BodyType::RIGID) + { + obj2->SetXPosition(obj2->GetPosition().x - (overlap * 2.0f * unitX)); + obj2->SetYPosition(obj2->GetPosition().y - (overlap * 2.0f * unitY)); + } + + // Resolve impulse at the point of contact + glm::vec2 contactPoint = glm::vec2(obj->GetPosition()) + normal * obj->GetComponent()->GetCircleCollideRadius(); + CalculateLinearVelocity(*obj->GetComponent(), *obj2->GetComponent(), normal, &depth, contactPoint); + } + return true; + } + return false; } -bool Physics2D::CollisionPC(Object* poly, Object* cir) +bool Physics2D::CollisionPC(Object* poly, Object* cir, CollisionMode mode) { - glm::vec2 circleCenter = cir->GetPosition(); - float circleRadius = cir->GetComponent()->GetCircleCollideRadius(); - std::vector rotatedPoints; - - glm::vec2 normal = { 0.f,0.f }; - float depth = INFINITY; - - float min1 = INFINITY; - float min2 = INFINITY; - float max1 = -INFINITY; - float max2 = -INFINITY; - - - for (const glm::vec2 point : collidePolygon) - { - rotatedPoints.push_back(RotatePoint(poly->GetPosition(), point, DegreesToRadians(poly->GetRotate()))); - } - - for (size_t i = 0; i < rotatedPoints.size(); ++i) - { - float axisDepth = 0.f; - glm::vec2 edge = rotatedPoints[(i + 1) % rotatedPoints.size()] - rotatedPoints[i]; - glm::vec2 axis = glm::vec2(-edge.y, edge.x); // 수직인 축 - axis = normalize(axis); - if (IsSeparatingAxis(axis, rotatedPoints, circleCenter, circleRadius, &axisDepth, &min1, &max1, &min2, &max2)) - { - return false; // 충돌이 없음 - } - if (axisDepth < depth) - { - depth = axisDepth; - normal = axis; - } - } - - { - glm::vec2 closestPoint = FindClosestPointOnSegment(circleCenter, rotatedPoints); - float axisDepth = 0.f; - glm::vec2 axis = closestPoint - circleCenter; - axis = normalize(axis); - if (IsSeparatingAxis(axis, rotatedPoints, circleCenter, circleRadius, &axisDepth, &min1, &max1, &min2, &max2)) - { - return false; // 충돌이 없음 - } - if (axisDepth < depth) - { - depth = axisDepth; - normal = axis; - } - } - - glm::vec2 direction = FindSATCenter(rotatedPoints) - circleCenter; - if (glm::dot(direction, normal) < 0.f) - { - normal = -normal; - } - - - if (poly->GetComponent()->GetIsGhostCollision() == false && - cir->GetComponent()->GetIsGhostCollision() == false) - { - if (poly->GetComponent()->GetBodyType() == BodyType::RIGID && - cir->GetComponent()->GetBodyType() == BodyType::RIGID) - { - poly->SetXPosition(poly->GetPosition().x + (normal * depth / 2.f).x); - poly->SetYPosition(poly->GetPosition().y + (normal * depth / 2.f).y); - - cir->SetXPosition(cir->GetPosition().x + (-normal * depth / 2.f).x); - cir->SetYPosition(cir->GetPosition().y + (-normal * depth / 2.f).y); - } - else if (poly->GetComponent()->GetBodyType() == BodyType::RIGID && - cir->GetComponent()->GetBodyType() == BodyType::BLOCK) - { - poly->SetXPosition(poly->GetPosition().x + (normal * depth / 2.f).x); - poly->SetYPosition(poly->GetPosition().y + (normal * depth / 2.f).y); - } - else if (poly->GetComponent()->GetBodyType() == BodyType::BLOCK && - cir->GetComponent()->GetBodyType() == BodyType::RIGID) - { - cir->SetXPosition(cir->GetPosition().x + (-normal * depth / 2.f).x); - cir->SetYPosition(cir->GetPosition().y + (-normal * depth / 2.f).y); - } - CalculateLinearVelocity(*poly->GetComponent(), *cir->GetComponent(), normal, &depth); - } - return true; + // Polygon vs Circle collision check + glm::vec2 circleCenter = cir->GetPosition(); + float circleRadius = cir->GetComponent()->GetCircleCollideRadius(); + std::vector rotatedPoints; + + glm::vec2 normal = { 0.f,0.f }; + float depth = INFINITY; + + float min1 = INFINITY; + float min2 = INFINITY; + float max1 = -INFINITY; + float max2 = -INFINITY; + + // Convert polygon vertices to world space + for (const glm::vec2 point : collidePolygon) + { + rotatedPoints.push_back(RotatePoint(poly->GetPosition(), point, DegreesToRadians(poly->GetRotate()))); + } + + // Check SAT using polygon edge normals + for (size_t i = 0; i < rotatedPoints.size(); ++i) + { + float axisDepth = 0.f; + glm::vec2 edge = rotatedPoints[(i + 1) % rotatedPoints.size()] - rotatedPoints[i]; + glm::vec2 axis = glm::vec2(-edge.y, edge.x); + axis = normalize(axis); + + if (IsSeparatingAxis(axis, rotatedPoints, circleCenter, circleRadius, &axisDepth, &min1, &max1, &min2, &max2)) + { + return false; + } + if (axisDepth < depth) + { + depth = axisDepth; + normal = axis; + } + } + + // Check additional axis from the closest vertex to circle center + { + glm::vec2 closestPoint = FindClosestPointOnSegment(circleCenter, rotatedPoints); + float axisDepth = 0.f; + glm::vec2 axis = closestPoint - circleCenter; + axis = normalize(axis); + + if (IsSeparatingAxis(axis, rotatedPoints, circleCenter, circleRadius, &axisDepth, &min1, &max1, &min2, &max2)) + { + return false; + } + if (axisDepth < depth) + { + depth = axisDepth; + normal = axis; + } + } + + // 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) + { + normal = -normal; + } + + if (mode == CollisionMode::COLLIDE && + poly->GetComponent()->GetIsGhostCollision() == false && + cir->GetComponent()->GetIsGhostCollision() == false) + { + // Resolve overlap + if (poly->GetComponent()->GetBodyType() == BodyType::RIGID && + cir->GetComponent()->GetBodyType() == BodyType::RIGID) + { + poly->SetXPosition(poly->GetPosition().x - (normal * depth / 2.f).x); + poly->SetYPosition(poly->GetPosition().y - (normal * depth / 2.f).y); + cir->SetXPosition(cir->GetPosition().x + (normal * depth / 2.f).x); + cir->SetYPosition(cir->GetPosition().y + (normal * depth / 2.f).y); + } + else if (poly->GetComponent()->GetBodyType() == BodyType::RIGID) + { + poly->SetXPosition(poly->GetPosition().x - (normal * depth).x); + poly->SetYPosition(poly->GetPosition().y - (normal * depth).y); + } + else if (cir->GetComponent()->GetBodyType() == BodyType::RIGID) + { + cir->SetXPosition(cir->GetPosition().x + (normal * depth).x); + cir->SetYPosition(cir->GetPosition().y + (normal * depth).y); + } + + // Apply impulse calculation + glm::vec2 contactPoint = FindClosestPointOnSegment(circleCenter, rotatedPoints); + CalculateLinearVelocity(*poly->GetComponent(), *cir->GetComponent(), normal, &depth, contactPoint); + } + return true; } bool Physics2D::CollisionPPWithoutPhysics(Object* obj, Object* obj2) { - 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 }; - - float min1 = INFINITY; - float min2 = INFINITY; - float max1 = -INFINITY; - float max2 = -INFINITY; - - // 첫 번째 다각형 회전 - for (const glm::vec2 point : obj->GetComponent()->GetCollidePolygon()) - { - rotatedPoints1.push_back(RotatePoint(obj->GetPosition(), point, DegreesToRadians(obj->GetRotate()))); - } - - // 두 번째 다각형 회전 - for (const glm::vec2 point : obj2->GetComponent()->GetCollidePolygon()) - { - rotatedPoints2.push_back(RotatePoint(obj2->GetPosition(), point, DegreesToRadians(obj2->GetRotate()))); - } - - for (size_t i = 0; i < rotatedPoints1.size(); ++i) - { - float axisDepth = 0.f; - glm::vec2 edge = rotatedPoints1[(i + 1) % rotatedPoints1.size()] - rotatedPoints1[i]; - glm::vec2 axis = glm::vec2(-edge.y, edge.x); // 수직인 축 - axis = normalize(axis); - if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) - { - return false; // 충돌이 없음 - } - if (axisDepth < depth) - { - depth = axisDepth; - normal = axis; - } - } - - 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); - if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) - { - return false; // 충돌이 없음 - } - if (axisDepth < depth) - { - depth = axisDepth; - normal = axis; - } - } - - glm::vec2 direction = FindSATCenter(rotatedPoints2) - FindSATCenter(rotatedPoints1); - if (glm::dot(direction, normal) < 0.f) - { - normal = -normal; - } - - return true; // 모든 축에서 겹침이 없음 - } - return false; + // Simplified SAT check for collision detection only (no response) + 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 }; + + float min1 = INFINITY, min2 = INFINITY; + float max1 = -INFINITY, max2 = -INFINITY; + + for (const glm::vec2 point : obj->GetComponent()->GetCollidePolygon()) + { + rotatedPoints1.push_back(RotatePoint(glm::vec2(obj->GetPosition()), point, DegreesToRadians(obj->GetRotate()))); + } + + for (const glm::vec2 point : obj2->GetComponent()->GetCollidePolygon()) + { + rotatedPoints2.push_back(RotatePoint(glm::vec2(obj2->GetPosition()), point, DegreesToRadians(obj2->GetRotate()))); + } + + for (size_t i = 0; i < rotatedPoints1.size(); ++i) + { + float axisDepth = 0.f; + glm::vec2 edge = rotatedPoints1[(i + 1) % rotatedPoints1.size()] - rotatedPoints1[i]; + glm::vec2 axis = normalize(glm::vec2(-edge.y, edge.x)); + if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) + { + // Strict separation check for grounding (using tiny epsilon) + if (axisDepth < -0.1f) + { + return false; + } + } + if (axisDepth < depth) + { + depth = axisDepth; + normal = axis; + } + } + + 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 = normalize(glm::vec2(-edge.y, edge.x)); + if (IsSeparatingAxis(axis, rotatedPoints1, rotatedPoints2, &axisDepth, &min1, &max1, &min2, &max2)) + { + if (axisDepth < -0.1f) + { + return false; + } + } + if (axisDepth < depth) + { + depth = axisDepth; + normal = axis; + } + } + + return true; + } + return false; } void Physics2D::AddCollideCircle(float r) { #ifdef _DEBUG - float increment = 3.14f * 2.f / 10.f; - for (int i = 0; i < 10; i++) - { - AddPoint({ r * static_cast(cos(i * increment - (3.14f / 2.f))), - r * static_cast(sin(i * increment - (3.14f / 2.f))) }); - - } + // Generate vertex points for debug visualization of the circle + float increment = 3.14f * 2.f / 10.f; + for (int i = 0; i < 10; i++) + { + AddPoint({ r * static_cast(cos(i * increment - (3.14f / 2.f))), + r * static_cast(sin(i * increment - (3.14f / 2.f))) }); + } #endif - collideType = CollideType::CIRCLE; - collidePolygon.clear(); - circle.radius = r; + collideType = CollideType::CIRCLE; + collidePolygon.clear(); + circle.radius = r; + + float calcInertia = (mass * r * r) / 2.0f; + SetMomentOfInertia(calcInertia); } void Physics2D::AddCollidePolygon(glm::vec2 position) { #ifdef _DEBUG - AddPoint(position); + AddPoint(position); #endif - collideType = CollideType::POLYGON; - collidePolygon.push_back(position); + collideType = CollideType::POLYGON; + collidePolygon.push_back(position); } void Physics2D::AddCollidePolygonAABB(glm::vec2 min, glm::vec2 max) { #ifdef _DEBUG - AddPoint({ min.x, min.y }); - AddPoint({ min.x, max.y }); - AddPoint({ max.x, max.y }); - AddPoint({ max.x, min.y }); + AddPoint({ min.x, min.y }); + AddPoint({ min.x, max.y }); + AddPoint({ max.x, max.y }); + AddPoint({ max.x, min.y }); #endif - collideType = CollideType::POLYGON; - collidePolygon.clear(); - collidePolygon = { {min.x, min.y}, {min.x, max.y}, {max.x, max.y}, {max.x, min.y} }; + collideType = CollideType::POLYGON; + collidePolygon.clear(); + collidePolygon = { {min.x, min.y}, {min.x, max.y}, {max.x, max.y}, {max.x, min.y} }; + + float width = (min.x + max.x) * 2.0f; + float height = (min.y + max.y) * 2.0f; + float calcInertia = (mass * (width * width + height * height)) / 12.0f; + SetMomentOfInertia(calcInertia); } void Physics2D::AddCollidePolygonAABB(glm::vec2 size) { #ifdef _DEBUG - AddPoint({ -size.x, -size.y }); - AddPoint({ -size.x, size.y }); - AddPoint({ size.x, size.y }); - AddPoint({ size.x, -size.y }); + AddPoint({ -size.x, -size.y }); + AddPoint({ -size.x, size.y }); + AddPoint({ size.x, size.y }); + AddPoint({ size.x, -size.y }); #endif - collideType = CollideType::POLYGON; - collidePolygon.clear(); - collidePolygon = { {-size.x, -size.y}, {-size.x, size.y}, {size.x, size.y}, {size.x, -size.y} }; + collideType = CollideType::POLYGON; + collidePolygon.clear(); + collidePolygon = { {-size.x / 2.f, -size.y / 2.f}, {-size.x / 2.f, size.y / 2.f}, + {size.x / 2.f, size.y / 2.f}, {size.x / 2.f, -size.y / 2.f} }; + + float width = size.x * 2.0f; + float height = size.y * 2.0f; + float calcInertia = (mass * (width * width + height * height)) / 12.0f; + SetMomentOfInertia(calcInertia); } -glm::vec2 Physics2D::FindSATCenter(const std::vector& points_) +glm::vec2 Physics2D::FindSATCenter(const std::vector& points) { - float totalArea = 0.0f; - float centerX = 0.0f; - float centerY = 0.0f; - - for (size_t i = 0; i < points_.size(); ++i) - { - glm::vec2 currentPoint = points_[i]; - glm::vec2 nextPoint = points_[(i + 1) % points_.size()]; - - float crossProduct = (currentPoint.x * nextPoint.y) - (nextPoint.x * currentPoint.y); - - totalArea += crossProduct; - centerX += (currentPoint.x + nextPoint.x) * crossProduct; - centerY += (currentPoint.y + nextPoint.y) * crossProduct; - } - - centerX *= 1.f / (3.f * totalArea); - centerY *= 1.f / (3.f * totalArea); - - return glm::vec2(centerX, centerY); + // Find the centroid of the polygon for consistent direction calculations + float totalArea = 0.0f; + float centerX = 0.0f; + float centerY = 0.0f; + + for (size_t i = 0; i < points.size(); ++i) + { + glm::vec2 currentPoint = points[i]; + glm::vec2 nextPoint = points[(i + 1) % points.size()]; + + float crossProduct = (currentPoint.x * nextPoint.y) - (nextPoint.x * currentPoint.y); + totalArea += crossProduct; + centerX += (currentPoint.x + nextPoint.x) * crossProduct; + centerY += (currentPoint.y + nextPoint.y) * crossProduct; + } + + centerX *= 1.f / (3.f * totalArea); + centerY *= 1.f / (3.f * totalArea); + + return glm::vec2(centerX, centerY); } glm::vec2 Physics2D::FindClosestPointOnSegment(const glm::vec2& circleCenter, std::vector& vertices) { - glm::vec2 returnValue = { 0.f,0.f }; - float minDistance = INFINITY; - - for (int i = 0; i < vertices.size(); i++) - { - glm::vec2 v = vertices[i]; - float distance = Distance(v, circleCenter); - - if (distance < minDistance) - { - minDistance = distance; - returnValue = v; - } - } - return returnValue; + // Find the point on the polygon's edges closest to the circle center + glm::vec2 resultPoint = { 0.f, 0.f }; + float minDistanceSquared = FLT_MAX; + + for (size_t i = 0; i < vertices.size(); i++) + { + glm::vec2 va = vertices[i]; + glm::vec2 vb = vertices[(i + 1) % vertices.size()]; + glm::vec2 edge = vb - va; + glm::vec2 toCircle = circleCenter - va; + + float edgeLengthSq = LengthSquared(edge); + float t = 0.0f; + if (edgeLengthSq > 0.0f) + { + // Project the circle center onto the line segment to find parameter t + t = glm::dot(toCircle, edge) / edgeLengthSq; + t = std::max(0.0f, std::min(1.0f, t)); + } + + glm::vec2 closestPoint = va + edge * t; + float distSq = DistanceSquared(closestPoint, circleCenter); + + if (distSq < minDistanceSquared) + { + minDistanceSquared = distSq; + resultPoint = closestPoint; + } + } + return resultPoint; } float Physics2D::calculatePolygonRadius(const std::vector& vertices) -{ // 다각형의 중심 계산 - float centerX = 0.0f; - float centerY = 0.0f; - for (const glm::vec2& vertex : vertices) { - centerX += vertex.x; - centerY += vertex.y; - } - centerX /= vertices.size(); - centerY /= vertices.size(); - - // 가장 먼 거리 찾기 - float maxDistance = 0.0f; - for (const glm::vec2& vertex : vertices) { - float distance = std::sqrt((vertex.x - centerX) * (vertex.x - centerX) + (vertex.y - centerY) * (vertex.y - centerY)); - if (distance > maxDistance) { - maxDistance = distance; - } - } - return maxDistance; +{ + // Calculate the distance to the vertex furthest from the center + float centerX = 0.0f; + float centerY = 0.0f; + for (const glm::vec2& vertex : vertices) + { + centerX += vertex.x; + centerY += vertex.y; + } + centerX /= static_cast(vertices.size()); + centerY /= static_cast(vertices.size()); + + float maxDistance = 0.0f; + for (const glm::vec2& vertex : vertices) + { + float distance = std::sqrt((vertex.x - centerX) * (vertex.x - centerX) + (vertex.y - centerY) * (vertex.y - centerY)); + if (distance > maxDistance) + { + maxDistance = distance; + } + } + return maxDistance; } float Physics2D::DegreesToRadians(float degrees) { - return degrees * 3.14f / 180.f; + return degrees * 3.14f / 180.f; } glm::vec2 Physics2D::RotatePoint(const glm::vec2 point, const glm::vec2 size, float angle) { - float x = point.x + (size.x * cos(angle) - size.y * sin(angle)); - float y = point.y + (size.x * sin(angle) + size.y * cos(angle)); - return glm::vec2(x, y); + // Apply 2D rotation matrix around a point + float x = point.x + (size.x * cos(angle) - size.y * sin(angle)); + float y = point.y + (size.x * sin(angle) + size.y * cos(angle)); + 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) { - float min1 = INFINITY; - float min2 = INFINITY; - float max1 = -INFINITY; - float max2 = -INFINITY; - - for (const glm::vec2 point : points1) - { - float projection = (point.x * axis.x) + (point.y * axis.y); - min1 = std::min(min1, projection); - max1 = std::max(max1, projection); - } - - for (const glm::vec2 point : points2) - { - float projection = (point.x * axis.x) + (point.y * axis.y); - min2 = std::min(min2, projection); - max2 = std::max(max2, projection); - } - - *axisDepth = std::min(max2 - min1, max1 - min2); - *min1_ = min1; - *max1_ = max1; - *min2_ = min2; - *max2_ = max2; - return !(max1 >= min2 && max2 >= min1); + // Project all points of both polygons onto the given axis + float minPoint1 = INFINITY; + float minPoint2 = INFINITY; + float maxPoint1 = -INFINITY; + float maxPoint2 = -INFINITY; + + for (const glm::vec2 point : points1) + { + float projection = glm::dot(point, axis); + minPoint1 = std::min(minPoint1, projection); + maxPoint1 = std::max(maxPoint1, projection); + } + + for (const glm::vec2 point : points2) + { + float projection = glm::dot(point, axis); + minPoint2 = std::min(minPoint2, projection); + maxPoint2 = std::max(maxPoint2, projection); + } + + // Check if the two projected ranges overlap + *axisDepth = std::min(maxPoint2 - minPoint1, maxPoint1 - minPoint2); + *min1 = minPoint1; + *max1 = maxPoint1; + *min2 = minPoint2; + *max2 = maxPoint2; + + // Return true if there is a gap (separation) + return !(maxPoint1 >= minPoint2 && maxPoint2 >= minPoint1); } -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 pointCir, const float radius, float* axisDepth, float* min1, float* max1, float* min2, float* max2) { - float min1 = INFINITY; - float min2 = INFINITY; - float max1 = -INFINITY; - float max2 = -INFINITY; - - for (const glm::vec2 point : pointsPoly) - { - float projection = (point.x * axis.x) + (point.y * axis.y); - min1 = std::min(min1, projection); - max1 = std::max(max1, projection); - } - - glm::vec2 direction = normalize(axis); - glm::vec2 directionAndRadius = direction * radius; - - glm::vec2 p1 = pointCir + directionAndRadius; - glm::vec2 p2 = pointCir - directionAndRadius; - - min2 = glm::dot(p1, axis); - max2 = glm::dot(p2, axis); - - if (min2 > max2) - { - // swap the min and max values. - float t = min2; - min2 = max2; - max2 = t; - } - - *axisDepth = std::min(max2 - min1, max1 - min2); - *min1_ = min1; - *max1_ = max1; - *min2_ = min2; - *max2_ = max2; - return !(max1 >= min2 && max2 >= min1); + // SAT projection for polygon vs circle + float minPoint1 = INFINITY; + float maxPoint1 = -INFINITY; + + for (const glm::vec2 point : pointsPoly) + { + float projection = glm::dot(point, axis); + minPoint1 = std::min(minPoint1, projection); + maxPoint1 = std::max(maxPoint1, 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); + + *axisDepth = std::min(maxPoint2 - minPoint1, maxPoint1 - minPoint2); + *min1 = minPoint1; + *max1 = maxPoint1; + *min2 = minPoint2; + *max2 = maxPoint2; + + return !(maxPoint1 >= minPoint2 && maxPoint2 >= minPoint1); } -void Physics2D::CalculateLinearVelocity(Physics2D& body, Physics2D& body2, glm::vec2 normal, float* /*axisDepth*/) +void Physics2D::CalculateLinearVelocity(Physics2D& body, Physics2D& body2, glm::vec2 normal, float* /*axisDepth*/, glm::vec2 contactPoint) { - glm::vec2 relativeVelocity = body2.GetVelocity() - body.GetVelocity(); - float res = std::min(body.GetRestitution(), body2.GetRestitution()); - float j = -(1.f - res) * glm::dot(relativeVelocity, normal); - j /= (1.f / body.mass) + (1.f / body2.mass); - - if (body.GetBodyType() == BodyType::RIGID) - { - body.SetVelocity(body.GetVelocity() - j / body.mass * normal); - } - if (body2.GetBodyType() == BodyType::RIGID) - { - body2.SetVelocity(body2.GetVelocity() + j / body2.mass * normal); - } + // Lever arms from centers of mass to contact point + glm::vec2 ra = contactPoint - glm::vec2(body.GetOwner()->GetPosition()); + glm::vec2 rb = contactPoint - glm::vec2(body2.GetOwner()->GetPosition()); + + // Contact velocity including rotational component + glm::vec2 vaContact = body.GetVelocity() + glm::vec2(-body.GetAngularVelocity().x * ra.y, body.GetAngularVelocity().x * ra.x); + glm::vec2 vbContact = body2.GetVelocity() + glm::vec2(-body2.GetAngularVelocity().x * rb.y, body2.GetAngularVelocity().x * rb.x); + + glm::vec2 relativeVelocity = vbContact - vaContact; + + // Do not resolve if objects are moving apart + float velAlongNormal = glm::dot(relativeVelocity, normal); + if (velAlongNormal > 0.0f) + { + return; + } + + // Determine impulse scalar magnitude + float res = std::min(body.GetRestitution(), body2.GetRestitution()); + + // 2D Cross product equivalent for rotational effect + float raCrossN = ra.x * normal.y - ra.y * normal.x; + float rbCrossN = rb.x * normal.y - rb.y * normal.x; + + float invMassSum = (body.GetBodyType() == BodyType::RIGID ? (1.f / body.mass) : 0.0f) + + (body2.GetBodyType() == BodyType::RIGID ? (1.f / body2.mass) : 0.0f); + + float denominator = invMassSum; + if (body.GetBodyType() == BodyType::RIGID && body.GetEnableRotationalPhysics()) + { + denominator += (raCrossN * raCrossN) * body.GetInverseInertia(); + } + if (body2.GetBodyType() == BodyType::RIGID && body2.GetEnableRotationalPhysics()) + { + denominator += (rbCrossN * rbCrossN) * body2.GetInverseInertia(); + } + + if (denominator <= 0.0f) + { + return; + } + + // Apply the impulse to both bodies + // Using historical formula: (1.0f - restitution) where -1.0f = perfect bounce + float j = -(1.f - res) * velAlongNormal / denominator; + glm::vec2 impulse = normal * j; + + if (body.GetBodyType() == BodyType::RIGID) + { + body.SetVelocity(body.GetVelocity() - impulse * (1.f / body.mass)); + if (body.GetEnableRotationalPhysics()) + { + body.SetAngularVelocity(glm::vec2(body.GetAngularVelocity().x - raCrossN * j * body.GetInverseInertia(), 0.0f)); + } + } + if (body2.GetBodyType() == BodyType::RIGID) + { + body2.SetVelocity(body2.GetVelocity() + impulse * (1.f / body2.mass)); + if (body2.GetEnableRotationalPhysics()) + { + body2.SetAngularVelocity(glm::vec2(body2.GetAngularVelocity().x + rbCrossN * j * body2.GetInverseInertia(), 0.0f)); + } + } } #ifdef _DEBUG void Physics2D::AddPoint(glm::vec2 /*pos*/) { - //if (points.empty() == true) - //{ - // Point temp; - // temp.pos = { 0.f,0.f }; - // temp.sprite = new Sprite(); - // temp.sprite->AddQuad({ 1.f,0.f,0.f,1.f }); - // points.push_back(std::move(temp)); - //} - //Point temp; - //temp.pos = pos; - //temp.sprite = new Sprite(); - //switch (bodyType) - //{ - //case BodyType::RIGID: - // temp.sprite->AddQuad({ 0.f,1.f,0.f,1.f }); - // break; - //case BodyType::BLOCK: - // temp.sprite->AddQuad({ 0.f,0.f,1.f,1.f }); - // break; - //default: - // temp.sprite->AddQuad({ 1.f,1.f,1.f,1.f }); - // break; - //} - //points.push_back(std::move(temp)); + //if (points.empty() == true) + //{ + // Point temp; + // temp.pos = { 0.f,0.f }; + // temp.sprite = new Sprite(); + // temp.sprite->AddQuad({ 1.f,0.f,0.f,1.f }); + // points.push_back(std::move(temp)); + //} + //Point temp; + //temp.pos = pos; + //temp.sprite = new Sprite(); + //switch (bodyType) + //{ + //case BodyType::RIGID: + // temp.sprite->AddQuad({ 0.f,1.f,0.f,1.f }); + // break; + //case BodyType::BLOCK: + // temp.sprite->AddQuad({ 0.f,0.f,1.f,1.f }); + // break; + //default: + // temp.sprite->AddQuad({ 1.f,1.f,1.f,1.f }); + // break; + //} + //points.push_back(std::move(temp)); } #endif diff --git a/Engine/source/BasicComponents/Physics3D.cpp b/Engine/source/BasicComponents/Physics3D.cpp index 788e9f69..0cfb02b5 100644 --- a/Engine/source/BasicComponents/Physics3D.cpp +++ b/Engine/source/BasicComponents/Physics3D.cpp @@ -10,1060 +10,1220 @@ #include #include #include +#include +#include Physics3D::~Physics3D() { + // Remove this body from the global physics manager upon destruction + Engine::GetPhysicsManager().RemoveBody3D(this); } void Physics3D::Init() { + // Register this body to the physics manager for updates + Engine::GetPhysicsManager().AddBody3D(this); } -void Physics3D::Update(float dt) +void Physics3D::Update(float /*dt*/) { - UpdatePhysics(dt); + // Integration is handled globally by PhysicsManager::StepPhysics3D() +} +void Physics3D::SetMomentOfInertia(float i) +{ + // Store moment of inertia and calculate its inverse for rotation math + momentOfInertia = i; + if (i > 0.0f) + { + inverseInertia = 1.0f / i; + } + else + { + // Zero inertia represents an unrotatable object + inverseInertia = 0.0f; + } } -void Physics3D::UpdatePhysics(float dt) +void Physics3D::Awake() { - if (isGravityOn) - { - Gravity(dt); - } - - acceleration = force / mass; - velocity += acceleration * dt; - - if (friction > 0.f) - { - if (isGravityOn) - { - velocity.x *= (1.f - friction * dt); - velocity.z *= (1.f - friction * dt); - } - else - { - velocity *= (1.f - friction * dt); - } - } - - force = { 0.f, 0.f, 0.f }; - - if (std::abs(velocity.x) > velocityMax.x) - { - velocity.x = velocityMax.x * ((velocity.x < 0.f) ? -1.f : 1.f); - } - if (std::abs(velocity.y) > velocityMax.y) - { - velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); - } - if (std::abs(velocity.z) > velocityMax.z) - { - velocity.z = velocityMax.z * ((velocity.z < 0.f) ? -1.f : 1.f); - } - - if (std::abs(velocity.x) < velocityMin.x) - { - velocity.x = 0.f; - } - if (std::abs(velocity.y) < velocityMin.y) - { - velocity.y = 0.f; - } - if (std::abs(velocity.z) < velocityMin.z) - { - velocity.z = 0.f; - } - - if (collisionMode == CollisionDetectionMode::CONTINUOUS) - { - CollisionResult collision = FindClosestCollision(dt); - if (collision.hasCollided && collision.timeOfImpact <= 1.0f) - { - const float skin_width = 0.005f; - float move_time = collision.timeOfImpact > skin_width ? collision.timeOfImpact - skin_width : 0.0f; - GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * dt * move_time); - - CalculateLinearVelocity(*this, *collision.otherObject->GetComponent(), collision.collisionNormal, nullptr); - - float remaining_time = dt - (dt * move_time); - if (remaining_time > 0.0f) - { - GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * remaining_time); - } - } - else - { - GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * dt); - } - } - else - { - GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * dt); - } + // Reset sleep state and timer to reactivate physics + isSleeping = false; + sleepTimer = 0.0f; } -void Physics3D::UpdateForParticle(float dt, glm::vec3& pos) +void Physics3D::SetEnableRotationalPhysics(bool v) { - if (isGravityOn) - { - Gravity(dt); - } - - acceleration = force / mass; - velocity += acceleration * dt; - - if (friction > 0.f) - { - if (isGravityOn) - { - velocity.x *= (1.f - friction * dt); - velocity.z *= (1.f - friction * dt); - } - else - { - velocity *= (1.f - friction * dt); - } - } - - force = { 0.f, 0.f, 0.f }; - - if (std::abs(velocity.x) > velocityMax.x) - { - velocity.x = velocityMax.x * ((velocity.x < 0.f) ? -1.f : 1.f); - } - if (std::abs(velocity.y) > velocityMax.y) - { - velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); - } - if (std::abs(velocity.z) > velocityMax.z) - { - velocity.z = velocityMax.z * ((velocity.z < 0.f) ? -1.f : 1.f); - } - - if (std::abs(velocity.x) < velocityMin.x) - { - velocity.x = 0.f; - } - if (std::abs(velocity.y) < velocityMin.y) - { - velocity.y = 0.f; - } - if (std::abs(velocity.z) < velocityMin.z) - { - velocity.z = 0.f; - } - - pos += velocity * dt; + enableRotationalPhysics = v; + if (v && GetOwner() != nullptr) + { + // Synchronize orientation from the visual object transform + orientation = glm::quat(-glm::radians(GetOwner()->GetRotate3D())); + angularVelocity = glm::vec3(0.0f); + sleepTimer = 0.0f; + } } -void Physics3D::Gravity(float dt) +void Physics3D::SetAcceleration(glm::vec3 v) +{ + // Set direct acceleration and wake the body + acceleration = v; + Awake(); +} + +void Physics3D::AddForce(glm::vec3 v) +{ + // Accumulate force for this frame + force += v; + Awake(); +} + +void Physics3D::AddForceX(float amount) +{ + force.x += amount; + Awake(); +} + +void Physics3D::AddForceY(float amount) +{ + force.y += amount; + Awake(); +} + +void Physics3D::AddForceZ(float amount) { - if (isGravityOn) - { - velocity.y -= gravity * dt /** 60.f*/; - if (std::abs(velocity.y) > velocityMax.y) - { - velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); - } - } + force.z += amount; + Awake(); } +void Physics3D::UpdatePhysics(float dt) +{ + if (isSleeping) + { + // Wake the object if any significant force or gravity is applied + if (glm::length2(force) > 0.0001f || glm::length2(torque) > 0.0001f || glm::length2(acceleration) > 0.0001f || isGravityOn) + { + Awake(); + } + else + { + // Keep forces zeroed out while sleeping to prevent accumulation + force = glm::vec3(0.0f); + torque = glm::vec3(0.0f); + return; + } + } + + if (isGravityOn) + { + Gravity(dt); + } + + // Apply Newton's second law: F = ma -> a = F/m + acceleration = force / mass; + velocity += acceleration * dt; + + if (friction > 0.f) + { + if (isGravityOn) + { + // Apply horizontal friction when gravity is active + velocity.x *= (1.f - friction * dt); + velocity.z *= (1.f - friction * dt); + } + else + { + // Apply full linear damping + velocity *= (1.f - friction * dt); + } + } + + // Reset accumulated force after integration + force = glm::vec3(0.0f); + + // Clamp linear velocity to the defined maximum limits + if (std::abs(velocity.x) > velocityMax.x) + { + velocity.x = velocityMax.x * ((velocity.x < 0.f) ? -1.f : 1.f); + } + if (std::abs(velocity.y) > velocityMax.y) + { + velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); + } + if (std::abs(velocity.z) > velocityMax.z) + { + velocity.z = velocityMax.z * ((velocity.z < 0.f) ? -1.f : 1.f); + } + + if (enableRotationalPhysics && inverseInertia > 0.0f) + { + // Calculate angular acceleration from torque + glm::vec3 angularAcceleration = torque * inverseInertia; + angularVelocity += angularAcceleration * dt; + + // Apply rotational damping + angularVelocity *= (1.f - angularDamping * dt); + + // Clamp extremely low angular velocities to zero + if (glm::length2(angularVelocity) < 1e-6f) + { + angularVelocity = glm::vec3(0.0f); + } + + torque = glm::vec3(0.0f); + + // Perform quaternion integration to update orientation + 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; + orientation.y += spin.y * dt; + orientation.z += spin.z * dt; + orientation = glm::normalize(orientation); + + // Map the orientation back to Euler angles for the visual object + glm::vec3 eulerArgs = glm::degrees(glm::eulerAngles(orientation)); + GetOwner()->SetRotate(-eulerArgs); + } + + // Thresholds for deciding if the object should go to sleep + const float linearSleepThreshold = 0.01f; + const float angularSleepThreshold = 0.0001f; + + if (glm::length2(velocity) < linearSleepThreshold && glm::length2(angularVelocity) < angularSleepThreshold) + { + sleepTimer += dt; + if (sleepTimer > 0.5f) + { + // Object has been stationary long enough to sleep + isSleeping = true; + velocity = glm::vec3(0.0f); + angularVelocity = glm::vec3(0.0f); + } + } + else + { + sleepTimer = 0.0f; + } + + // Zero out velocity components if they fall below the minimum threshold + if (std::abs(velocity.x) < velocityMin.x) + { + velocity.x = 0.f; + } + if (std::abs(velocity.y) < velocityMin.y) + { + velocity.y = 0.f; + } + if (std::abs(velocity.z) < velocityMin.z) + { + velocity.z = 0.f; + } + + if (collisionMode == CollisionDetectionMode::CONTINUOUS) + { + // Perform swept collision detection for high-speed movement + CollisionResult collision = FindClosestCollision(dt); + if (collision.hasCollided && collision.timeOfImpact <= 1.0f) + { + const float skinWidth = 0.005f; + // Calculate time to move before impact, including a small safety skin + 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 + glm::vec3 contactPoint = GetOwner()->GetPosition() - collision.collisionNormal * 0.1f; + CalculateLinearVelocity(*this, *collision.otherObject->GetComponent(), collision.collisionNormal, nullptr, contactPoint); + + // Move the object for the remaining timeframe after resolution + float remainingTime = dt - (dt * moveTime); + if (remainingTime > 0.0f) + { + GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * remainingTime); + } + } + else + { + GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * dt); + } + } + else + { + // Simple discrete movement + GetOwner()->SetPosition(GetOwner()->GetPosition() + velocity * dt); + } +} + +void Physics3D::Gravity(float dt) +{ + if (isGravityOn) + { + // Apply gravitational acceleration to the vertical velocity + velocity.y -= gravity * dt; + if (std::abs(velocity.y) > velocityMax.y) + { + velocity.y = velocityMax.y * ((velocity.y < 0.f) ? -1.f : 1.f); + } + } +} void Physics3D::Teleport(glm::vec3 newPosition) { - GetOwner()->SetPosition(newPosition); - velocity = { 0.f, 0.f, 0.f }; + // Instantly move the object and stop its motion + GetOwner()->SetPosition(newPosition); + velocity = glm::vec3(0.0f); } void Physics3D::SetMass(float m) { - if (m > 0.f) - { - mass = m; - } + if (m > 0.f) + { + mass = m; + } } bool Physics3D::CheckCollision(Object* obj) { - switch (colliderType) - { - case ColliderType3D::BOX: - if (obj->GetComponent()->GetColliderType() == ColliderType3D::BOX) - { - return CollisionPP(GetOwner(), obj); - } - else if (obj->GetComponent()->GetColliderType() == ColliderType3D::SPHERE) - { - return CollisionPS(GetOwner(), obj); - } - break; - case ColliderType3D::SPHERE: - if (obj->GetComponent()->GetColliderType() == ColliderType3D::BOX) - { - return CollisionPS(obj, GetOwner()); - } - else if (obj->GetComponent()->GetColliderType() == ColliderType3D::SPHERE) - { - return CollisionSS(GetOwner(), obj); - } - break; - default: - return false; - break; - } - return false; + // Dispatch to specific collision logic based on collider types + switch (colliderType) + { + case ColliderType3D::BOX: + { + if (obj->GetComponent()->GetColliderType() == ColliderType3D::BOX) + { + return CollisionPP(GetOwner(), obj); + } + else if (obj->GetComponent()->GetColliderType() == ColliderType3D::SPHERE) + { + return CollisionPS(GetOwner(), obj); + } + break; + } + case ColliderType3D::SPHERE: + { + if (obj->GetComponent()->GetColliderType() == ColliderType3D::BOX) + { + return CollisionPS(obj, GetOwner()); + } + else if (obj->GetComponent()->GetColliderType() == ColliderType3D::SPHERE) + { + return CollisionSS(GetOwner(), obj); + } + break; + } + default: + { + return false; + } + } + return false; } -bool Physics3D::CollisionPP(Object* obj, Object* obj2) +bool Physics3D::CollisionPP(Object* obj, Object* obj2, CollisionMode mode) { - if (obj->GetComponent()->GetCollidePolyhedron().empty() == false && obj2->GetComponent()->GetCollidePolyhedron().empty() == false) - { - const auto& poly1 = obj->GetComponent()->collidePolyhedron; - const auto& poly2 = obj2->GetComponent()->collidePolyhedron; - - if (poly1.empty() || poly2.empty()) - { - return false; - } - - std::vector rotatedPoly1, rotatedPoly2; - glm::mat4 transform1 = glm::translate(glm::mat4(1.0f), obj->GetPosition()) * glm::mat4_cast(glm::quat(-glm::radians(obj->GetRotate3D()))); - glm::mat4 transform2 = glm::translate(glm::mat4(1.0f), obj2->GetPosition()) * glm::mat4_cast(glm::quat(-glm::radians(obj2->GetRotate3D()))); - - 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))); - - - std::vector axes; - axes.emplace_back(glm::vec3(1, 0, 0)); - axes.emplace_back(glm::vec3(0, 1, 0)); - axes.emplace_back(glm::vec3(0, 0, 1)); - - for (size_t i = 0; i < rotatedPoly1.size(); ++i) - { - for (size_t j = 0; j < rotatedPoly2.size(); ++j) - { - 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) - { - axes.push_back(glm::normalize(axis)); - } - } - } - - float minDepth = std::numeric_limits::max(); - glm::vec3 collisionNormal(0.f); - - for (const auto& axis : axes) - { - float min1, max1, min2, max2; - float depth; - if (IsSeparatingAxis(axis, rotatedPoly1, rotatedPoly2, &depth, &min1, &max1, &min2, &max2)) - { - return false; - } - if (depth < minDepth) - { - minDepth = depth; - collisionNormal = axis; - } - } - - glm::vec3 centerDiff = FindSATCenter(rotatedPoly2) - FindSATCenter(rotatedPoly1); - if (glm::dot(centerDiff, collisionNormal) < 0) - collisionNormal = -collisionNormal; - - auto* physics1 = obj->GetComponent(); - auto* physics2 = obj2->GetComponent(); - - if (!physics1->GetIsGhostCollision() && !physics2->GetIsGhostCollision()) - { - const float correction_percent = 0.8f; // 관통의 80%만 한 프레임에 해결 - glm::vec3 moveVector = collisionNormal * (minDepth * correction_percent); - - if (physics1->GetBodyType() == BodyType3D::RIGID) - obj->SetPosition(obj->GetPosition() - moveVector / 2.0f); - if (physics2->GetBodyType() == BodyType3D::RIGID) - obj2->SetPosition(obj2->GetPosition() + moveVector / 2.0f); - - CalculateLinearVelocity(*physics1, *physics2, collisionNormal, &minDepth); - } - return true; - } - return false; + auto* physics1 = obj->GetComponent(); + auto* physics2 = obj2->GetComponent(); + + if (physics1->GetCollidePolyhedron().empty() == false && physics2->GetCollidePolyhedron().empty() == false) + { + const auto& poly1 = physics1->collidePolyhedron; + const auto& poly2 = physics2->collidePolyhedron; + + if (poly1.empty() || poly2.empty()) + { + return false; + } + + // 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())); + + glm::mat4 rotationMatrix1 = glm::mat4_cast(orient1); + glm::mat4 rotationMatrix2 = glm::mat4_cast(orient2); + + glm::mat4 transform1 = glm::translate(glm::mat4(1.0f), obj->GetPosition()) * rotationMatrix1; + glm::mat4 transform2 = glm::translate(glm::mat4(1.0f), obj2->GetPosition()) * rotationMatrix2; + + 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))); + } + + // 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]))); + + for (size_t i = 0; i < rotatedPoly1.size(); ++i) + { + for (size_t j = 0; j < rotatedPoly2.size(); ++j) + { + 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) + { + axes.push_back(glm::normalize(axis)); + } + } + } + + float minDepth = FLT_MAX; + glm::vec3 collisionNormal(0.f); + + // Test for separation along each axis (Separating Axis Theorem) + for (const auto& axis : axes) + { + float min1, max1, min2, max2; + float depth; + if (IsSeparatingAxis(axis, rotatedPoly1, rotatedPoly2, &depth, &min1, &max1, &min2, &max2)) + { + return false; // Gap found, no collision + } + if (depth < minDepth) + { + minDepth = depth; + collisionNormal = ((max1 - min2) < (max2 - min1)) ? axis : -axis; + } + } + + // Resolve penetration and calculate contact physics + if (mode == CollisionMode::COLLIDE && !physics1->GetIsGhostCollision() && !physics2->GetIsGhostCollision()) + { + const float slop = 0.005f; + const float correctionPercent = 0.15f; + float penetrationAmt = std::max(minDepth - slop, 0.0f); + glm::vec3 moveVector = collisionNormal * (penetrationAmt * correctionPercent); + + // Separate the overlapping objects based on their body type + if (physics1->GetBodyType() == BodyType3D::RIGID && physics2->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveVector) > 0.0f) + { + obj->SetPosition(obj->GetPosition() - moveVector * 0.5f); + obj2->SetPosition(obj2->GetPosition() + moveVector * 0.5f); + } + physics1->Awake(); + physics2->Awake(); + } + else if (physics1->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveVector) > 0.0f) + { + obj->SetPosition(obj->GetPosition() - moveVector); + } + physics1->Awake(); + } + else if (physics2->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveVector) > 0.0f) + { + obj2->SetPosition(obj2->GetPosition() + moveVector); + } + physics2->Awake(); + } + + // Find valid contact points to apply rotational impulses + const float contactTolerance = 0.001f; + float maxDot1 = -FLT_MAX; + for (const auto& p : rotatedPoly1) + { + maxDot1 = std::max(maxDot1, glm::dot(p, collisionNormal)); + } + + std::vector deepestPoints1; + for (const auto& p : rotatedPoly1) + { + if (glm::dot(p, collisionNormal) > maxDot1 - contactTolerance) + { + deepestPoints1.push_back(p); + } + } + + float maxDot2 = -FLT_MAX; + for (const auto& p : rotatedPoly2) + { + maxDot2 = std::max(maxDot2, glm::dot(p, -collisionNormal)); + } + + std::vector deepestPoints2; + for (const auto& p : rotatedPoly2) + { + if (glm::dot(p, -collisionNormal) > maxDot2 - contactTolerance) + { + deepestPoints2.push_back(p); + } + } + + // Filter points to find those actually inside the other polygon's bounds + glm::mat4 invTransform1 = glm::inverse(transform1); + glm::mat4 invTransform2 = glm::inverse(transform2); + glm::vec3 minLocal1(FLT_MAX), maxLocal1(-FLT_MAX); + for (const auto& p : poly1) + { + minLocal1 = glm::min(minLocal1, p); + maxLocal1 = glm::max(maxLocal1, p); + } + + glm::vec3 minLocal2(FLT_MAX), maxLocal2(-FLT_MAX); + for (const auto& p : poly2) + { + minLocal2 = glm::min(minLocal2, p); + maxLocal2 = glm::max(maxLocal2, p); + } + + const float eps = 0.01f; + minLocal1 -= eps; maxLocal1 += eps; + minLocal2 -= eps; maxLocal2 += eps; + + std::vector validContacts; + for (const auto& p : deepestPoints1) + { + glm::vec3 localP = glm::vec3(invTransform2 * glm::vec4(p, 1.0f)); + if (localP.x >= minLocal2.x && localP.x <= maxLocal2.x && localP.y >= minLocal2.y && localP.y <= maxLocal2.y && localP.z >= minLocal2.z && localP.z <= maxLocal2.z) + { + validContacts.push_back(p); + } + } + for (const auto& p : deepestPoints2) + { + glm::vec3 localP = glm::vec3(invTransform1 * glm::vec4(p, 1.0f)); + if (localP.x >= minLocal1.x && localP.x <= maxLocal1.x && localP.y >= minLocal1.y && localP.y <= maxLocal1.y && localP.z >= minLocal1.z && localP.z <= maxLocal1.z) + { + validContacts.push_back(p); + } + } + + // Apply linear and angular impulse at the contact centroid + if (validContacts.empty()) + { + glm::vec3 cp = deepestPoints1.empty() ? obj->GetPosition() : deepestPoints1[0]; + CalculateLinearVelocity(*physics1, *physics2, collisionNormal, &minDepth, cp); + } + else + { + glm::vec3 centroid(0.f); + for (const auto& cp : validContacts) + { + centroid += cp; + } + centroid /= static_cast(validContacts.size()); + CalculateLinearVelocity(*physics1, *physics2, collisionNormal, &minDepth, centroid); + } + } + return true; + } + return false; } -bool Physics3D::CollisionSS(Object* obj, Object* obj2) +bool Physics3D::CollisionSS(Object* obj, Object* obj2, CollisionMode mode) { - 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 (obj->GetComponent()->GetIsGhostCollision() == false && - obj2->GetComponent()->GetIsGhostCollision() == false) - { - if (obj->GetComponent()->GetBodyType() == BodyType3D::RIGID && - obj2->GetComponent()->GetBodyType() == BodyType3D::RIGID) - { - obj->SetPosition(obj->GetPosition() - normal * depth * 0.5f); - obj2->SetPosition(obj2->GetPosition() + normal * depth * 0.5f); - } - else if (obj->GetComponent()->GetBodyType() == BodyType3D::RIGID && - obj2->GetComponent()->GetBodyType() == BodyType3D::BLOCK) - { - obj->SetPosition(obj->GetPosition() - normal * depth); - } - else if (obj->GetComponent()->GetBodyType() == BodyType3D::BLOCK && - obj2->GetComponent()->GetBodyType() == BodyType3D::RIGID) - { - obj2->SetPosition(obj2->GetPosition() + normal * depth); - } - CalculateLinearVelocity(*obj->GetComponent(), *obj2->GetComponent(), normal, &depth); - } - return true; - } - return false; + // 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) +bool Physics3D::CollisionPS(Object* poly, Object* sph, CollisionMode mode) { - Physics3D* polyPhysics = poly->GetComponent(); - Physics3D* sphPhysics = sph->GetComponent(); - - const glm::vec3 polyPosition = poly->GetPosition(); - const glm::quat polyOrientation = glm::quat(glm::radians(-poly->GetRotate3D())); - - const glm::vec3 sphereCenter = sph->GetPosition(); - const float sphereRadius = sphPhysics->sphere.radius / 2.f; - - glm::vec3 minExtent = polyPhysics->GetCollidePolyhedron()[0]; - glm::vec3 maxExtent = polyPhysics->GetCollidePolyhedron()[6]; - - glm::vec3 sphereCenterInLocal = glm::inverse(polyOrientation) * (sphereCenter - polyPosition); - - glm::vec3 closestPointInLocal; - closestPointInLocal.x = std::max(minExtent.x, std::min(sphereCenterInLocal.x, maxExtent.x)); - closestPointInLocal.y = std::max(minExtent.y, std::min(sphereCenterInLocal.y, maxExtent.y)); - closestPointInLocal.z = std::max(minExtent.z, std::min(sphereCenterInLocal.z, maxExtent.z)); - - float distanceSquared = glm::length2(closestPointInLocal - sphereCenterInLocal); - - if (distanceSquared <= (sphereRadius * sphereRadius)) - { - if (!polyPhysics->GetIsGhostCollision() && !sphPhysics->GetIsGhostCollision()) - { - glm::vec3 collisionNormalInLocal = sphereCenterInLocal - closestPointInLocal; - if (glm::length2(collisionNormalInLocal) < 0.0001f) - { - collisionNormalInLocal = -sphereCenterInLocal; - } - - float distance = std::sqrt(distanceSquared); - float depth = sphereRadius - distance; - - glm::vec3 collisionNormal = glm::normalize(polyOrientation * collisionNormalInLocal); - glm::vec3 moveVector = collisionNormal * depth; - - if (polyPhysics->GetBodyType() == BodyType3D::RIGID) - { - poly->SetPosition(poly->GetPosition() - moveVector * 0.5f); - } - if (sphPhysics->GetBodyType() == BodyType3D::RIGID) - { - sph->SetPosition(sph->GetPosition() + moveVector * 0.5f); - } - - CalculateLinearVelocity(*polyPhysics, *sphPhysics, collisionNormal, &depth); - } - return true; - } - - return false; + // Polygon vs Sphere: Find the closest point on the polygon to the sphere center + Physics3D* polyPhysics = poly->GetComponent(); + Physics3D* sphPhysics = sph->GetComponent(); + + const glm::vec3 polyPos = poly->GetPosition(); + const glm::quat polyOrient = polyPhysics->GetEnableRotationalPhysics() ? polyPhysics->GetOrientation() : glm::quat(-glm::radians(poly->GetRotate3D())); + + const glm::vec3 sphereCenter = sph->GetPosition(); + const float sphereRadius = sphPhysics->sphere.radius / 2.f; + + if (polyPhysics->GetCollidePolyhedron().size() < 7) + { + return false; + } + + glm::vec3 minExtent = polyPhysics->GetCollidePolyhedron()[0]; + glm::vec3 maxExtent = polyPhysics->GetCollidePolyhedron()[6]; + + // Convert sphere center to the local coordinate space of the polygon + glm::vec3 sphereCenterInLocal = glm::inverse(polyOrient) * (sphereCenter - polyPos); + + // Find the closest point in local space by clamping + glm::vec3 closestPointInLocal; + closestPointInLocal.x = std::max(minExtent.x, std::min(sphereCenterInLocal.x, maxExtent.x)); + closestPointInLocal.y = std::max(minExtent.y, std::min(sphereCenterInLocal.y, maxExtent.y)); + closestPointInLocal.z = std::max(minExtent.z, std::min(sphereCenterInLocal.z, maxExtent.z)); + + float distSq = glm::length2(closestPointInLocal - sphereCenterInLocal); + + if (distSq <= (sphereRadius * sphereRadius)) + { + if (mode == CollisionMode::COLLIDE && !polyPhysics->GetIsGhostCollision() && !sphPhysics->GetIsGhostCollision()) + { + glm::vec3 normalInLocal = sphereCenterInLocal - closestPointInLocal; + if (glm::length2(normalInLocal) < 0.0001f) + { + normalInLocal = -sphereCenterInLocal; + } + + float dist = std::sqrt(distSq); + float depth = sphereRadius - dist; + const float slop = 0.005f; + float penetrationAmt = std::max(depth - slop, 0.0f); + + // Transform local normal back to world space + glm::vec3 normal = glm::normalize(polyOrient * normalInLocal); + glm::vec3 moveVec = normal * penetrationAmt; + + // Apply positional correction + if (polyPhysics->GetBodyType() == BodyType3D::RIGID && sphPhysics->GetBodyType() == BodyType3D::RIGID) + { + if (glm::length2(moveVec) > 0.0f) + { + poly->SetPosition(poly->GetPosition() - moveVec * 0.5f); + sph->SetPosition(sph->GetPosition() + moveVec * 0.5f); + } + 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); + colliderType = ColliderType3D::BOX; + collidePolyhedron.push_back(position); } void Physics3D::AddCollidePolyhedronAABB(glm::vec3 min, glm::vec3 max) { - colliderType = ColliderType3D::BOX; - collidePolyhedron.clear(); - collidePolyhedron = - { - {min.x, min.y, min.z}, {min.x, max.y, min.z}, {max.x, max.y, min.z}, {max.x, min.y, min.z}, - {min.x, min.y, max.z}, {min.x, max.y, max.z}, {max.x, max.y, max.z}, {max.x, min.y, max.z} - }; + // 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); + AddCollidePolyhedronAABB(-size / 2.f, size / 2.f); } void Physics3D::AddCollideSphere(float r) { - colliderType = ColliderType3D::SPHERE; - collidePolyhedron.clear(); - sphere.radius = r; + colliderType = ColliderType3D::SPHERE; + collidePolyhedron.clear(); + sphere.radius = r; } -glm::vec3 Physics3D::FindSATCenter(const std::vector& points_) +glm::vec3 Physics3D::FindSATCenter(const std::vector& pts) { - glm::vec3 center(0.0f); - for (const auto& point : points_) - { - center += point; - } - return center / static_cast(points_.size()); + // 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()); } -glm::vec3 Physics3D::RotatePoint(const glm::vec3& point, const glm::vec3& position, const glm::quat& rotation) //WIP +glm::vec3 Physics3D::RotatePoint(const glm::vec3& pt, const glm::vec3& pos, const glm::quat& rot) { - return (rotation * point) + position; + // Rotate a point around an origin and translate it + return (rot * pt) + pos; } -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_) +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) { - float min1 = INFINITY; - float min2 = INFINITY; - float max1 = -INFINITY; - float max2 = -INFINITY; - - glm::vec3 normalizedAxis = glm::normalize(axis); - float xAxisInfluence = std::abs(glm::dot(normalizedAxis, glm::vec3(1, 0, 0))); - float yAxisInfluence = std::abs(glm::dot(normalizedAxis, glm::vec3(0, 1, 0))); - float zAxisInfluence = std::abs(glm::dot(normalizedAxis, glm::vec3(0, 0, 1))); - - float depthScale = 1.0f + - (xAxisInfluence * 0.5f) + - (yAxisInfluence * 0.5f) + - (zAxisInfluence * 0.5f); - - for (const glm::vec3& vertex : points1) - { - float projection = glm::dot(axis, vertex); - min1 = std::min(min1, projection); - max1 = std::max(max1, projection); - } - for (const glm::vec3& vertex : points2) - { - float projection = glm::dot(axis, vertex); - min2 = std::min(min2, projection); - max2 = std::max(max2, projection); - } - - *axisDepth = std::min(max2 - min1, max1 - min2) * depthScale; - - *min1_ = min1; - *max1_ = max1; - *min2_ = min2; - *max2_ = max2; - - // 겹침 검사 - return !(max1 >= min2 && max2 >= min1); + // 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; + + for (const glm::vec3& v : pts1) + { + float p = glm::dot(axis, v); + min1 = std::min(min1, p); + max1 = std::max(max1, p); + } + for (const glm::vec3& v : pts2) + { + float p = glm::dot(axis, v); + min2 = std::min(min2, p); + max2 = std::max(max2, p); + } + + // Determine the amount of overlap along the axis + *depth = std::min(max2 - min1, max1 - min2); + *mi1 = min1; *ma1 = max1; *mi2 = min2; *ma2 = max2; + + return !(max1 >= min2 && max2 >= min1); } -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_) +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) { - float min1 = INFINITY; - float max1 = -INFINITY; - float min2 = INFINITY; - float max2 = -INFINITY; - - glm::vec3 normalizedAxis = glm::normalize(axis); - float xAxisInfluence = std::abs(glm::dot(normalizedAxis, glm::vec3(1, 0, 0))); - float yAxisInfluence = std::abs(glm::dot(normalizedAxis, glm::vec3(0, 1, 0))); - float zAxisInfluence = std::abs(glm::dot(normalizedAxis, glm::vec3(0, 0, 1))); - - float depthScale = 1.0f + - (xAxisInfluence * 0.5f) + - (yAxisInfluence * 0.5f) + - (zAxisInfluence * 0.5f); - - for (const glm::vec3& point : pointsPoly) - { - float projection = glm::dot(point, axis); - min1 = std::min(min1, projection); - max1 = std::max(max1, projection); - } - - float sphereProjection = glm::dot(pointSphere, axis); - min2 = sphereProjection - radius; - max2 = sphereProjection + radius; - - *axisDepth = std::min(max2 - min1, max1 - min2) * depthScale; - *min1_ = min1; - *max1_ = max1; - *min2_ = min2; - *max2_ = max2; - - return !(max1 >= min2 && max2 >= min1); + // 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*/) +void Physics3D::CalculateLinearVelocity(Physics3D& body, Physics3D& body2, glm::vec3 normal, float* /*axisDepth*/, glm::vec3 contactPoint, float impulseScale) { - glm::vec3 relativeVelocity = body2.GetVelocity() - body.GetVelocity(); - float res = std::min(body.GetRestitution(), body2.GetRestitution()); - float j = -(1.f - res) * glm::dot(relativeVelocity, normal); - j /= (1.f / body.mass) + (1.f / body2.mass); - glm::vec3 impulse = j * normal; - - if (body.GetBodyType() == BodyType3D::RIGID) - { - body.SetVelocity(body.GetVelocity() - impulse / body.mass); - } - if (body2.GetBodyType() == BodyType3D::RIGID) - { - body2.SetVelocity(body2.GetVelocity() + impulse / body2.mass); - } + // 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 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 impulse magnitude j + float j = -(1.f + res) * velAlongNormal / denominator; + j *= impulseScale; + glm::vec3 impulse = normal * j; + + // 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()); + } + } } -glm::vec3 Physics3D::FindClosestPointOnSegment(const glm::vec3& sphereCenter, std::vector& vertices) +glm::vec3 Physics3D::FindClosestPointOnSegment(const glm::vec3& cpSphere, std::vector& verts) { - glm::vec3 closestPoint = vertices[0]; - float minDistanceSquared = glm::length2(sphereCenter - closestPoint); - - for (size_t i = 1; i < vertices.size(); ++i) - { - const glm::vec3& v = vertices[i]; - float distanceSquared = glm::length2(v - sphereCenter); - - if (distanceSquared < minDistanceSquared) - { - minDistanceSquared = distanceSquared; - closestPoint = v; - } - } - return closestPoint; + // Find the point on the polygon boundary closest to the sphere center + if (verts.empty()) + { + return glm::vec3(0.0f); + } + glm::vec3 resultPoint = verts[0]; + float minDistanceSquared = FLT_MAX; + + for (size_t i = 0; i < verts.size(); i++) + { + glm::vec3 va = verts[i]; + glm::vec3 vb = verts[(i + 1) % verts.size()]; + glm::vec3 edge = vb - va; + glm::vec3 toSphere = cpSphere - va; + float lenSq = glm::length2(edge); + float t = 0.0f; + if (lenSq > 0.0f) + { + // Project to find the normalized parameter t along the edge + t = std::max(0.0f, std::min(1.0f, glm::dot(toSphere, edge) / lenSq)); + } + glm::vec3 closestPoint = va + edge * t; + float dSq = glm::length2(closestPoint - cpSphere); + if (dSq < minDistanceSquared) + { + minDistanceSquared = dSq; + resultPoint = closestPoint; + } + } + return resultPoint; } - -void Physics3D::ProjectPolygon(const std::vector& vertices, const glm::vec3& axis, float& min, float& max) +void Physics3D::ProjectPolygon(const std::vector& verts, const glm::vec3& axis, float& min, float& max) { - min = glm::dot(vertices[0], axis); - max = min; - for (size_t i = 1; i < vertices.size(); ++i) - { - float p = glm::dot(vertices[i], axis); - if (p < min) - { - min = p; - } - else if (p > max) - { - max = p; - } - } + // Utility to project a list of vertices onto a single axis + if (verts.empty()) + { + return; + } + min = glm::dot(verts[0], axis); + max = min; + for (size_t i = 1; i < verts.size(); ++i) + { + float p = glm::dot(verts[i], axis); + if (p < min) + { + min = p; + } + else if (p > max) + { + max = p; + } + } } -bool Physics3D::SweptSATOBB(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& outResult) +bool Physics3D::SweptSATOBB(Physics3D* body1, Physics3D* body2, float dt, CollisionResult& res) { - Object* obj1 = body1->GetOwner(); - Object* obj2 = body2->GetOwner(); - - const auto& poly1 = body1->GetCollidePolyhedron(); - const auto& poly2 = body2->GetCollidePolyhedron(); - - if (poly1.empty() || poly2.empty()) - { - return false; - } - - std::vector rotatedPoly1, rotatedPoly2; - - // Build transformation matrix for the first object (body1). - glm::vec3 eulerAngles1 = glm::radians(obj1->GetRotate3D()); - glm::mat4 rotX1 = glm::rotate(glm::mat4(1.0f), eulerAngles1.x, glm::vec3(1, 0, 0)); - glm::mat4 rotY1 = glm::rotate(glm::mat4(1.0f), eulerAngles1.y, glm::vec3(0, 1, 0)); - glm::mat4 rotZ1 = glm::rotate(glm::mat4(1.0f), eulerAngles1.z, glm::vec3(0, 0, 1)); - glm::mat4 rotationMatrix1 = rotZ1 * rotY1 * rotX1; - glm::mat4 transform1 = glm::translate(glm::mat4(1.0f), obj1->GetPosition()) * rotationMatrix1; - - // Build transformation matrix for the second object (body2). - glm::vec3 eulerAngles2 = glm::radians(obj2->GetRotate3D()); - glm::mat4 rotX2 = glm::rotate(glm::mat4(1.0f), eulerAngles2.x, glm::vec3(1, 0, 0)); - glm::mat4 rotY2 = glm::rotate(glm::mat4(1.0f), eulerAngles2.y, glm::vec3(0, 1, 0)); - glm::mat4 rotZ2 = glm::rotate(glm::mat4(1.0f), eulerAngles2.z, glm::vec3(0, 0, 1)); - glm::mat4 rotationMatrix2 = rotZ2 * rotY2 * rotX2; - glm::mat4 transform2 = glm::translate(glm::mat4(1.0f), obj2->GetPosition()) * rotationMatrix2; - - // Transform polyhedron vertices to world space. - 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))); - } - - // Check for initial intersection before performing swept test. - glm::vec3 initialNormal; - float initialDepth; - if (StaticSATIntersection(body1, body2, rotatedPoly1, rotatedPoly2, rotationMatrix1, rotationMatrix2, initialNormal, initialDepth)) - { - outResult.hasCollided = true; - outResult.timeOfImpact = 0.0f; - outResult.otherObject = body2->GetOwner(); - outResult.collisionNormal = initialNormal; - return true; - } - - // Calculate relative velocity for the current frame. - glm::vec3 relativeVelocity = (body2->GetVelocity() - body1->GetVelocity()) * dt; - - // Collect all potential separating axes. - std::vector axes; - const std::vector> faces = - { - {4, 5, 6, 7}, {3, 2, 1, 0}, {7, 6, 2, 3}, - {4, 0, 1, 5}, {1, 2, 6, 5}, {4, 7, 3, 0} - }; - const std::vector> edges = - { - {0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, - {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7} - }; - - // Axes from face normals of both polyhedrons. - for (const auto& face : faces) - { - axes.push_back(glm::normalize(glm::cross(rotatedPoly1[face[1]] - rotatedPoly1[face[0]], rotatedPoly1[face[3]] - rotatedPoly1[face[0]]))); - } - for (const auto& face : faces) - { - axes.push_back(glm::normalize(glm::cross(rotatedPoly2[face[1]] - rotatedPoly2[face[0]], rotatedPoly2[face[3]] - rotatedPoly2[face[0]]))); - } - - // Axes from cross products of edges from both polyhedrons. - std::vector edges1, edges2; - for (const auto& edge : edges) - { - edges1.push_back(rotatedPoly1[edge[1]] - rotatedPoly1[edge[0]]); - } - for (const auto& edge : edges) - { - edges2.push_back(rotatedPoly2[edge[1]] - rotatedPoly2[edge[0]]); - } - for (const auto& edge1 : edges1) - { - for (const auto& edge2 : edges2) - { - glm::vec3 axis = glm::cross(edge1, edge2); - if (glm::length2(axis) > 0.0001f) axes.push_back(glm::normalize(axis)); - } - } - - float tFirst = 0.0f; - float tLast = 1.0f; - glm::vec3 bestNormal; - - for (const auto& axis : axes) - { - glm::vec3 normalizedAxis = glm::normalize(axis); - if (glm::length2(normalizedAxis) < 0.0001f) continue; - - float min1, max1, min2, max2; - ProjectPolygon(rotatedPoly1, normalizedAxis, min1, max1); - ProjectPolygon(rotatedPoly2, normalizedAxis, min2, max2); - - float projectedVelocity = glm::dot(relativeVelocity, normalizedAxis); - - // Find the time of first and last contact on this axis. - if (max1 < min2) { - if (projectedVelocity <= 0) { // Moving apart or parallel. - return false; - } - float tEnter = (min2 - max1) / projectedVelocity; - if (tEnter > tFirst) { - tFirst = tEnter; - bestNormal = -normalizedAxis; - } - } - else if (max2 < min1) - { - if (projectedVelocity >= 0) - { // Moving apart or parallel. - return false; - } - float tEnter = (max2 - min1) / projectedVelocity; - if (tEnter > tFirst) - { - tFirst = tEnter; - bestNormal = normalizedAxis; - } - } - - if (projectedVelocity > 0) - { - tLast = std::min(tLast, (max2 - min1) / projectedVelocity); - } - else if (projectedVelocity < 0) - { - tLast = std::min(tLast, (min2 - max1) / projectedVelocity); - } - - // If the intervals do not overlap, a separating axis is found for the whole movement. - if (tFirst > tLast) - { - return false; - } - } - - // If a collision is predicted within the frame time. - if (tFirst >= 0.0f && tFirst <= 1.0f) - { - // Correct the normal direction. - glm::vec3 center1 = FindSATCenter(rotatedPoly1); - glm::vec3 center2 = FindSATCenter(rotatedPoly2); - if (glm::dot(center2 - center1, bestNormal) < 0.0f) - { - bestNormal = -bestNormal; - } - - outResult.hasCollided = true; - outResult.timeOfImpact = tFirst; - outResult.otherObject = obj2; - outResult.collisionNormal = glm::normalize(bestNormal); - return true; - } - - return false; + // High-performance swept collision detection using SAT + Object* o1 = body1->GetOwner(); + Object* o2 = body2->GetOwner(); + const auto& poly1 = body1->GetCollidePolyhedron(); + const auto& poly2 = body2->GetCollidePolyhedron(); + if (poly1.empty() || poly2.empty()) + { + return false; + } + + // Determine world-space vertices + std::vector rotPoly1, rotPoly2; + glm::quat orient1 = body1->GetEnableRotationalPhysics() ? body1->GetOrientation() : glm::quat(glm::radians(o1->GetRotate3D())); + glm::quat orient2 = body2->GetEnableRotationalPhysics() ? body2->GetOrientation() : glm::quat(glm::radians(o2->GetRotate3D())); + glm::mat4 rMat1 = glm::mat4_cast(orient1); + glm::mat4 rMat2 = glm::mat4_cast(orient2); + glm::mat4 t1 = glm::translate(glm::mat4(1.0f), o1->GetPosition()) * rMat1; + glm::mat4 t2 = glm::translate(glm::mat4(1.0f), o2->GetPosition()) * rMat2; + + for (const auto& p : poly1) + { + rotPoly1.emplace_back(glm::vec3(t1 * glm::vec4(p, 1.0f))); + } + for (const auto& p : poly2) + { + rotPoly2.emplace_back(glm::vec3(t2 * glm::vec4(p, 1.0f))); + } + + // Check for static intersection first + glm::vec3 iNorm; + float iDepth; + if (StaticSATIntersection(body1, body2, rotPoly1, rotPoly2, rMat1, rMat2, iNorm, iDepth)) + { + res.hasCollided = true; + res.timeOfImpact = 0.0f; + res.otherObject = o2; + res.collisionNormal = iNorm; + return true; + } + + // Calculate relative movement per frame + glm::vec3 relVel = (body2->GetVelocity() - body1->GetVelocity()) * dt; + std::vector axes; + glm::vec3 a1[3] = { glm::normalize(glm::vec3(rMat1[0])), glm::normalize(glm::vec3(rMat1[1])), glm::normalize(glm::vec3(rMat1[2])) }; + glm::vec3 a2[3] = { glm::normalize(glm::vec3(rMat2[0])), glm::normalize(glm::vec3(rMat2[1])), glm::normalize(glm::vec3(rMat2[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)); + } + } + } + + // Iterate through axes to find the first and last time of contact + float tFirst = 0.0f, tLast = 1.0f; + glm::vec3 bestNorm(0.0f); + for (const auto& axis : axes) + { + float min1, max1, min2, max2; + ProjectPolygon(rotPoly1, axis, min1, max1); + ProjectPolygon(rotPoly2, axis, min2, max2); + float v = glm::dot(relVel, axis); + if (max1 < min2) + { + if (v <= 0.0f) + { + return false; + } + float t = (min2 - max1) / v; + if (t > tFirst) + { + tFirst = t; bestNorm = -axis; + } + } + else if (max2 < min1) + { + if (v >= 0.0f) + { + return false; + } + float t = (max2 - min1) / v; + if (t > tFirst) + { + tFirst = t; bestNorm = axis; + } + } + + // Update exit time + if (v > 0.0f) + { + tLast = std::min(tLast, (max2 - min1) / v); + } + else if (v < 0.0f) + { + tLast = std::min(tLast, (min2 - max1) / v); + } + + if (tFirst > tLast) + { + return false; + } + } + + if (tFirst >= 0.0f && tFirst <= 1.0f) + { + res.hasCollided = true; + res.timeOfImpact = tFirst; + res.otherObject = o2; + res.collisionNormal = glm::normalize(bestNorm); + return true; + } + return false; } -bool Physics3D::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 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) { - Object* obj1 = body1->GetOwner(); - Object* obj2 = body2->GetOwner(); - - std::vector axes; - // Get the world-space axes of each OBB (face normals). - glm::vec3 obb1_axes[3] = - { - glm::normalize(glm::vec3(rotationMatrix1[0])), - glm::normalize(glm::vec3(rotationMatrix1[1])), - glm::normalize(glm::vec3(rotationMatrix1[2])) - }; - glm::vec3 obb2_axes[3] = - { - glm::normalize(glm::vec3(rotationMatrix2[0])), - glm::normalize(glm::vec3(rotationMatrix2[1])), - glm::normalize(glm::vec3(rotationMatrix2[2])) - }; - - // 1. Add the 3 face normal axes of the first OBB. - axes.push_back(obb1_axes[0]); - axes.push_back(obb1_axes[1]); - axes.push_back(obb1_axes[2]); - - // 2. Add the 3 face normal axes of the second OBB. - axes.push_back(obb2_axes[0]); - axes.push_back(obb2_axes[1]); - axes.push_back(obb2_axes[2]); - - // 3. Add up to 9 cross-product axes from the edges of both OBBs. - for (int i = 0; i < 3; ++i) - { - for (int j = 0; j < 3; ++j) - { - glm::vec3 cross_axis = glm::cross(obb1_axes[i], obb2_axes[j]); - // Avoid adding a zero vector if axes are parallel. - if (glm::length2(cross_axis) > 0.0001f) - { - axes.push_back(glm::normalize(cross_axis)); - } - } - } - - float minDepth = std::numeric_limits::max(); - glm::vec3 collisionNormal; - - for (const auto& axis : axes) - { - float min1, max1, min2, max2; - ProjectPolygon(rotatedPoly1, axis, min1, max1); - ProjectPolygon(rotatedPoly2, axis, min2, max2); - - // Found a separating axis, no collision. - if (max1 < min2 || max2 < min1) - { - return false; - } - - float depth = std::min(max2 - min1, max1 - min2); - if (depth < minDepth) - { - minDepth = depth; - collisionNormal = axis; - } - } - - // Correct the direction of the collision normal. - glm::vec3 center1 = FindSATCenter(rotatedPoly1); - glm::vec3 center2 = FindSATCenter(rotatedPoly2); - if (glm::dot(center2 - center1, collisionNormal) < 0.0f) - { - collisionNormal = -collisionNormal; - } - - outNormal = glm::normalize(collisionNormal); - outDepth = minDepth; - return true; // Overlap on all axes, collision detected. + // 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* body1, Physics3D* body2, float dt, CollisionResult& outResult) +bool Physics3D::SweptSpheres(Physics3D* b1, Physics3D* b2, float dt, CollisionResult& res) { - Object* obj1 = body1->GetOwner(); - Object* obj2 = body2->GetOwner(); - - glm::vec3 pos1 = obj1->GetPosition(); - glm::vec3 pos2 = obj2->GetPosition(); - float r1 = body1->sphere.radius / 2.0f; - float r2 = body2->sphere.radius / 2.0f; - - // Calculate the relative velocity vector for the current frame. - glm::vec3 rel_vel_per_second = body2->GetVelocity() - body1->GetVelocity(); - glm::vec3 rel_vel = rel_vel_per_second * dt; - - glm::vec3 start_diff = pos2 - pos1; - float combined_radius = r1 + r2; - - // Coefficients for the quadratic equation: At^2 + Bt + C = 0 - float a = glm::dot(rel_vel, rel_vel); - float b = 2.0f * glm::dot(start_diff, rel_vel); - float c = glm::dot(start_diff, start_diff) - combined_radius * combined_radius; - - // Check for initial overlap. If c < 0, the spheres are already overlapping. - if (c < 0.0f) - { - outResult.hasCollided = true; - outResult.timeOfImpact = 0.0f; - outResult.otherObject = obj2; - outResult.collisionNormal = glm::normalize(pos1 - pos2); - return true; - } - - // Handle the case where there's no relative movement. - // If not moving and not overlapping, they will never collide. - if (a < 0.00001f) - { - return false; - } - - // Use the discriminant to check if a future collision will occur. - float discriminant = b * b - 4 * a * c; - if (discriminant < 0) - { - return false; // No real roots, no collision. - } - - // Calculate and validate the time of impact (t). - // Use the smaller root for the first time of impact. - float t = (-b - sqrt(discriminant)) / (2.0f * a); - - // Check if the collision occurs within this frame. - if (t >= 0.0f && t <= 1.0f) - { - outResult.hasCollided = true; - outResult.timeOfImpact = t; - outResult.otherObject = obj2; - // Calculate the exact normal at the moment of impact. - outResult.collisionNormal = glm::normalize((pos1 + body1->GetVelocity() * dt * t) - (pos2 + body2->GetVelocity() * dt * t)); - return true; - } - - return false; + // 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* boxBody, float dt, CollisionResult& outResult) +bool Physics3D::SweptSphereVsOBB(Physics3D* b2, float dt, CollisionResult& res) { - Object* sphereObj = this->GetOwner(); - Object* boxObj = boxBody->GetOwner(); - - // Create the inverse transform matrix to move into the box's local space. - glm::mat4 boxRotationMatrix; - { - glm::vec3 eulerAngles = glm::radians(boxObj->GetRotate3D()); - boxRotationMatrix = glm::rotate(glm::mat4(1.0f), -eulerAngles.z, glm::vec3(0, 0, 1)); - boxRotationMatrix = glm::rotate(boxRotationMatrix, -eulerAngles.y, glm::vec3(0, 1, 0)); - boxRotationMatrix = glm::rotate(boxRotationMatrix, -eulerAngles.x, glm::vec3(1, 0, 0)); - } - glm::mat4 boxTransform = glm::translate(glm::mat4(1.0f), boxObj->GetPosition()) * boxRotationMatrix; - glm::mat4 inverseBoxTransform = glm::inverse(boxTransform); - - // Transform sphere's position and velocity into the box's local space. - glm::vec3 spherePos_local = glm::vec3(inverseBoxTransform * glm::vec4(sphereObj->GetPosition(), 1.0f)); - glm::vec3 sphereVel_local = glm::vec3(inverseBoxTransform * glm::vec4(this->GetVelocity() * dt, 0.0f)); - - glm::vec3 boxHalfExtents = boxBody->GetCollidePolyhedron()[6]; - float sphereRadius = this->sphere.radius / 2.0f; - - float tFirst = 0.0f; - float tLast = 1.0f; - glm::vec3 hitNormal_local(0.0f); - - // Test against each of the three slabs of the OBB. - for (int i = 0; i < 3; ++i) - { - float slab_min = -boxHalfExtents[i] - sphereRadius; - float slab_max = boxHalfExtents[i] + sphereRadius; - - // Check for no collision if velocity is parallel to the slab. - if (std::abs(sphereVel_local[i]) < 0.00001f) - { - if (spherePos_local[i] < slab_min || spherePos_local[i] > slab_max) return false; - } - else - { - // Calculate time of entry and exit from the slab. - float tEnter = (slab_min - spherePos_local[i]) / sphereVel_local[i]; - float tLeave = (slab_max - spherePos_local[i]) / sphereVel_local[i]; - if (tEnter > tLeave) - { - std::swap(tEnter, tLeave); - } - - // Update the overall time of first contact and collision normal. - if (tEnter >= tFirst) - { - tFirst = tEnter; - hitNormal_local = glm::vec3(0.0f); - hitNormal_local[i] = (sphereVel_local[i] > 0) ? -1.0f : 1.0f; - } - tLast = std::min(tLast, tLeave); - - if (tFirst > tLast) - { - return false; // No overlap in collision intervals. - } - } - } - - // If the time of impact is 0 (initial overlap), calculate a stable push-out normal. - if (tFirst == 0.0f) - { - // Determine the normal based on the vector from the box center to the sphere's local position. - glm::vec3 closestPointOnBox = glm::clamp(spherePos_local, -boxHalfExtents, boxHalfExtents); - glm::vec3 direction = spherePos_local - closestPointOnBox; - if (glm::length2(direction) < 0.0001f) - { - // If the sphere's center is inside the box, - hitNormal_local = -glm::normalize(spherePos_local); // push it out from the center. - } - else - { - hitNormal_local = glm::normalize(direction); - } - } - - // If a collision occurs within the frame. - if (tFirst >= 0.0f && tFirst <= 1.0f) - { - // Transform the normal to world space using only the rotation part of the matrix. - glm::mat4 pureRotation = glm::mat4(1.0f); - glm::vec3 boxEuler = glm::radians(boxObj->GetRotate3D()); - pureRotation = glm::rotate(pureRotation, -boxEuler.z, glm::vec3(0, 0, 1)); - pureRotation = glm::rotate(pureRotation, -boxEuler.y, glm::vec3(0, 1, 0)); - pureRotation = glm::rotate(pureRotation, -boxEuler.x, glm::vec3(1, 0, 0)); - glm::vec3 hitNormal_world = glm::normalize(glm::vec3(glm::inverse(pureRotation) * glm::vec4(hitNormal_local, 0.0f))); - - outResult.hasCollided = true; - outResult.timeOfImpact = tFirst; - outResult.otherObject = boxObj; - outResult.collisionNormal = hitNormal_world; - return true; - } - - return false; + // Test moving sphere against a static/rotating Oriented Bounding Box + Object* o1 = GetOwner(), * o2 = b2->GetOwner(); + glm::quat orient2 = b2->GetEnableRotationalPhysics() ? b2->GetOrientation() : glm::quat(glm::radians(o2->GetRotate3D())); + glm::mat4 rm2 = glm::mat4_cast(orient2); + glm::mat4 im2 = glm::inverse(glm::translate(glm::mat4(1.0f), o2->GetPosition()) * rm2); + + // Convert relative motion into OBB local space + glm::vec3 p1l = glm::vec3(im2 * glm::vec4(o1->GetPosition(), 1.0f)); + glm::vec3 v1l = glm::vec3(im2 * glm::vec4(GetVelocity() * dt, 0.0f)); + glm::vec3 h2 = b2->GetCollidePolyhedron().size() > 6 ? b2->GetCollidePolyhedron()[6] : glm::vec3(1.0f); + + float r1 = sphere.radius / 2.0f, tFirst = 0.0f, tLast = 1.0f; + glm::vec3 nL(0.0f); + + // Slab method for AABB/OBB collision in local space + for (int i = 0; i < 3; ++i) + { + float sMin = -h2[i] - r1, sMax = h2[i] + r1; + if (std::abs(v1l[i]) < 0.00001f) + { + if (p1l[i] < sMin || p1l[i] > sMax) + { + return false; + } + } + else + { + float tE = (sMin - p1l[i]) / v1l[i]; + float tL = (sMax - p1l[i]) / v1l[i]; + if (tE > tL) + { + std::swap(tE, tL); + } + if (tE > tFirst) + { + tFirst = tE; nL = glm::vec3(0.0f); nL[i] = (v1l[i] > 0) ? -1.0f : 1.0f; + } + tLast = std::min(tLast, tL); + if (tFirst > tLast) + { + return false; + } + } + } + + if (tFirst == 0.0f) + { + glm::vec3 cp = glm::clamp(p1l, -h2, h2); + glm::vec3 d = p1l - cp; + nL = glm::length2(d) < 0.0001f ? -glm::normalize(p1l) : glm::normalize(d); + } + if (tFirst >= 0.0f && tFirst <= 1.0f) + { + res.hasCollided = true; res.timeOfImpact = tFirst; res.otherObject = o2; + res.collisionNormal = glm::normalize(orient2 * nL); + return true; + } + return false; } CollisionResult Physics3D::FindClosestCollision(float dt) { - CollisionResult closestCollision; - closestCollision.timeOfImpact = 1.1f; // Initialize with a time greater than 1.0 - Object* self = GetOwner(); - const auto& allObjects = Engine::GetObjectManager().GetObjectMap(); - - for (const auto& pair : allObjects) - { - Object* other = pair.second.get(); - if (self == other || !other->HasComponent()) continue; - - Physics3D* otherBody = other->GetComponent(); - CollisionResult currentCollision; - - ColliderType3D type1 = this->GetColliderType(); - ColliderType3D type2 = otherBody->GetColliderType(); - - // Call the appropriate CCD function based on collider types. - if (type1 == ColliderType3D::SPHERE && type2 == ColliderType3D::SPHERE) - { - // Sphere vs Sphere test. - if (SweptSpheres(this, otherBody, dt, currentCollision)) - { - if (currentCollision.timeOfImpact < closestCollision.timeOfImpact) - { - closestCollision = currentCollision; - } - } - } - else if (type1 == ColliderType3D::BOX && type2 == ColliderType3D::BOX) - { - // Box vs Box test. - if (SweptSATOBB(this, otherBody, dt, currentCollision)) - { - if (currentCollision.timeOfImpact < closestCollision.timeOfImpact) - { - closestCollision = currentCollision; - } - } - } - else if (type1 == ColliderType3D::SPHERE && type2 == ColliderType3D::BOX) - { - // Sphere vs Box test. - if (this->SweptSphereVsOBB(otherBody, dt, currentCollision)) - { - if (currentCollision.timeOfImpact < closestCollision.timeOfImpact) - { - closestCollision = currentCollision; - } - } - } - else if (type1 == ColliderType3D::BOX && type2 == ColliderType3D::SPHERE) - { - // Box vs Sphere test (reusing the function and reversing the normal). - if (otherBody->SweptSphereVsOBB(this, dt, currentCollision)) - { - currentCollision.collisionNormal *= -1.0f; - if (currentCollision.timeOfImpact < closestCollision.timeOfImpact) - { - closestCollision = currentCollision; - } - } - } - } - return closestCollision; + // 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 diff --git a/Engine/source/CameraManager.cpp b/Engine/source/CameraManager.cpp index 83801b6f..a25c1844 100644 --- a/Engine/source/CameraManager.cpp +++ b/Engine/source/CameraManager.cpp @@ -69,73 +69,90 @@ void CameraManager::CameraControllerImGui() } } + ImGui::Begin("CameraController"); glm::vec3 position = GetCameraPosition(); float zoom = GetZoom(); - float nearClip = GetNear(); - float farClip = GetFar(); - float pitch = GetPitch(); - float yaw = GetYaw(); - float cameraSensitivity = GetCameraSensitivity(); - bool isRelativeOn = Engine::GetInputManager().GetRelativeMouseMode(); - glm::vec3 cameraOffset = GetCameraOffset(); - float cameraDistance = GetCameraDistance(); - bool isThirdPersonView = GetIsThirdPersonView(); + if (GetCameraType() == CameraType::ThreeDimension) + { + float nearClip = GetNear(); + float farClip = GetFar(); + float pitch = GetPitch(); + float yaw = GetYaw(); - ImGui::Begin("CameraController"); + glm::vec3 cameraOffset = GetCameraOffset(); + float cameraDistance = GetCameraDistance(); + bool isThirdPersonView = GetIsThirdPersonView(); - ImGui::Checkbox("Third Person View Mod", &isThirdPersonView); - SetIsThirdPersonViewMod(isThirdPersonView); + bool isRelativeOn = Engine::GetInputManager().GetRelativeMouseMode(); + ImGui::Checkbox("Relative Mouse Mod (Press Q)", &isRelativeOn); + Engine::GetInputManager().SetRelativeMouseMode(isRelativeOn); - ImGui::Checkbox("Relative Mouse Mod (Press Q)", &isRelativeOn); - Engine::GetInputManager().SetRelativeMouseMode(isRelativeOn); + ImGui::Checkbox("Third Person View Mod", &isThirdPersonView); + SetIsThirdPersonViewMod(isThirdPersonView); - ImGui::SliderFloat("CameraSensitivity", &cameraSensitivity, 0.1f, 100.f); - SetCameraSensitivity(cameraSensitivity); + float cameraSensitivity = GetCameraSensitivity(); + ImGui::SliderFloat("CameraSensitivity", &cameraSensitivity, 0.1f, 100.f); + SetCameraSensitivity(cameraSensitivity); - ImGui::DragFloat3("Position", &position.x, 0.01f); - SetCameraPosition(position); + ImGui::DragFloat3("Position", &position.x, 0.01f); + SetCameraPosition(position); - ImGui::DragFloat("Zoom", &zoom, 0.5f); - SetZoom(zoom); + ImGui::DragFloat("Zoom", &zoom, 0.5f); + SetZoom(zoom); - ImGui::DragFloat("Near", &nearClip, 0.05f); - SetNear(nearClip); + ImGui::DragFloat("Near", &nearClip, 0.05f); + SetNear(nearClip); - ImGui::DragFloat("Far", &farClip, 0.05f); - SetFar(farClip); + ImGui::DragFloat("Far", &farClip, 0.05f); + SetFar(farClip); - ImGui::DragFloat("Pitch", &pitch, 0.5f); - SetPitch(pitch); + ImGui::DragFloat("Pitch", &pitch, 0.5f); + SetPitch(pitch); - ImGui::DragFloat("Yaw", &yaw, 0.5f); - SetYaw(yaw); - - if (isThirdPersonView == true && !Engine::GetObjectManager().GetObjectMap().empty()) - { - if (ImGui::CollapsingHeader("Third Person View Option", ImGuiTreeNodeFlags_DefaultOpen)) + ImGui::DragFloat("Yaw", &yaw, 0.5f); + SetYaw(yaw); + + if (isThirdPersonView == true && !Engine::GetObjectManager().GetObjectMap().empty()) { - ImGui::BeginChild("Scolling"); - int index = 0; - for (auto& object : Engine::GetObjectManager().GetObjectMap()) + if (ImGui::CollapsingHeader("Third Person View Option", ImGuiTreeNodeFlags_DefaultOpen)) { - 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)) + ImGui::BeginChild("Scrolling", ImVec2(0, 100)); + int index = 0; + for (auto& object : Engine::GetObjectManager().GetObjectMap()) { - currentObjIndex = index; + 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::PopStyleColor(); - index++; - } - ImGui::EndChild(); - SetTarget(Engine::GetObjectManager().FindObjectWithId(currentObjIndex)->GetPosition()); + ImGui::EndChild(); + SetTarget(Engine::GetObjectManager().FindObjectWithId(currentObjIndex)->GetPosition()); - ImGui::DragFloat("Distance", &cameraDistance, 0.05f); - SetCameraDistance(cameraDistance); - ImGui::DragFloat3("Offset", &cameraOffset.x, 0.01f); - SetCameraOffset(cameraOffset); + ImGui::DragFloat("Distance", &cameraDistance, 0.05f); + SetCameraDistance(cameraDistance); + ImGui::DragFloat3("Offset", &cameraOffset.x, 0.01f); + SetCameraOffset(cameraOffset); + } } } + else if (GetCameraType() == CameraType::TwoDimension) + { + float rotate2D = GetRotate2D(); + + ImGui::DragFloat2("Position", &position.x, 0.1f); + SetCameraPosition(position); + + ImGui::DragFloat("Zoom", &zoom, 0.1f); + SetZoom(zoom); + + ImGui::DragFloat("Rotation", &rotate2D, 0.5f); + SetRotate2D(rotate2D); + } + ImGui::End(); } diff --git a/Engine/source/Engine.cpp b/Engine/source/Engine.cpp index 879aea38..6c59f1dc 100644 --- a/Engine/source/Engine.cpp +++ b/Engine/source/Engine.cpp @@ -63,7 +63,9 @@ void Engine::Update() threadManager.ProcessEvents(); timer.ResetLastTimeStamp(); frameCount++; - if (frameCount >= static_cast(timer.GetFrameRate())) + + // if (frameCount >= static_cast(timer.GetFrameRate())) + if (timer.GetFrameRateCalculateTime() >= 1.f) //frame rate history update every 1 seconds { timer.AddFrameHistory(frameCount / timer.GetFrameRateCalculateTime()); timer.ResetFPSCalculateTime(); @@ -98,10 +100,13 @@ void Engine::SetFPS(FrameRate fps) { ResetDeltaTime(); timer.Init(fps); + frameCount = 0; } void Engine::ResetDeltaTime() { timer.Reset(); + timer.ResetFPSCalculateTime(); deltaTime = 0.f; + frameCount = 0; } \ No newline at end of file diff --git a/Engine/source/GameStateManager.cpp b/Engine/source/GameStateManager.cpp index 688a4cae..05466446 100644 --- a/Engine/source/GameStateManager.cpp +++ b/Engine/source/GameStateManager.cpp @@ -176,10 +176,11 @@ void GameStateManager::UpdateGameLogic(float dt) Engine::GetObjectManager().Update(dt); Engine::GetParticleManager().Update(dt); Engine::GetCameraManager().Update(); - CollideObjects(); + Engine::GetPhysicsManager().Update(dt); Engine::GetSpriteManager().Update(dt); } } + void GameStateManager::UpdateDraw(float dt) { if (!(SDL_GetWindowFlags(Engine::GetWindow().GetWindow()) & SDL_WINDOW_MINIMIZED)) @@ -379,23 +380,4 @@ const char* GameStateManager::GameLevelTypeEnumToChar(GameLevel type) return "NONE"; } -void GameStateManager::CollideObjects() -{ - for (auto& target : Engine::GetObjectManager().GetObjectMap()) - { - for (auto& object : Engine::GetObjectManager().GetObjectMap()) - { - if (target.second != nullptr && object.second != nullptr && target.second != object.second) - { - if (target.second->HasComponent() == true && object.second->HasComponent() == true) - { - target.second->CollideObject(object.second.get()); - } - else if (target.second->HasComponent() == true && object.second->HasComponent() == true) - { - target.second->CollideObject(object.second.get()); - } - } - } - } -} + diff --git a/Engine/source/ObjectManager.cpp b/Engine/source/ObjectManager.cpp index dd2f4d8d..3642dee7 100644 --- a/Engine/source/ObjectManager.cpp +++ b/Engine/source/ObjectManager.cpp @@ -9,6 +9,7 @@ #include "VKRenderManager.hpp" #include "DXRenderManager.hpp" +#include "BasicComponents/Physics2D.hpp" #include "BasicComponents/Physics3D.hpp" #include "BasicComponents/Light.hpp" #include "BasicComponents/SkeletalAnimator.hpp" @@ -194,6 +195,9 @@ void ObjectManager::ObjectControllerForImGui() case ComponentTypes::PHYSICS3D: Physics3DControllerForImGui(reinterpret_cast(comp)); break; + case ComponentTypes::PHYSICS2D: + Physics2DControllerForImGui(reinterpret_cast(comp)); + break; case ComponentTypes::LIGHT: LightControllerForImGui(reinterpret_cast(comp)); break; @@ -250,6 +254,10 @@ void ObjectManager::ProcessFunctionQueue() void ObjectManager::Physics3DControllerForImGui(Physics3D* phy) { + if (!phy) + { + return; + } Physics3D* physics = phy; if (ImGui::CollapsingHeader("Physics3D", ImGuiTreeNodeFlags_DefaultOpen)) { @@ -257,6 +265,12 @@ void ObjectManager::Physics3DControllerForImGui(Physics3D* phy) ImGui::Checkbox("Use Gravity", &isGravityOn); physics->SetGravity(physics->GetGravity(), isGravityOn); + bool enableRotational = physics->GetEnableRotationalPhysics(); + if (ImGui::Checkbox("Enable Rotational Physics", &enableRotational)) + { + physics->SetEnableRotationalPhysics(enableRotational); + } + glm::vec3 velocity = physics->GetVelocity(); ImGui::DragFloat3("Velocity", &velocity.x, 0.01f); physics->SetVelocity(velocity); @@ -281,6 +295,13 @@ void ObjectManager::Physics3DControllerForImGui(Physics3D* phy) ImGui::DragFloat("Mass", &mass, 0.01f); physics->SetMass(mass); + float inertia = physics->GetMomentOfInertia(); + ImGui::DragFloat("Moment Of Inertia", &inertia, 0.1f, 0.0f, 1000.0f); + if (inertia != physics->GetMomentOfInertia()) + { + physics->SetMomentOfInertia(inertia); + } + ImGui::SeparatorText("Collision"); int mode_int = static_cast(phy->GetCollisionDetectionMode()); @@ -318,8 +339,87 @@ void ObjectManager::Physics3DControllerForImGui(Physics3D* phy) physics = nullptr; } +void ObjectManager::Physics2DControllerForImGui(Physics2D* phy) +{ + if (!phy) + { + return; + } + Physics2D* physics = phy; + if (ImGui::CollapsingHeader("Physics2D", ImGuiTreeNodeFlags_DefaultOpen)) + { + bool isGravityOn = physics->GetIsGravityOn(); + ImGui::Checkbox("Use Gravity", &isGravityOn); + physics->SetGravity(physics->GetGravity(), isGravityOn); + + bool enableRotational = physics->GetEnableRotationalPhysics(); + if (ImGui::Checkbox("Enable Rotational Physics", &enableRotational)) + { + physics->SetEnableRotationalPhysics(enableRotational); + } + + glm::vec2 velocity = physics->GetVelocity(); + ImGui::DragFloat2("Velocity", &velocity.x, 0.01f); + physics->SetVelocity(velocity); + + glm::vec2 minVelocity = physics->GetMinVelocity(); + ImGui::DragFloat2("Min Velocity", &minVelocity.x, 0.01f); + physics->SetMinVelocity(minVelocity); + + glm::vec2 maxVelocity = physics->GetMaxVelocity(); + ImGui::DragFloat2("Max Velocity", &maxVelocity.x, 0.01f); + physics->SetMaxVelocity(maxVelocity); + + float gravity = physics->GetGravity(); + ImGui::DragFloat("Gravity", &gravity, 0.01f); + physics->SetGravity(gravity, isGravityOn); + + float friction = physics->GetFriction(); + ImGui::DragFloat("Friction", &friction, 0.01f); + physics->SetFriction(friction); + + float mass = physics->GetMass(); + ImGui::DragFloat("Mass", &mass, 0.01f); + physics->SetMass(mass); + + float restitution = physics->GetRestitution(); + ImGui::DragFloat("Restitution", &restitution, 0.01f); + physics->SetRestitution(restitution); + + float inertia = physics->GetMomentOfInertia(); + ImGui::DragFloat("Moment Of Inertia", &inertia, 0.1f, 0.0f, 1000.0f); + if (inertia != physics->GetMomentOfInertia()) + { + physics->SetMomentOfInertia(inertia); + } + + if (ImGui::RadioButton("RIGID", physics->GetBodyType() == BodyType::RIGID)) + { + physics->SetBodyType(BodyType::RIGID); + } + ImGui::SameLine(); + if (ImGui::RadioButton("BLOCK", physics->GetBodyType() == BodyType::BLOCK)) + { + physics->SetBodyType(BodyType::BLOCK); + } + + ImGui::SeparatorText("Debug"); + ImGui::Checkbox("Show Physics Debug", &isShowPhysics); + + if (isShowPhysics) + { + RenderPhysics2DDebug(physics); + } + } + physics = nullptr; +} + void ObjectManager::SpriteControllerForImGui(DynamicSprite* sprite) { + if (!sprite) + { + return; + } RenderManager* renderManager = Engine::GetRenderManager(); DynamicSprite* spriteComp = sprite; bool isSelectedObjModel = false; @@ -599,6 +699,10 @@ void ObjectManager::SpriteControllerForImGui(DynamicSprite* sprite) void ObjectManager::LightControllerForImGui(Light* light) { + if (!light) + { + return; + } if (light->GetLightType() != LightType::NONE) { glm::vec3 position = light->GetPosition(); @@ -721,7 +825,10 @@ void ObjectManager::LightControllerForImGui(Light* light) void ObjectManager::SkeletalAnimatorControllerForImGui(SkeletalAnimator* animator) { - if (!animator) return; + if (!animator) + { + return; + } if (ImGui::CollapsingHeader("Skeletal Animator")) { @@ -876,7 +983,10 @@ void ObjectManager::SkeletalAnimatorControllerForImGui(SkeletalAnimator* animato void ObjectManager::AnimationStateMachineControllerForImGui(SkeletalAnimationStateMachine* fsm) { - if (!fsm) return; + if (!fsm) + { + return; + } ImGui::Text("Animation State Machine"); ImGui::Spacing(); @@ -1289,7 +1399,10 @@ void ObjectManager::SelectObjModelPopUpForImGui() void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) { - if (!phy) return; + if (!phy) + { + return; + } Object* obj = phy->GetOwner(); if (!obj) return; @@ -1301,9 +1414,12 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) ImU32 color; switch (phy->GetBodyType()) { - case BodyType3D::RIGID: color = IM_COL32(0, 255, 0, 255); break; // Green - case BodyType3D::BLOCK: color = IM_COL32(255, 0, 0, 255); break; // Red - default: color = IM_COL32(255, 255, 0, 255); break; // Yellow + case BodyType3D::RIGID: color = IM_COL32(0, 255, 0, 255); + break; + case BodyType3D::BLOCK: color = IM_COL32(255, 0, 0, 255); + break; + default: color = IM_COL32(255, 255, 0, 255); + break; } if (phy->GetColliderType() == ColliderType3D::SPHERE) @@ -1318,7 +1434,7 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) glm::vec2 firstScreenPos; for (int i = 0; i <= segments; ++i) { - float theta = (2.0f * 3.14159265359f * i) / segments; + 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); @@ -1380,3 +1496,84 @@ void ObjectManager::RenderPhysics3DDebug(Physics3D* phy) } } } + +void ObjectManager::RenderPhysics2DDebug(Physics2D* phy) +{ + if (!phy) return; + Object* obj = phy->GetOwner(); + if (!obj) return; + + glm::mat4 view = Engine::GetCameraManager().GetViewMatrix(); + glm::mat4 proj = Engine::GetCameraManager().GetProjectionMatrix(); + ImDrawList* drawList = ImGui::GetBackgroundDrawList(); + + ImU32 color; + switch (phy->GetBodyType()) + { + case BodyType::RIGID: color = IM_COL32(0, 255, 0, 255); + break; + case BodyType::BLOCK: color = IM_COL32(255, 0, 0, 255); + break; + default: color = IM_COL32(255, 255, 0, 255); + break; + } + + glm::vec2 objPos = obj->GetPosition(); + // Apply the engine's unconventional 2D position scaling (2.0f) to match DynamicSprite and Camera logic + glm::vec2 scaledPos = objPos * 2.0f; + + float objRotVal = obj->GetRotate(); + // Handle graphics API specific rotation direction (GL typically negates the angle) + if (Engine::GetRenderManager()->GetGraphicsMode() == GraphicsMode::GL) + { + objRotVal = -objRotVal; + } + float objRotRad = objRotVal * PI / 180.f; + + if (phy->GetCollideType() == CollideType::POLYGON) + { + const auto& localPoints = phy->GetCollidePolygon(); + if (localPoints.empty()) + { + return; + } + + std::vector screenPoints; + for (const auto& localPt : localPoints) + { + // Scale the local point by 2 to match the engine's 2D rendering scale + glm::vec2 scaledLocalPt = localPt * 2.0f; + + // Rotate and scale local point + float cosTheta = cos(objRotRad); + 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); + screenPoints.push_back(screenPt); + } + + for (size_t i = 0; i < screenPoints.size(); ++i) + { + glm::vec2 p1 = screenPoints[i]; + glm::vec2 p2 = screenPoints[(i + 1) % screenPoints.size()]; + if (p1.x >= 0 && p1.y >= 0 && p2.x >= 0 && p2.y >= 0) + { + drawList->AddLine(ImVec2(p1.x, p1.y), ImVec2(p2.x, p2.y), color, 2.0f); + } + } + } + else if (phy->GetCollideType() == CollideType::CIRCLE) + { + // 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); + + float screenRadius = glm::distance(screenCenter, screenEdge); + if (screenCenter.x >= 0 && screenCenter.y >= 0) + { + drawList->AddCircle(ImVec2(screenCenter.x, screenCenter.y), screenRadius, color, 32, 2.0f); + } + } +} diff --git a/Engine/source/Particle/Particle.cpp b/Engine/source/Particle/Particle.cpp index 33a4944a..d9c60efd 100644 --- a/Engine/source/Particle/Particle.cpp +++ b/Engine/source/Particle/Particle.cpp @@ -8,7 +8,7 @@ Particle::Particle(glm::vec3 position_, glm::vec3 size_, glm::vec3 speed_, float { position = position_; size = size_; - speed = speed_; + velocity = speed_; angle.z = angle_; this->lifeTime = lifeTime_; @@ -41,7 +41,7 @@ Particle::Particle(glm::vec3 position_, glm::vec3 size_, glm::vec3 speed_, glm:: { position = position_; size = size_; - speed = speed_; + velocity = speed_; angle = angle_; this->lifeTime = lifeTime_; @@ -76,9 +76,18 @@ Particle::~Particle() void Particle::Update(float dt) { - position.x += speed.x * dt; - position.y += speed.y * dt; - pPhysics.UpdateForParticle(dt, position); + // Self-contained physics integration (no Physics2D dependency). + if (useGravity) + { + velocity.y -= gravity * dt; + } + if (drag > 0.f) + { + velocity *= (1.f - drag * dt); + } + + position += velocity * dt; + switch (effect) { case ParticleEffect::NORMAL: diff --git a/Engine/source/PhysicsManager.cpp b/Engine/source/PhysicsManager.cpp new file mode 100644 index 00000000..c60e7261 --- /dev/null +++ b/Engine/source/PhysicsManager.cpp @@ -0,0 +1,454 @@ +//Author: DOYEONG LEE +//Project: CubeEngine +//File: PhysicsManager.cpp + +#include "PhysicsManager.hpp" +#include "Engine.hpp" + +#include +#include +#include + +void PhysicsManager::Update(float dt) +{ + // Execute both 2D and 3D physics pipelines + UpdatePhysics2D(dt); + UpdatePhysics3D(dt); +} + +void PhysicsManager::SetCollisionMode(ObjectType typeA, ObjectType typeB, CollisionMode mode) +{ + if (typeA > typeB) + { + std::swap(typeA, typeB); + } + collisionMaskMap[{typeA, typeB}] = mode; +} + +CollisionMode PhysicsManager::GetCollisionMode(ObjectType typeA, ObjectType typeB) +{ + if (typeA > typeB) + { + std::swap(typeA, typeB); + } + auto it = collisionMaskMap.find({typeA, typeB}); + if (it != collisionMaskMap.end()) + { + return it->second; + } + return CollisionMode::COLLIDE; +} + +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); + } +} + +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); + } +} + +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); + } +} + +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); + } +} + +void PhysicsManager::UpdatePhysics2D(float dt) +{ + if (bodies2D.empty()) + { + return; + } + + // Step 1: Move objects based on their current velocity + ApplyMovement2D(dt); + + // Step 2: Broad Phase - Quickly filter out objects that are far apart + auto collisionPairs = BroadPhase2D(); + + // Step 3: Narrow Phase - Perform detailed collision checks on potential pairs + NarrowPhase2D(collisionPairs, dt); + + // Step 4: Notify objects about potential collisions + for (auto& pair : collisionPairs) + { + Object* objectA = pair.bodyA->GetOwner(); + Object* objectB = pair.bodyB->GetOwner(); + + if (objectA != nullptr && objectB != nullptr) + { + objectA->CollideObject(objectB); + objectB->CollideObject(objectA); + } + } +} + +void PhysicsManager::ApplyMovement2D(float dt) +{ + // Update owner positions using linear velocity + for (Physics2D* body : bodies2D) + { + 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); + } +} + + + +std::vector PhysicsManager::BroadPhase2D() +{ + std::vector pairs; + + // Helper lambda to calculate the Axis-Aligned Bounding Box (AABB) + auto getAabb2D = [](Physics2D* body, glm::vec2& outMin, glm::vec2& outMax) + { + Object* owner = body->GetOwner(); + glm::vec3 currentPosition = owner->GetPosition(); + + if (body->GetCollideType() == CollideType::CIRCLE) + { + // For circles, AABB is center +/- radius + float radius = body->GetCircleCollideRadius(); + outMin = { currentPosition.x - radius, currentPosition.y - radius }; + outMax = { currentPosition.x + radius, currentPosition.y + radius }; + } + else + { + // For polygons, calculate the bounding box based on rotated vertices + outMin = { INFINITY, INFINITY }; + outMax = { -INFINITY, -INFINITY }; + + float angleDegrees = owner->GetRotate(); + float angleRadians = angleDegrees * 3.14159265f / 180.f; + float cosAngle = std::cos(angleRadians); + float sinAngle = std::sin(angleRadians); + + 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); + } + } + }; + + // Double-loop check for AABB overlaps + for (size_t i = 0; i < bodies2D.size(); ++i) + { + glm::vec2 minA, maxA; + getAabb2D(bodies2D[i], minA, maxA); + + for (size_t j = i + 1; j < bodies2D.size(); ++j) + { + glm::vec2 minB, maxB; + getAabb2D(bodies2D[j], minB, maxB); + + // AABB Overlap test + if (maxA.x < minB.x || maxB.x < minA.x) + { + continue; + } + if (maxA.y < minB.y || maxB.y < minA.y) + { + continue; + } + + pairs.push_back({ bodies2D[i], bodies2D[j] }); + } + } + return pairs; +} + + + +void PhysicsManager::NarrowPhase2D(std::vector& pairs, float /*dt*/) +{ + auto it = pairs.begin(); + while (it != pairs.end()) + { + Physics2D* bodyA = it->bodyA; + Physics2D* bodyB = it->bodyB; + bool isColliding = false; + + if (bodyA != nullptr && bodyB != nullptr) + { + Object* objectA = bodyA->GetOwner(); + Object* objectB = bodyB->GetOwner(); + + if (objectA != nullptr && objectB != nullptr) + { + CollisionMode currentMode = GetCollisionMode(objectA->GetObjectType(), objectB->GetObjectType()); + + if (currentMode == CollisionMode::IGNORED) + { + it = pairs.erase(it); + continue; + } + + CollideType typeA = bodyA->GetCollideType(); + CollideType typeB = bodyB->GetCollideType(); + + // Perform SAT and distance checks, receiving results as bools. + if (typeA == CollideType::POLYGON && typeB == CollideType::POLYGON) + { + isColliding = bodyA->CollisionPP(objectA, objectB, currentMode); + } + else if (typeA == CollideType::CIRCLE && typeB == CollideType::CIRCLE) + { + isColliding = bodyA->CollisionCC(objectA, objectB, currentMode); + } + else if (typeA == CollideType::POLYGON && typeB == CollideType::CIRCLE) + { + isColliding = bodyA->CollisionPC(objectA, objectB, currentMode); + } + else if (typeA == CollideType::CIRCLE && typeB == CollideType::POLYGON) + { + isColliding = bodyB->CollisionPC(objectB, objectA, currentMode); + } + } + } + + // If the SAT narrow phase check determines that the objects are not actually colliding, + // we remove the pair from the list to eliminate false positives from the broad phase AABB check. + if (isColliding) + { + ++it; + } + else + { + it = pairs.erase(it); + } + } +} + +void PhysicsManager::UpdatePhysics3D(float dt) +{ + if (bodies3D.empty()) + { + return; + } + + // Step 1: Linear/Angular integration + Integrate3D(dt); + + // Step 2: 3D Broad Phase + auto collisionPairs = BroadPhase3D(dt); + + // Step 3: 3D Narrow Phase + NarrowPhase3D(collisionPairs, dt); + + for (auto& pair : collisionPairs) + { + Object* objectA = pair.bodyA->GetOwner(); + Object* objectB = pair.bodyB->GetOwner(); + + if (objectA != nullptr && objectB != nullptr) + { + objectA->CollideObject(objectB); + objectB->CollideObject(objectA); + } + } +} + +void PhysicsManager::Integrate3D(float dt) +{ + // Delegate integration to each 3D physics body + for (Physics3D* body : bodies3D) + { + body->UpdatePhysics(dt); + } +} + +std::vector PhysicsManager::BroadPhase3D(float dt) +{ + 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; +} + +void PhysicsManager::NarrowPhase3D(std::vector& pairs, float dt) +{ + for (auto& pair : pairs) + { + Physics3D* bodyA = pair.bodyA; + Physics3D* bodyB = pair.bodyB; + + if (bodyA == nullptr || bodyB == nullptr) + { + continue; + } + + CollisionMode currentMode = GetCollisionMode(bodyA->GetOwner()->GetObjectType(), bodyB->GetOwner()->GetObjectType()); + + if (currentMode == CollisionMode::IGNORED) + { + continue; + } + + ColliderType3D typeA = bodyA->GetColliderType(); + ColliderType3D typeB = bodyB->GetColliderType(); + + // Resolve continuous collision sub-steps if enabled + if (bodyA->GetCollisionDetectionMode() == CollisionDetectionMode::CONTINUOUS) + { + SolveContinuous(bodyA, dt); + } + if (bodyB->GetCollisionDetectionMode() == CollisionDetectionMode::CONTINUOUS) + { + SolveContinuous(bodyB, dt); + } + + // Discrete collision dispatch + if (typeA == ColliderType3D::BOX && typeB == ColliderType3D::BOX) + { + bodyA->CollisionPP(bodyA->GetOwner(), bodyB->GetOwner(), currentMode); + } + else if (typeA == ColliderType3D::SPHERE && typeB == ColliderType3D::SPHERE) + { + bodyA->CollisionSS(bodyA->GetOwner(), bodyB->GetOwner(), currentMode); + } + else if (typeA == ColliderType3D::BOX && typeB == ColliderType3D::SPHERE) + { + bodyA->CollisionPS(bodyA->GetOwner(), bodyB->GetOwner(), currentMode); + } + else if (typeA == ColliderType3D::SPHERE && typeB == ColliderType3D::BOX) + { + bodyB->CollisionPS(bodyB->GetOwner(), bodyA->GetOwner(), currentMode); + } + } +} + +void PhysicsManager::SolveContinuous(Physics3D* body, float dt) +{ + float remainingTime = dt; + int iterationCount = 0; + + // User-defined or engine constants for safety + const int maxIterations = 5; + const float skinWidth = 0.005f; + + // Sub-stepping loop for CCD resolution + while (remainingTime > 0.0f && iterationCount < maxIterations) + { + CollisionResult collisionResult = body->FindClosestCollision(remainingTime); + + // If no hit occurred within this frame slice + 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 + 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 + Physics3D* otherBody = collisionResult.otherObject->GetComponent(); + if (otherBody != nullptr) + { + glm::vec3 contactPoint = body->GetPosition() - collisionResult.collisionNormal * 0.1f; + body->CalculateLinearVelocity(*body, *otherBody, collisionResult.collisionNormal, nullptr, contactPoint); + } + + // Subtract the consumed time and loop again to handle potential secondary collisions + remainingTime -= remainingTime * moveFraction; + iterationCount++; + } +} \ No newline at end of file diff --git a/Game/include/3DPhysicsDemo.hpp b/Game/include/3DPhysicsDemo.hpp index e87af6c5..e34def3a 100644 --- a/Game/include/3DPhysicsDemo.hpp +++ b/Game/include/3DPhysicsDemo.hpp @@ -9,6 +9,12 @@ #include "Material.hpp" +enum class PhysicsMode +{ + TwoDimension, + ThreeDimension +}; + class PhysicsDemo : public GameState { public: @@ -20,6 +26,16 @@ class PhysicsDemo : public GameState void ImGuiDraw(float dt) override; void Restart() override; void End() override; + + void Init3D(); + void Init2D(); + + void Spawn3D(); + void Spawn2D(); + + void ClearScene(); + private: ThreeDimension::PointLightUniform l; + PhysicsMode mode = PhysicsMode::ThreeDimension; }; diff --git a/Game/source/3DPhysicsDemo.cpp b/Game/source/3DPhysicsDemo.cpp index 9f737209..598b7143 100644 --- a/Game/source/3DPhysicsDemo.cpp +++ b/Game/source/3DPhysicsDemo.cpp @@ -2,13 +2,27 @@ //Project: CubeEngine //File: 3DPhysicsDemo.cpp #include "3DPhysicsDemo.hpp" +#include "BasicComponents/Physics2D.hpp" #include "BasicComponents/Physics3D.hpp" #include "BasicComponents/Light.hpp" #include "Engine.hpp" +#include #include void PhysicsDemo::Init() +{ + if (mode == PhysicsMode::ThreeDimension) + { + Init3D(); + } + else + { + Init2D(); + } +} + +void PhysicsDemo::Init3D() { Engine::GetRenderManager()->SetRenderType(RenderType::ThreeDimension); Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::ThreeDimension, 1.f); @@ -19,30 +33,13 @@ void PhysicsDemo::Init() Engine::GetCameraManager().SetCameraPosition({ 0.f,2.f,13.f }); Engine::GetCameraManager().SetTarget(glm::vec3{ 0.f, 0.f,0.f }); - //// 1. ٴ Ŵ (DISCRETE ) - //Engine::GetObjectManager().AddObject(glm::vec3{ 0.f, -10.f, 0.f }, glm::vec3{ 10.f, 10.f, 10.f }, "STATIC_SPHERE", ObjectType::NONE); - //Engine::GetObjectManager().GetLastObject()->AddComponent(); - //Engine::GetObjectManager().GetLastObject()->GetComponent()->AddMesh3D(MeshType::OBJ, "../Game/assets/Models/sphere.obj", 1, 1, { 0.8f, 0.8f, 0.8f, 1.0f }); - //Engine::GetObjectManager().GetLastObject()->AddComponent(); - //Engine::GetObjectManager().GetLastObject()->GetComponent()->SetBodyType(BodyType3D::BLOCK); // ü - //Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollideSphere(10.f); // 10 - - //// 2. ׽Ʈ (CONTINUOUS ) - //Engine::GetObjectManager().AddObject(glm::vec3{ 0.1f, 5.f, 0.f }, glm::vec3{ 1.f, 1.f, 1.f }, "MOVING_SPHERE", ObjectType::NONE); - //Engine::GetObjectManager().GetLastObject()->AddComponent(); - //Engine::GetObjectManager().GetLastObject()->GetComponent()->AddMesh3D(MeshType::OBJ, "../Game/assets/Models/sphere.obj", 1, 1, { 0.0, 0.0, 1.0, 1.0 }); - //Engine::GetObjectManager().GetLastObject()->AddComponent(); - //Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollideSphere(1.f); // 1 - //Engine::GetObjectManager().GetLastObject()->GetComponent()->SetGravity(10.f); // ϸ ߷ - //Engine::GetObjectManager().GetLastObject()->GetComponent()->SetCollisionDetectionMode(CollisionDetectionMode::CONTINUOUS); - 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); Engine::GetObjectManager().GetLastObject()->AddComponent(); Engine::GetObjectManager().GetLastObject()->GetComponent()->AddMesh3D(MeshType::PLANE, "", 2, 2, { 0.0, 0.8f, 0.0, 1.0 }, 0.f, 0.f); Engine::GetObjectManager().GetLastObject()->AddComponent(); Engine::GetObjectManager().GetLastObject()->GetComponent()->SetBodyType(BodyType3D::BLOCK); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollidePolyhedronAABB({ 20.f,20.f,0.001f }); + Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollidePolyhedronAABB({ 20.f,20.f,1.f }); Engine::GetObjectManager().AddObject(glm::vec3{ 0.f,0.f,3.f }, glm::vec3{ 1.f,1.f,1.f }, "CUBE", ObjectType::NONE); Engine::GetObjectManager().GetLastObject()->AddComponent(); @@ -52,35 +49,6 @@ void PhysicsDemo::Init() Engine::GetObjectManager().GetLastObject()->GetComponent()->SetGravity(2.f); Engine::GetObjectManager().GetLastObject()->GetComponent()->SetCollisionDetectionMode(CollisionDetectionMode::CONTINUOUS); - Engine::GetObjectManager().AddObject(glm::vec3{ 2.0f,0.f,0.f }, glm::vec3{ 1.f,1.f,1.f }, "CUBE1", ObjectType::NONE); - Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddMesh3D(MeshType::OBJ, "../Game/assets/Models/cube.obj", 1, 1, { 0.0, 0.0, 1.0, 1.0 }, 0.5f, 0.5f); - Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollidePolyhedronAABB({ 1.f,1.f,1.f }); - Engine::GetObjectManager().GetLastObject()->GetComponent()->SetGravity(4.f); - - Engine::GetObjectManager().AddObject(glm::vec3{ 1.0f,0.f,1.f }, glm::vec3{ 1.f,1.f,1.f }, "CUBE2COn", ObjectType::NONE); - Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddMesh3D(MeshType::OBJ, "../Game/assets/Models/cube.obj", 1, 1, { 0.0, 0.0, 1.0, 1.0 }, 0.5f, 0.5f); - Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollidePolyhedronAABB({ 1.f,1.f,1.f }); - Engine::GetObjectManager().GetLastObject()->GetComponent()->SetGravity(4.f); - Engine::GetObjectManager().GetLastObject()->GetComponent()->SetCollisionDetectionMode(CollisionDetectionMode::CONTINUOUS); - - Engine::GetObjectManager().AddObject(glm::vec3{ -1.0f,0.f,-1.f }, glm::vec3{ 0.5f,0.5f,0.5f }, "CUBE3", ObjectType::NONE); - Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddMesh3D(MeshType::OBJ, "../Game/assets/Models/cube.obj", 1, 1, { 0.0, 0.0, 1.0, 1.0 }, 0.5f, 0.5f); - Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollidePolyhedronAABB({ 0.5f,0.5f,0.5f }); - Engine::GetObjectManager().GetLastObject()->GetComponent()->SetGravity(2.f); - - Engine::GetObjectManager().AddObject(glm::vec3{ -1.0f,0.f,-2.f }, glm::vec3{ 0.5f,0.5f,0.5f }, "SPHERE", ObjectType::NONE); - Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddMesh3D(MeshType::OBJ, "../Game/assets/Models/sphere.obj", 1, 1, {0.0, 0.0, 1.0, 1.0}, 0.5f, 0.5f); - Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollideSphere(0.5f); - Engine::GetObjectManager().GetLastObject()->GetComponent()->SetGravity(4.f); - Engine::GetObjectManager().AddObject(glm::vec3(0.f, 0.5f, 0.f), glm::vec3{ 0.1f,0.1f,0.1f }, "LIGHT", ObjectType::NONE); Engine::GetObjectManager().GetLastObject()->AddComponent(); Engine::GetObjectManager().GetLastObject()->GetComponent()->AddLight(LightType::POINT, 25.f, 100.f); @@ -89,53 +57,101 @@ void PhysicsDemo::Init() Engine::GetRenderManager()->LoadSkybox("../Game/assets/Skybox/HDR/snowy_forest_path_02_4k.hdr"); } +void PhysicsDemo::Init2D() +{ + Engine::GetRenderManager()->SetRenderType(RenderType::TwoDimension); + Engine::GetCameraManager().Init(Engine::GetWindow().GetWindowSize(), CameraType::TwoDimension, 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); + Object* floor = Engine::GetObjectManager().GetLastObject(); + floor->AddComponent(); + floor->GetComponent()->AddQuad({ 0.5f, 0.5f, 0.5f, 1.0f }); + floor->AddComponent(); + floor->GetComponent()->AddCollidePolygonAABB(glm::vec2(1280.f, 50.f)); + floor->GetComponent()->SetBodyType(BodyType::BLOCK); +} + +void PhysicsDemo::ClearScene() +{ + Engine::GetCameraManager().Reset(); + Engine::GetParticleManager().Clear(); + Engine::GetObjectManager().End(); + if (mode == PhysicsMode::ThreeDimension) + { + Engine::GetRenderManager()->DeletePointLights(); + Engine::GetRenderManager()->DeleteDirectionalLights(); + Engine::GetRenderManager()->DeleteSkybox(); + } +} + void PhysicsDemo::Update(float dt) { - //Engine::GetRenderManager()->SetPolygonType(PolygonType::LINE); - for (auto& target : Engine::GetObjectManager().GetObjectMap()) { - for (auto& object : Engine::GetObjectManager().GetObjectMap()) + if (mode == PhysicsMode::ThreeDimension) { - if (target.second != nullptr && object.second != nullptr && target.second != object.second) + glm::vec3 movement(0.0f); + float speed = 200.0f * dt; + + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::UP)) + { + movement += Engine::GetCameraManager().GetBackVector() * speed; + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::DOWN)) { - if (target.second->HasComponent() == true && object.second->HasComponent() == true) + movement -= Engine::GetCameraManager().GetBackVector() * speed; + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::RIGHT)) + { + movement += Engine::GetCameraManager().GetRightVector() * speed; + } + if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LEFT)) + { + movement -= Engine::GetCameraManager().GetRightVector() * speed; + } + + + for (auto& target : Engine::GetObjectManager().GetObjectMap()) + { + for (auto& object : Engine::GetObjectManager().GetObjectMap()) { - if (target.second->GetComponent()->CheckCollision(object.second.get())) + if (target.second != nullptr && object.second != nullptr && target.second != object.second) { - //std::cout << "!" << '\n'; + if (target.second->HasComponent() == true && object.second->HasComponent() == true) + { + target.second->GetComponent()->CheckCollision(object.second.get()); + } } } } - } - } - { - glm::vec3 movement(0.0f); - float speed = 200.0f * dt; + movement.y = 0; - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::UP)) - { - movement += Engine::GetCameraManager().GetBackVector() * speed; - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::DOWN)) - { - movement -= Engine::GetCameraManager().GetBackVector() * speed; - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::RIGHT)) - { - movement += Engine::GetCameraManager().GetRightVector() * speed; - } - if (Engine::GetInputManager().IsKeyPressed(KEYBOARDKEYS::LEFT)) - { - movement -= Engine::GetCameraManager().GetRightVector() * speed; + if (glm::length(movement) > 0) + { + movement = glm::normalize(movement) * speed; + Object* cube = Engine::GetObjectManager().FindObjectWithName("CUBE"); + if (cube != nullptr && cube->HasComponent()) + { + cube->GetComponent()->AddForce(movement); + } + } } - - movement.y = 0; - - if (glm::length(movement) > 0) + else // TwoDimension { - movement = glm::normalize(movement) * speed; - Engine::GetObjectManager().FindObjectWithName("CUBE")->GetComponent()->AddForce(movement); + for (auto& target : Engine::GetObjectManager().GetObjectMap()) + { + for (auto& object : Engine::GetObjectManager().GetObjectMap()) + { + if (target.second != nullptr && object.second != nullptr && target.second != object.second) + { + if (target.second->HasComponent() == true && object.second->HasComponent() == true) + { + target.second->GetComponent()->CheckCollision(object.second.get()); + } + } + } + } } } @@ -149,6 +165,39 @@ void PhysicsDemo::Update(float dt) void PhysicsDemo::ImGuiDraw(float /*dt*/) { + ImGui::Begin("Physics Control"); + /*if (mode == PhysicsMode::ThreeDimension) + { + if (ImGui::Button("Switch to 2D")) + { + ClearScene(); + mode = PhysicsMode::TwoDimension; + Init(); + } + } + else + { + if (ImGui::Button("Switch to 3D")) + { + ClearScene(); + mode = PhysicsMode::ThreeDimension; + Init(); + } + }*/ + + if (ImGui::Button("Spawn Object")) + { + if (mode == PhysicsMode::ThreeDimension) + { + Spawn3D(); + } + else + { + Spawn2D(); + } + } + ImGui::End(); + Engine::GetCameraManager().CameraControllerImGui(); Engine::GetObjectManager().ObjectControllerForImGui(); } @@ -163,7 +212,84 @@ void PhysicsDemo::End() Engine::GetCameraManager().Reset(); Engine::GetParticleManager().Clear(); Engine::GetObjectManager().End(); - Engine::GetRenderManager()->DeletePointLights(); - Engine::GetRenderManager()->DeleteDirectionalLights(); - Engine::GetRenderManager()->DeleteSkybox(); + if (mode == PhysicsMode::ThreeDimension) + { + Engine::GetRenderManager()->DeletePointLights(); + Engine::GetRenderManager()->DeleteDirectionalLights(); + Engine::GetRenderManager()->DeleteSkybox(); + } +} + +void PhysicsDemo::Spawn3D() +{ + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution disType(0, 1); + std::uniform_real_distribution disPosXZ(-4.0f, 4.0f); + std::uniform_real_distribution disPosY(4.0f, 8.0f); + std::uniform_real_distribution disScale(0.4f, 1.2f); + std::uniform_real_distribution disColor(0.2f, 1.0f); + std::uniform_real_distribution disRot(-180.0f, 180.0f); + + int type = disType(gen); + float s = disScale(gen); + glm::vec3 randomPos = { disPosXZ(gen), disPosY(gen), disPosXZ(gen) }; + glm::vec3 randomScale = { s, s, s }; + glm::vec4 randomColor = { disColor(gen), disColor(gen), disColor(gen), 1.0f }; + + Engine::GetObjectManager().AddObject(randomPos, randomScale, "PhysicsObj", ObjectType::NONE); + Object* newObj = Engine::GetObjectManager().GetLastObject(); + newObj->SetRotate({ disRot(gen), disRot(gen), disRot(gen) }); + + newObj->AddComponent(); + newObj->AddComponent(); + Physics3D* phys = newObj->GetComponent(); + + if (type == 0) + { + newObj->GetComponent()->AddMesh3D(MeshType::OBJ, "../Game/assets/Models/cube.obj", 1, 1, randomColor, 0.5f, 0.5f); + phys->AddCollidePolyhedronAABB(randomScale); + phys->SetEnableRotationalPhysics(true); + } + else + { + newObj->GetComponent()->AddMesh3D(MeshType::OBJ, "../Game/assets/Models/sphere.obj", 1, 1, randomColor, 0.5f, 0.5f); + phys->AddCollideSphere(s); + phys->SetRestitution(1.f); + } + + phys->SetGravity(9.8f); + phys->SetMass(s * s * s); +} + +void PhysicsDemo::Spawn2D() +{ + std::random_device rd; + std::mt19937 gen(rd()); + //std::uniform_real_distribution disPosX(-400.0f, 400.0f); + //std::uniform_real_distribution disPosY(200.0f, 400.0f); + std::uniform_real_distribution disScale(30.f, 60.f); + std::uniform_real_distribution disColor(0.2f, 1.0f); + std::uniform_real_distribution disRot(0.0f, 360.0f); + + float s = disScale(gen); + //glm::vec3 randomPos = { disPosX(gen), disPosY(gen), 0.f }; + glm::vec3 randomScale = { s, s, 1.f }; + glm::vec4 randomColor = { disColor(gen), disColor(gen), disColor(gen), 1.0f }; + + Engine::GetObjectManager().AddObject(glm::vec3(0.f, 0.f, 0.f), randomScale, "PhysicsObj2D", ObjectType::NONE); + Object* newObj = Engine::GetObjectManager().GetLastObject(); + newObj->SetRotate(disRot(gen)); + + newObj->AddComponent(); + newObj->AddComponent(); + Physics2D* phys = newObj->GetComponent(); + + newObj->GetComponent()->AddQuad(randomColor); + phys->AddCollidePolygonAABB(glm::vec2(s, s)); + phys->SetMaxVelocity(glm::vec2(1000.f, 1000.f)); + phys->SetEnableRotationalPhysics(true); + + phys->SetGravity(980.0f); + phys->SetMass(100.f); } diff --git a/Game/source/BeatEmUpDemo/BEUAttackBox.cpp b/Game/source/BeatEmUpDemo/BEUAttackBox.cpp index 444cc1b2..9befccc1 100644 --- a/Game/source/BeatEmUpDemo/BEUAttackBox.cpp +++ b/Game/source/BeatEmUpDemo/BEUAttackBox.cpp @@ -15,7 +15,7 @@ BEUAttackBox::BEUAttackBox(glm::vec3 offset_, glm::vec3 size_, std::string name, { Init(); AddComponent(); - GetComponent()->AddCollidePolygonAABB(size_ / 2.f); + GetComponent()->AddCollidePolygonAABB(size_); GetComponent()->SetIsGhostCollision(true); //AddComponent(); diff --git a/Game/source/BeatEmUpDemo/BEUEnemy.cpp b/Game/source/BeatEmUpDemo/BEUEnemy.cpp index e2fadd73..e1a451b1 100644 --- a/Game/source/BeatEmUpDemo/BEUEnemy.cpp +++ b/Game/source/BeatEmUpDemo/BEUEnemy.cpp @@ -32,7 +32,7 @@ BEUEnemy::BEUEnemy(glm::vec3 pos_, glm::vec3 size_, std::string name, BeatEmUpDe GetComponent()->SetMaxVelocity({ 10.f,50.f }); GetComponent()->SetGravity(75.0f); GetComponent()->SetFriction(0.f); - GetComponent()->AddCollidePolygonAABB({ size_.x / 4.f, size_.y / 2.f }); + GetComponent()->AddCollidePolygonAABB({ size_.x / 2.f, size_.y}); GetComponent()->SetBodyType(BodyType::RIGID); beatEmUpDemoSystem = sys; SetStateOn(BEUObjectStates::DIRECTION); @@ -133,11 +133,10 @@ void BEUEnemy::CollideObject(Object* obj) switch (obj->GetObjectType()) { case ObjectType::WALL: - GetComponent()->CheckCollision(obj); - obj->GetComponent()->CheckCollision(this); + // Resolution handled by PhysicsManager automatically break; case ObjectType::BULLET: - if (GetInvincibleState() == false && GetComponent()->CheckCollision(obj) == true && (position.z < obj->GetPosition().z + 1.f && position.z > obj->GetPosition().z - 1.f)) + if (GetInvincibleState() == false && (position.z < obj->GetPosition().z + 1.f && position.z > obj->GetPosition().z - 1.f)) { Hit(static_cast(obj)->GetDamage()); beatEmUpDemoSystem->ShowEnemyHpBarOn(hp, maxHp); diff --git a/Game/source/BeatEmUpDemo/BEUPlayer.cpp b/Game/source/BeatEmUpDemo/BEUPlayer.cpp index bfe0166e..c3805331 100644 --- a/Game/source/BeatEmUpDemo/BEUPlayer.cpp +++ b/Game/source/BeatEmUpDemo/BEUPlayer.cpp @@ -30,7 +30,7 @@ BEUPlayer::BEUPlayer(glm::vec3 pos_, glm::vec3 size_, std::string name, BeatEmUp GetComponent()->SetMaxVelocity({ 10.f,50.f }); GetComponent()->SetGravity(75.0f); GetComponent()->SetFriction(0.f); - GetComponent()->AddCollidePolygonAABB({ size_.x / 4.f, size_.y / 2.f }); + GetComponent()->AddCollidePolygonAABB({ size_.x / 2.f, size_.y }); GetComponent()->SetBodyType(BodyType::RIGID); beatEmUpDemoSystem = sys; } @@ -135,11 +135,10 @@ void BEUPlayer::CollideObject(Object* obj) switch (obj->GetObjectType()) { case ObjectType::WALL: - GetComponent()->CheckCollision(obj); - obj->GetComponent()->CheckCollision(this); + // Resolution handled by PhysicsManager automatically break; case ObjectType::ENEMYBULLET: - if (GetInvincibleState() == false && GetComponent()->CheckCollision(obj) == true && (position.z < obj->GetPosition().z + 1.f && position.z > obj->GetPosition().z - 1.f)) + if (GetInvincibleState() == false && (position.z < obj->GetPosition().z + 1.f && position.z > obj->GetPosition().z - 1.f)) { beatEmUpDemoSystem->HpDecrease(static_cast(obj)->GetDamage()); if (IsStateOn(BEUObjectStates::DIRECTION) == true && obj->GetPosition().x <= position.x) diff --git a/Game/source/BeatEmUpDemo/BeatEmUpDemoSystem.cpp b/Game/source/BeatEmUpDemo/BeatEmUpDemoSystem.cpp index b3364836..fa7b3af5 100644 --- a/Game/source/BeatEmUpDemo/BeatEmUpDemoSystem.cpp +++ b/Game/source/BeatEmUpDemo/BeatEmUpDemoSystem.cpp @@ -22,6 +22,10 @@ void BeatEmUpDemoSystem::Init() emeyHealthBar = new DynamicSprite(); emeyHealthBar->AddQuadWithTexture("hpbar", { 0.f,0.f,0.f,0.f }); emeyHealthBar->SetSpriteDrawType(SpriteDrawType::UI); + + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::PLAYER, ObjectType::ENEMY, CollisionMode::DETECT); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::PLAYER, ObjectType::WALL, CollisionMode::COLLIDE); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::ENEMY, ObjectType::ENEMY, CollisionMode::DETECT); } void BeatEmUpDemoSystem::Update(float dt) diff --git a/Game/source/PlatformDemo/PBullet.cpp b/Game/source/PlatformDemo/PBullet.cpp index 1ca9c730..56e8abe0 100644 --- a/Game/source/PlatformDemo/PBullet.cpp +++ b/Game/source/PlatformDemo/PBullet.cpp @@ -11,7 +11,7 @@ PBullet::PBullet(glm::vec3 pos_, glm::vec3 size_, std::string name) { Init(); AddComponent(); - GetComponent()->AddCollidePolygonAABB(size_ / 2.f); + GetComponent()->AddCollidePolygonAABB(size_); GetComponent()->SetIsGhostCollision(true); AddComponent(); diff --git a/Game/source/PlatformDemo/PEnemyBullet.cpp b/Game/source/PlatformDemo/PEnemyBullet.cpp index 526ba0a4..cfe86c9b 100644 --- a/Game/source/PlatformDemo/PEnemyBullet.cpp +++ b/Game/source/PlatformDemo/PEnemyBullet.cpp @@ -12,7 +12,7 @@ PEnemyBullet::PEnemyBullet(glm::vec3 pos_, glm::vec3 size_, std::string name) { Init(); AddComponent(); - GetComponent()->AddCollidePolygonAABB(size_ / 2.f); + GetComponent()->AddCollidePolygonAABB(size_); GetComponent()->SetIsGhostCollision(true); AddComponent(); diff --git a/Game/source/PlatformDemo/PlatformDemo.cpp b/Game/source/PlatformDemo/PlatformDemo.cpp index 666e7079..43060794 100644 --- a/Game/source/PlatformDemo/PlatformDemo.cpp +++ b/Game/source/PlatformDemo/PlatformDemo.cpp @@ -35,19 +35,18 @@ void PlatformDemo::Init() Engine::GetObjectManager().AddObject(glm::vec3{ -64.f,196.f,0.f }, glm::vec3{ 64.f, 96.f,0.f }, "Enemy", EnemyType::NORMAL); Engine::GetObjectManager().AddObject(glm::vec3{ 640.f,0.f,0.f }, glm::vec3{ 320.f, 320.f,0.f }, "Enemy", EnemyType::AIRSHIP); platformDemoSystem->InitHealthBar(); + + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::PLAYER, ObjectType::ENEMY, CollisionMode::DETECT); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::PLAYER, ObjectType::ENEMYBULLET, CollisionMode::DETECT); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::ENEMY, ObjectType::BULLET, CollisionMode::DETECT); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::ENEMY, ObjectType::ENEMY, CollisionMode::DETECT); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::PLAYER, ObjectType::WALL, CollisionMode::COLLIDE); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::ENEMY, ObjectType::WALL, CollisionMode::COLLIDE); } void PlatformDemo::Update(float dt) { - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::NUMBER_1)) - { - Engine::GetGameStateManager().ChangeLevel(GameLevel::POCKETBALL); - } - else if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::NUMBER_2)) - { - Engine::GetGameStateManager().ChangeLevel(GameLevel::PLATFORMDEMO); - } - else if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::R)) + if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::R)) { Engine::GetGameStateManager().SetGameState(State::RESTART); } diff --git a/Game/source/PlatformDemo/PlatformDemoSystem.cpp b/Game/source/PlatformDemo/PlatformDemoSystem.cpp index 4a2e95ea..509d89f2 100644 --- a/Game/source/PlatformDemo/PlatformDemoSystem.cpp +++ b/Game/source/PlatformDemo/PlatformDemoSystem.cpp @@ -150,7 +150,7 @@ void PDemoMapEditorDemo::LoadLevelData(const std::filesystem::path& filePath) Engine::GetObjectManager().GetLastObject()->GetComponent()->AddQuad({ 0.5f,0.5f,0.5f,1.f });*/ Engine::GetObjectManager().GetLastObject()->AddComponent(); - Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollidePolygonAABB({ Engine::GetObjectManager().GetLastObject()->GetSize().x / 2.f, Engine::GetObjectManager().GetLastObject()->GetSize().y / 2.f }); + Engine::GetObjectManager().GetLastObject()->GetComponent()->AddCollidePolygonAABB({ Engine::GetObjectManager().GetLastObject()->GetSize().x, Engine::GetObjectManager().GetLastObject()->GetSize().y}); Engine::GetObjectManager().GetLastObject()->GetComponent()->SetBodyType(BodyType::BLOCK); Engine::GetObjectManager().GetLastObject()->GetComponent()->SetMass(1.f); } @@ -161,7 +161,7 @@ void PDemoMapEditorDemo::LoadLevelData(const std::filesystem::path& filePath) temp->GetComponent()->AddQuad({ 0.f,1.f,0.f,0.25f }); temp->AddComponent(); - temp->GetComponent()->AddCollidePolygonAABB({ temp->GetSize().x / 2.f, temp->GetSize().y / 2.f }); + temp->GetComponent()->AddCollidePolygonAABB({ temp->GetSize().x, temp->GetSize().y}); temp->GetComponent()->SetBodyType(BodyType::BLOCK); temp->GetComponent()->SetMass(1.f); walls.push_back(std::move(temp)); @@ -589,7 +589,7 @@ void PDemoMapEditorDemo::WallCreator() temp->GetComponent()->AddQuad({ 0.f,1.f,0.f,0.25f }); temp->AddComponent(); - temp->GetComponent()->AddCollidePolygonAABB({ temp->GetSize().x / 2.f, temp->GetSize().y / 2.f }); + temp->GetComponent()->AddCollidePolygonAABB({ temp->GetSize().x, temp->GetSize().y}); temp->GetComponent()->SetBodyType(BodyType::BLOCK); temp->GetComponent()->SetMass(1.f); walls.push_back(std::move(temp)); diff --git a/Game/source/PocketBallDemo/Ball.cpp b/Game/source/PocketBallDemo/Ball.cpp index 0d72424c..12196e48 100644 --- a/Game/source/PocketBallDemo/Ball.cpp +++ b/Game/source/PocketBallDemo/Ball.cpp @@ -57,40 +57,42 @@ void Ball::CollideObject(Object* obj) { switch (obj->GetObjectType()) { - case ObjectType::WALL: - GetComponent()->CheckCollision(obj); - obj->GetComponent()->CheckCollision(this); - break; - case ObjectType::BALL: - GetComponent()->CheckCollision(obj); - break; - case ObjectType::GOAL: - if (obj->GetComponent()->CheckCollision(this) == true) + case ObjectType::WALL: + // Resolution handled by PhysicsManager + break; + case ObjectType::BALL: + // Resolution handled by PhysicsManager + break; + case ObjectType::GOAL: { - if (ballType != BallType::WHITE) + // Detection only for goal logic + if (true) // NarrowPhase already confirmed collision if we are here { - int time = (rand() % (3 + 1)) + 1; - int amount = (rand() % (8 + 4)) + 4; - int colorR = (rand() % (10 + 0)) + 0; - int colorG = (rand() % (10 + 0)) + 0; - int colorB = (rand() % (10 + 0)) + 0; - int colorA = (rand() % (10 + 5)) + 5; - float x = position.x; - float y = position.y; - float speedX = (float)(rand() % (15 - (-30) + 1) - 15); - float speedY = (float)(rand() % (15 - (-30) + 1) - 15); + if (ballType != BallType::WHITE) + { + int time = (rand() % (3 + 1)) + 1; + int amount = (rand() % (8 + 4)) + 4; + int colorR = (rand() % (10 + 0)) + 0; + int colorG = (rand() % (10 + 0)) + 0; + int colorB = (rand() % (10 + 0)) + 0; + int colorA = (rand() % (10 + 5)) + 5; + float x = position.x; + float y = position.y; + float speedX = (float)(rand() % (15 - (-30) + 1) - 15); + float speedY = (float)(rand() % (15 - (-30) + 1) - 15); - Engine::GetParticleManager().AddRandomParticle({ x,y,0.f }, { 4.f,4.f,0.f }, { speedX,speedY,0.f }, 0.f, static_cast(time), amount, - { static_cast(colorR * 0.1f),static_cast(colorG * 0.1f),static_cast(colorB * 0.1f),static_cast(colorA * 0.1f) }); + Engine::GetParticleManager().AddRandomParticle({ x,y,0.f }, { 4.f,4.f,0.f }, { speedX,speedY,0.f }, 0.f, static_cast(time), amount, + { static_cast(colorR * 0.1f),static_cast(colorG * 0.1f),static_cast(colorB * 0.1f),static_cast(colorA * 0.1f) }); - Engine::GetObjectManager().Destroy(Object::id); - pocketBallSystem->SetBallNum(pocketBallSystem->GetBallNum() - 1); - } - else - { - Engine::GetGameStateManager().SetGameState(State::RESTART); + Engine::GetObjectManager().Destroy(Object::id); + pocketBallSystem->SetBallNum(pocketBallSystem->GetBallNum() - 1); + } + else + { + Engine::GetGameStateManager().SetGameState(State::RESTART); + } } + break; } - break; } } diff --git a/Game/source/PocketBallDemo/PocketBallDemo.cpp b/Game/source/PocketBallDemo/PocketBallDemo.cpp index 6883b59c..fcf36d44 100644 --- a/Game/source/PocketBallDemo/PocketBallDemo.cpp +++ b/Game/source/PocketBallDemo/PocketBallDemo.cpp @@ -46,6 +46,10 @@ void PocketBallDemo::Init() pocketBallSystem->Init(); pocketBallSystem->SetBallNum(ballAmount); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::BALL, ObjectType::BALL, CollisionMode::COLLIDE); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::BALL, ObjectType::WALL, CollisionMode::COLLIDE); + Engine::GetPhysicsManager().SetCollisionMode(ObjectType::BALL, ObjectType::GOAL, CollisionMode::DETECT); + { glm::vec2 tempS{ 0.f,0.f }; Engine::GetObjectManager().AddObject(glm::vec3{ 0.f,192.f,0.f }, glm::vec3{ 640.f, 32.f,0.f }, "Wall", ObjectType::WALL); @@ -153,14 +157,6 @@ void PocketBallDemo::Init() void PocketBallDemo::Update(float dt) { - if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::NUMBER_1)) - { - Engine::GetGameStateManager().ChangeLevel(GameLevel::POCKETBALL); - } - else if (Engine::GetInputManager().IsKeyPressOnce(KEYBOARDKEYS::NUMBER_2)) - { - Engine::GetGameStateManager().ChangeLevel(GameLevel::PLATFORMDEMO); - } pocketBallSystem->Update(dt); } diff --git a/README.md b/README.md index 37387084..cfdc15a3 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,8 @@ dxc WorkGraphs.hlsl -T lib_6_8 -E broadcastNode -Fo WorkGraphs.cso | **Core Engine** | | | | | | CPU Dump Writer | ✅ | ➖ | ➖ | ➖ | | CRT Memory Leak Detector | ✅ | ➖ | ➖ | ➖ | -| Sound Manager | ✅ | ➖ | ➖ | ➖ | +| Sound Manager (FMOD) | ✅ | ➖ | ➖ | ➖ | +| Physics Manager (2D / 3D) | ✅ | ➖ | ➖ | ➖ | | Logger (CMD, CSV) | ✅ | ➖ | ➖ | ➖ | | **Rendering Features** | | | | | | 2D (Sprite Based Animation) Rendering | ➖ | ✅ | ✅ | ✅ | @@ -162,7 +163,7 @@ dxc WorkGraphs.hlsl -T lib_6_8 -E broadcastNode -Fo WorkGraphs.cso image -![image](https://github.com/user-attachments/assets/0116de70-4fcc-465d-be30-21321d67ee25) +Image ![image](https://github.com/user-attachments/assets/e7221576-5010-48b5-8a58-78bd5f197625)