From d0781db9f09c3625da9fa4613001ec3f500184d0 Mon Sep 17 00:00:00 2001 From: Bram Verhulst Date: Tue, 12 Mar 2024 12:21:17 +0100 Subject: [PATCH] Added a (temp) collision solver for Axis-Aligned rectangles --- Engine/structs.h | 2 + Engine/utils.cpp | 78 +++++++++++++++++++++++++++++ Engine/utils.h | 21 ++++++++ Game/Camera.cpp | 4 +- Game/Camera.h | 3 ++ Game/Game.cpp | 2 + Game/Level.cpp | 8 +-- Game/Level.h | 6 +-- Game/Player.cpp | 44 ++++++++++------ Game/WorldLevel.cpp | 119 ++++++++++++++++++++++++++++++++++++++++---- Game/WorldLevel.h | 3 ++ 11 files changed, 252 insertions(+), 38 deletions(-) diff --git a/Engine/structs.h b/Engine/structs.h index 0d58e25..fca5aaa 100644 --- a/Engine/structs.h +++ b/Engine/structs.h @@ -36,6 +36,8 @@ struct Rectf Rectf( ); explicit Rectf( float left, float bottom, float width, float height ); //explicit Rectf( int left, int bottom, int width, int height ); //Stupid fix for it giving an error (same as Point2f) + + Point2f BottomLeft() const { return Point2f{ left, bottom }; } float left; float bottom; diff --git a/Engine/utils.cpp b/Engine/utils.cpp index 6472313..349c025 100644 --- a/Engine/utils.cpp +++ b/Engine/utils.cpp @@ -686,9 +686,87 @@ bool utils::IntersectRectLine(const Rectf& r, const Point2f& p1, const Point2f& intersectMax = tMax; return true; } +bool utils::IsRectInRect(const Rectf& r1, const Rectf& r2) { + // the origin of both rectangles is in bottom left + return (r1.left < r2.left + r2.width && r1.left + r1.width > r2.left && r1.bottom < r2.bottom + r2.height && r1.bottom + r1.height > r2.bottom); +} +bool utils::RayVsRect(const Point2f& rayOrigin, const Point2f& rayDir, const Rectf& target, + Point2f& contactPoint, Point2f& contactNormal, float& t_hit_near) { + + Point2f t_near = Point2f{(target.BottomLeft() - rayOrigin).x / rayDir.x, (target.BottomLeft() - rayOrigin).y / rayDir.y}; + Point2f t_far = Point2f{(target.BottomLeft() + Point2f{target.width, target.height} - rayOrigin).x / rayDir.x, (target.BottomLeft() + Point2f{target.width, target.height} - rayOrigin).y / rayDir.y}; + if(std::isnan(t_far.y) || std::isnan(t_far.x)) return false; + if(std::isnan(t_near.y) || std::isnan(t_near.x)) return false; + + if (t_near.x > t_far.x) std::swap(t_near.x, t_far.x); + if (t_near.y > t_far.y) std::swap(t_near.y, t_far.y); + + if (t_near.x > t_far.y || t_near.y > t_far.x) return false; + + t_hit_near = std::max(t_near.x, t_near.y); + float t_hit_far = std::min(t_far.x, t_far.y); + + if (t_hit_far < 0) return false; + + contactPoint = rayOrigin + rayDir * t_hit_near; + + if(t_near.x > t_near.y) { + if(rayDir.x < 0) { + contactNormal = Point2f{1, 0}; + }else { + contactNormal = Point2f{-1, 0}; + } + } else if(t_near.x < t_near.y) { + if(rayDir.y < 0) { + contactNormal = Point2f{0, 1}; + }else { + contactNormal = Point2f{0, -1}; + } + } + + return true; +} + +bool utils::DynamicRectVsRect(const MovingRectf& in, const Rectf& target, Point2f& contactPoint, Point2f& contactNormal, float& contactTime, float dt) { + if(in.velocity.x == 0 && in.velocity.y == 0) return false; + + + Rectf expanded_target{}; + expanded_target.left = target.left - in.width / 2; + expanded_target.bottom = target.bottom - in.height / 2; + expanded_target.width = target.width + in.width; + expanded_target.height = target.height + in.height; + + if(RayVsRect(Point2f{in.bottomLeft.x + in.width / 2, in.bottomLeft.y + in.height / 2}, in.velocity * dt, expanded_target, contactPoint, contactNormal, contactTime)) { + if(contactTime <= 1.0f && contactTime >= 0.0f) { + return true; + } + } + return false; +} +float utils::DotProduct(const Point2f& a, const Point2f& b) { + return a.x * b.x + a.y * b.y; +} #pragma endregion CollisionFunctionality int utils::randRange(int min, int max) { return min + rand() % (( max + 1 ) - min); } +bool utils::isKeyDown(int keycode) { + const Uint8* pStates = SDL_GetKeyboardState(nullptr); + if (pStates != nullptr) + { + if(pStates[keycode]) { + return true; + } + } + return false; +} +bool utils::isMouseDown(int button) { + const Uint32 pStates = SDL_GetMouseState(nullptr, nullptr); + if (pStates & SDL_BUTTON(button)) { + return true; + } + return false; +} diff --git a/Engine/utils.h b/Engine/utils.h index 55509fd..fd7235d 100644 --- a/Engine/utils.h +++ b/Engine/utils.h @@ -6,6 +6,15 @@ namespace utils { const float g_Pi{ 3.1415926535f }; + struct MovingRectf + { + Point2f bottomLeft; + float width; + float height; + + Point2f velocity; + }; + #pragma region OpenGLDrawFunctionality void SetColor( const Color4f& color ); @@ -75,6 +84,7 @@ namespace utils bool IsOverlapping( const Circlef& c1, const Circlef& c2 ); bool IsOverlapping( const std::vector& vertices, const Circlef& c ); bool IsOverlapping( const Point2f* vertices, size_t nrVertices, const Circlef& c ); + bool Raycast( const Point2f* vertices, const size_t nrVertices, const Point2f& rayP1, const Point2f& rayP2, HitInfo& hitInfo ); bool Raycast( const std::vector& vertices, const Point2f& rayP1, const Point2f& rayP2, HitInfo& hitInfo ); @@ -82,8 +92,19 @@ namespace utils float DistPointLineSegment(const Point2f& p, const Point2f& a, const Point2f& b); bool IsPointOnLineSegment(const Point2f& p, const Point2f& a, const Point2f& b); bool IntersectRectLine(const Rectf& r, const Point2f& p1, const Point2f& p2, float& intersectMin, float& intersectMax); + bool IsRectInRect(const Rectf& r1, const Rectf& r2); + bool RayVsRect(const Point2f& rayOrigin, const Point2f& rayDir, const Rectf& target, + Point2f& contactPoint, Point2f& contactNormal, float& contactTime); + + bool DynamicRectVsRect(const MovingRectf& in, const Rectf& target, Point2f& contactPoint, Point2f& contactNormal, float& contactTime, float dt); + + float DotProduct(const Point2f& a, const Point2f& b); + int randRange(int min, int max); #pragma endregion CollisionFunctionality + bool isKeyDown(SDL_Keycode keycode); + bool isMouseDown(int button); + } \ No newline at end of file diff --git a/Game/Camera.cpp b/Game/Camera.cpp index 1a0b3d2..1d778f5 100644 --- a/Game/Camera.cpp +++ b/Game/Camera.cpp @@ -5,6 +5,8 @@ Camera::Camera() : m_Position { 0, 0 }, m_Scale { 1.0f } { } Camera::Camera(const Point2f& position, const float scale) : m_Position { position }, m_Scale { scale } { } +Camera::~Camera() { +} void Camera::BeginRendering() const { glPushMatrix(); @@ -16,6 +18,6 @@ void Camera::EndRendering() const { glPopMatrix(); } Point2f Camera::TransformMouse(const Point2f& mousePos) const { - const Point2f translatedPosition = mousePos + m_Position; + const Point2f translatedPosition = Point2f{ mousePos.x - m_Position.x, Viewport.height - mousePos.y - m_Position.y}; return translatedPosition; } diff --git a/Game/Camera.h b/Game/Camera.h index 5b8d34b..4ec06aa 100644 --- a/Game/Camera.h +++ b/Game/Camera.h @@ -5,6 +5,7 @@ class Camera public: Camera( ); Camera( const Point2f& position, float scale = 1); + virtual ~Camera(); void SetPosition( const Point2f& position ) { m_Position = position; } void SetScale( const float scale ) { m_Scale = scale; } @@ -16,6 +17,8 @@ public: void EndRendering() const; Point2f TransformMouse (const Point2f& mousePos) const; + Rectf Viewport = Rectf{ 0, 0, 846.f, 500.f }; + //TODO: Remove this and make it some static private: Point2f m_Position; diff --git a/Game/Game.cpp b/Game/Game.cpp index bf0e866..1788472 100644 --- a/Game/Game.cpp +++ b/Game/Game.cpp @@ -44,6 +44,8 @@ void Game::Update(float elapsedSec) { m_MouseOffset = m_Camera.GetPosition(); } + m_WorldLevel.Update(elapsedSec); + } diff --git a/Game/Level.cpp b/Game/Level.cpp index 2b9f4da..94e88b9 100644 --- a/Game/Level.cpp +++ b/Game/Level.cpp @@ -9,10 +9,4 @@ Level::Level(Camera* camera) { m_pCamera = camera; } Level::~Level() { -} -void Level::Update(float elapsedSec) { -} -void Level::Draw() const { -} -void Level::MouseMove(const Point2f& mousePos) { -} +} \ No newline at end of file diff --git a/Game/Level.h b/Game/Level.h index 42e9615..fb43ffd 100644 --- a/Game/Level.h +++ b/Game/Level.h @@ -8,9 +8,9 @@ public: Level(Camera* camera); virtual ~Level(); - virtual void Update(float elapsedSec); - virtual void Draw() const; - virtual void MouseMove(const Point2f& mousePos); + virtual void Update(float elapsedSec) = 0; + virtual void Draw() const = 0; + virtual void MouseMove(const Point2f& mousePos) = 0; private: diff --git a/Game/Player.cpp b/Game/Player.cpp index 9708f2d..8416ac9 100644 --- a/Game/Player.cpp +++ b/Game/Player.cpp @@ -14,11 +14,30 @@ void Player::Draw() const { } void Player::Update(float elapsedTime, const WorldLevel& level) { - Point2f acc{0, 0}; - acc.y += m_Gravity.y; + // m_Acc.y += m_Gravity.y; + // m_Vel.y = std::min(m_Vel.y, m_MaxSpeed); + // + // Point2f nextPos = m_Position + m_Vel * elapsedTime; + // //collision checking + m_Vel = Point2f{0, -100}; - Point2f nextPos = m_Position + m_Vel * elapsedTime * acc * elapsedTime * elapsedTime; - //collision checking + //check for keys + if(utils::isKeyDown(SDL_SCANCODE_W)) { + m_Vel.y = 100; + } + if(utils::isKeyDown(SDL_SCANCODE_S)) { + m_Vel.y = -100; + } + if(utils::isKeyDown(SDL_SCANCODE_A)) { + m_Vel.x = -100; + } + if(utils::isKeyDown(SDL_SCANCODE_D)) { + m_Vel.x = 100; + } + + Point2f nextPos = m_Position + m_Vel * elapsedTime; + bool isColliding = false; + auto tiles = level.GetAllTiles(); for (int x{0}; x < WorldLevel::WORLD_WIDTH; ++x) { for (int y{0}; y < WorldLevel::WORLD_HEIGHT; ++y) { @@ -26,20 +45,13 @@ void Player::Update(float elapsedTime, const WorldLevel& level) { if (tile->GetTileType() == GroundTileTypes::Dirt) { Rectf tileRect = Rectf{tile->GetPosition().x, tile->GetPosition().y, WorldLevel::TILE_WIDTH, WorldLevel::TILE_HEIGHT}; if (utils::IsOverlapping(nextPos, m_Size, tileRect)) { - //collision - if (m_Vel.y < 0) { - //collision from above - m_Position.y = tileRect.bottom; - m_Vel.y = 0; - } else { - m_Position.y = tileRect.bottom + m_Size.y; - m_Vel.y = 0; - } + isColliding = true; } } } } - m_Vel = Point2f{m_Vel.x + float(acc.x * elapsedTime), m_Vel.y + float(acc.y * elapsedTime)}; - // m_Position += m_Vel * elapsedTime; - m_Position = Point2f{m_Position.x + m_Vel.x * elapsedTime, m_Position.y + m_Vel.y * elapsedTime}; + + if(!isColliding) { + m_Position = nextPos; + } } \ No newline at end of file diff --git a/Game/WorldLevel.cpp b/Game/WorldLevel.cpp index 549e778..7499881 100644 --- a/Game/WorldLevel.cpp +++ b/Game/WorldLevel.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "WorldLevel.h" +#include #include #include @@ -16,6 +17,21 @@ WorldLevel::WorldLevel(Camera* camera) : Level(camera), m_mousePos{ 0, 0 }, m_pl m_worldTiles[x][y] = new WorldTile{ Point2f{ float(actualX * TILE_WIDTH), -float(y * TILE_HEIGHT) - TILE_HEIGHT}, GroundTileTypes::Dirt}; } } + + m_Rects.push_back(utils::MovingRectf{Point2f{20, 40}, 40, 50, Point2f{0,0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{510.0f, 210.0f}, 30.0f, 120.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{450.0f, 150.0f}, 60.0f, 60.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{450.0f, 450.0f}, 225.0f, 60.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{510.0f, 150.0f}, 60.0f, 60.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{570.0f, 150.0f}, 60.0f, 60.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{330.0f, 150.0f}, 60.0f, 60.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{150.0f, 390.0f}, 60.0f, 60.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{150.0f, 450.0f}, 60.0f, 60.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{150.0f, 510.0f}, 60.0f, 60.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{450.0f, 300.0f}, 30.0f, 3.0f, Point2f{0, 0}}); + m_Rects.push_back(utils::MovingRectf{Point2f{600.0f, 300.0f}, 60.0f, 180.0f, Point2f{0, 0}}); + + // std::string dirtPath = + "tiles/dirt/dirt" + std::to_string(utils::randRange(1, 5)) + ".png"; // m_pTextTexture = new Texture(dirtPath); @@ -28,30 +44,111 @@ void WorldLevel::Update(float elapsedSec) { int mouseX, mouseY; SDL_GetMouseState(&mouseX, &mouseY); m_mousePos = Point2f{ float(mouseX), float(mouseY) }; - m_player.Update(elapsedSec, *this); + m_mousePos = m_pCamera->TransformMouse(m_mousePos); + //m_player.Update(elapsedSec, *this); + + + Point2f RayPoint = Point2f{m_pCamera->Viewport.width, m_pCamera->Viewport.height}; + Point2f RayDir = Point2f{m_mousePos.x - RayPoint.x, m_mousePos.y - RayPoint.y}; + + //wasd movement + if(utils::isKeyDown(SDL_SCANCODE_W)) { + m_Rects[0].velocity.y += 10; + } + if(utils::isKeyDown(SDL_SCANCODE_S)) { + m_Rects[0].velocity.y += -10; + } + if(utils::isKeyDown(SDL_SCANCODE_A)) { + m_Rects[0].velocity.x += -10; + } + if(utils::isKeyDown(SDL_SCANCODE_D)) { + m_Rects[0].velocity.x += 10; + } + + Point2f intersectionPoint, normal; + float contactTime; + + std::vector> contactTimes{}; + + for (size_t i { 1 }; i < m_Rects.size(); ++i) { + Rectf target = Rectf{m_Rects[i].bottomLeft.x, m_Rects[i].bottomLeft.y, m_Rects[i].width, m_Rects[i].height}; + if(utils::DynamicRectVsRect(m_Rects[0], target, intersectionPoint, normal, contactTime, elapsedSec)) { + // m_Rects[0].velocity.x += normal.x * (1 - contactTime) * -utils::DotProduct( m_Rects[0].velocity, normal); + // m_Rects[0].velocity.y += normal.y * (1 - contactTime) * -utils::DotProduct( m_Rects[0].velocity, normal); + // + // //m_Rects[0].velocity = Point2f{0, 0}; + + contactTimes.push_back({i, contactTime}); + } + } + + std::sort(contactTimes.begin(), contactTimes.end(), [](const std::pair& a, const std::pair& b) { + return a.second < b.second; + }); + + for (std::pair contact : contactTimes) { + Rectf target = Rectf{m_Rects[contact.first].bottomLeft.x, m_Rects[contact.first].bottomLeft.y, m_Rects[contact.first].width, m_Rects[contact.first].height}; + if(utils::DynamicRectVsRect(m_Rects[0], target, intersectionPoint, normal, contactTime, elapsedSec)) { + m_Rects[0].velocity.x += normal.x * (1 - contactTime) * -utils::DotProduct( m_Rects[0].velocity, normal); + m_Rects[0].velocity.y += normal.y * (1 - contactTime) * -utils::DotProduct( m_Rects[0].velocity, normal); + } + } + + m_Rects[0].bottomLeft.x = m_Rects[0].bottomLeft.x + m_Rects[0].velocity.x * elapsedSec; + m_Rects[0].bottomLeft.y = m_Rects[0].bottomLeft.y + m_Rects[0].velocity.y * elapsedSec; + } void WorldLevel::Draw() const { m_pCamera->BeginRendering(); + for (utils::MovingRectf rect : m_Rects) { + utils::DrawRect(rect.bottomLeft, rect.width, rect.height); + } + + Rectf expanded = Rectf{ m_Rects[1].bottomLeft.x - m_Rects[0].width / 2, m_Rects[1].bottomLeft.y - m_Rects[0].height / 2, m_Rects[1].width + m_Rects[0].width, m_Rects[1].height + m_Rects[0].height }; + utils::SetColor(Colors::RED); + utils::DrawRect(expanded); + + + Point2f RayPoint = Point2f{m_pCamera->Viewport.width, m_pCamera->Viewport.height}; + Point2f RayDir = Point2f{m_mousePos.x - RayPoint.x, m_mousePos.y - RayPoint.y}; + + utils::SetColor(Colors::WHITE); + utils::DrawLine(RayPoint, m_mousePos); + + utils::FillEllipse(m_mousePos, 20, 20); + + Point2f intersectionPoint, normal; + float contactTime; + Rectf testRect = Rectf{m_Rects[1].bottomLeft.x, m_Rects[1].bottomLeft.y, m_Rects[1].width, m_Rects[1].height}; + if(utils::RayVsRect(RayPoint, RayDir, testRect, intersectionPoint, normal, contactTime) && contactTime > 0.0f && contactTime < 1.0f) { + utils::SetColor(Colors::GREEN); + utils::FillEllipse(intersectionPoint, 5, 5); + utils::DrawLine(intersectionPoint, Point2f{intersectionPoint.x + normal.x * 50, intersectionPoint.y + normal.y * 50}); + } + + + // utils::SetColor(Colors::YELLOW); + // utils::FillEllipse(m_mousePos,2,2); for (size_t x { 0 }; x < WORLD_WIDTH; ++x) { for (size_t y { 0 }; y < WORLD_HEIGHT; ++y) { m_worldTiles[x][y]->Draw(); } } - - utils::SetColor(Colors::WHITE); - for (int x { -100 }; x < 100; ++x) { - for (int y { -100 }; y < 100; ++y) { - utils::DrawLine(x * 50, -5000, x * 50, 50000); - utils::DrawLine(-5000, y * 50, 50000, y * 50); - } - } + + // utils::SetColor(Colors::WHITE); + // for (int x { -100 }; x < 100; ++x) { + // for (int y { -100 }; y < 100; ++y) { + // utils::DrawLine(x * 50, -5000, x * 50, 50000); + // utils::DrawLine(-5000, y * 50, 50000, y * 50); + // } + // } utils::SetColor(Colors::MAGENTA); - utils::FillEllipse(0, 0, 10, 10); + utils::FillEllipse(0, 0, 5, 5); - m_player.Draw(); + //m_player.Draw(); m_pCamera->EndRendering(); //utils::SetColor(Colors::WHITE); diff --git a/Game/WorldLevel.h b/Game/WorldLevel.h index 4d5b060..2f378e6 100644 --- a/Game/WorldLevel.h +++ b/Game/WorldLevel.h @@ -6,6 +6,7 @@ #include #include "Player.h" +#include "utils.h" class WorldLevel : public Level { @@ -29,6 +30,8 @@ public: std::array, WORLD_HEIGHT> GetAllTiles() const; + std::vector m_Rects; + private: