Added a (temp) collision solver for Axis-Aligned rectangles

This commit is contained in:
Bram Verhulst
2024-03-12 12:21:17 +01:00
parent e474a7815a
commit d0781db9f0
11 changed files with 252 additions and 38 deletions

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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<Point2f>& 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<Point2f>& 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);
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -44,6 +44,8 @@ void Game::Update(float elapsedSec) {
m_MouseOffset = m_Camera.GetPosition();
}
m_WorldLevel.Update(elapsedSec);
}

View File

@@ -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) {
}
}

View File

@@ -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:

View File

@@ -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;
}
}

View File

@@ -1,6 +1,7 @@
#include "pch.h"
#include "WorldLevel.h"
#include <algorithm>
#include <iostream>
#include <ostream>
@@ -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<std::pair<int, float>> 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<int, float>& a, const std::pair<int, float>& b) {
return a.second < b.second;
});
for (std::pair<int, float> 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);

View File

@@ -6,6 +6,7 @@
#include <array>
#include "Player.h"
#include "utils.h"
class WorldLevel : public Level {
@@ -29,6 +30,8 @@ public:
std::array<std::array<WorldTile*, WORLD_WIDTH>, WORLD_HEIGHT> GetAllTiles() const;
std::vector<utils::MovingRectf> m_Rects;
private: