Skip to content

Commit

Permalink
Add DetectCollision system
Browse files Browse the repository at this point in the history
This is a very basic and naive collision detection system. However for
the first game project in this engine it should be sufficient and for
more complex cases we can replace it with a more robust physics system
later.
  • Loading branch information
meisekimiu committed Sep 2, 2024
1 parent 619b12e commit 4077fe2
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 227 deletions.
11 changes: 11 additions & 0 deletions src/game/components/CollisionRadius.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef GL_ADAGIO_COLLISIONRADIUS_H
#define GL_ADAGIO_COLLISIONRADIUS_H

#include "../../math/Vector2.h"

struct CollisionRadius {
Adagio::Vector2d offset{0, 0};
double radius{0};
};

#endif //GL_ADAGIO_COLLISIONRADIUS_H
38 changes: 38 additions & 0 deletions src/game/systems/DetectCollision.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef GL_ADAGIO_DETECTCOLLISION_H
#define GL_ADAGIO_DETECTCOLLISION_H

#include "../../event/MessageDispatchService.h"
#include "../../state/GameStats.h"
#include "../../state/StateMachine.h"
#include "../components/CollisionRadius.h"
#include "entt/entt.hpp"

template<class Target, class Filter>
void DetectCollision(entt::registry &registry, Adagio::GameStats &stats,
Adagio::StateMachine *state) {
auto getCollisionCenter = [&registry](const CollisionRadius &collision, entt::entity entity) {
Adagio::Vector2d collisionCenter = collision.offset;
Position *pos = registry.try_get<Position>(entity);
if (pos) {
collisionCenter += pos->position;
}
return collisionCenter;
};

auto view = registry.view<CollisionRadius, Target>();
auto filterView = registry.view<CollisionRadius, Filter>();
for (auto entity: view) {
const CollisionRadius &collision = registry.get<CollisionRadius>(entity);
const Adagio::Vector2d collisionCenter = getCollisionCenter(collision, entity);
for (auto id: filterView) {
const CollisionRadius &otherCollision = registry.get<CollisionRadius>(id);
const Adagio::Vector2d otherCollisionCenter = getCollisionCenter(otherCollision, id) - collisionCenter;
const double minDistance = collision.radius + otherCollision.radius;
if (otherCollisionCenter.magnitudeSquared() <= minDistance * minDistance) {
registry.ctx().get<Adagio::MessageDispatchService *>()->dispatch(entity, id, "COLLISION"_hs);
}
}
}
}

#endif //GL_ADAGIO_DETECTCOLLISION_H
213 changes: 114 additions & 99 deletions src/math/Vector2-impl.hpp
Original file line number Diff line number Diff line change
@@ -1,101 +1,116 @@
namespace Adagio {
template <typename T> Vector2<T>::Vector2() {
this->x = 0;
this->y = 0;
}

template <typename T> Vector2<T>::Vector2(T x, T y) {
this->x = x;
this->y = y;
}

template <typename T>
template <typename U>
Vector2<T>::Vector2(const Vector2<U> &v) {
this->x = static_cast<T>(v.x);
this->y = static_cast<T>(v.y);
}

template <typename T> double Vector2<T>::magnitude() const {
return sqrt(x * x + y * y);
}

template <typename T> std::string Vector2<T>::to_string() const {
std::stringstream sstream;
sstream << *this;
std::string str = sstream.str();
return str;
}

template <typename T> Vector2<T> Vector2<T>::normalized() const {
double length = magnitude();
return Vector2<T>{x / length, y / length};
}

template <typename T> Vector2<T> Vector2<T>::floor() const {
return Vector2<T>{std::floor(x), std::floor(y)};
}

template <typename T>
void Vector2<T>::clamp(const Vector2<T> &lower, const Vector2<T> &upper) {
x = std::max(std::min(upper.x, x), lower.x);
y = std::max(std::min(upper.y, y), lower.y);
}

template <typename T>
Vector2<T> operator+(const Vector2<T> &left, Vector2<T> right) {
return Vector2<T>{left.x + right.x, left.y + right.y};
}

template <typename T>
Vector2<T> operator-(const Vector2<T> &left, Vector2<T> right) {
return Vector2<T>{left.x - right.x, left.y - right.y};
}

template <typename T>
Vector2<T> operator+=(Vector2<T> &left, Vector2<T> right) {
left.x += right.x;
left.y += right.y;

return left;
}

template <typename T>
Vector2<T> operator-=(Vector2<T> &left, Vector2<T> right) {
left.x -= right.x;
left.y -= right.y;

return left;
}

template <typename T> Vector2<T> operator*(const Vector2<T> &left, T right) {
return Vector2<T>{left.x * right, left.y * right};
}

template <typename T> Vector2<T> operator*(T left, const Vector2<T> &right) {
return Vector2<T>{left * right.x, left * right.y};
}

template <typename T> Vector2<T> operator/(const Vector2<T> &left, T right) {
return Vector2<T>{left.x / right, left.y / right};
}

template <typename T> Vector2<T> operator/(T left, const Vector2<T> &right) {
return Vector2<T>{left / right.x, left / right.y};
}

template <typename T>
bool operator==(const Vector2<T> &left, const Vector2<T> &right) {
return left.x == right.x && left.y == right.y;
}

template <typename T>
bool operator!=(const Vector2<T> &left, const Vector2<T> &right) {
return left.x != right.x && left.y != right.y;
}

template <typename T>
inline std::ostream &operator<<(std::ostream &left, const Vector2<T> &right) {
return left << "Vector2(" << right.x << "," << right.y << ")";
}
template<typename T>
Vector2<T>::Vector2() {
this->x = 0;
this->y = 0;
}

template<typename T>
Vector2<T>::Vector2(T x, T y) {
this->x = x;
this->y = y;
}

template<typename T>
template<typename U>
Vector2<T>::Vector2(const Vector2<U> &v) {
this->x = static_cast<T>(v.x);
this->y = static_cast<T>(v.y);
}

template<typename T>
double Vector2<T>::magnitude() const {
return sqrt(x * x + y * y);
}

template<typename T>
double Vector2<T>::magnitudeSquared() const {
return x * x + y * y;
}

template<typename T>
std::string Vector2<T>::to_string() const {
std::stringstream sstream;
sstream << *this;
std::string str = sstream.str();
return str;
}

template<typename T>
Vector2<T> Vector2<T>::normalized() const {
double length = magnitude();
return Vector2<T>{x / length, y / length};
}

template<typename T>
Vector2<T> Vector2<T>::floor() const {
return Vector2<T>{std::floor(x), std::floor(y)};
}

template<typename T>
void Vector2<T>::clamp(const Vector2<T> &lower, const Vector2<T> &upper) {
x = std::max(std::min(upper.x, x), lower.x);
y = std::max(std::min(upper.y, y), lower.y);
}

template<typename T>
Vector2<T> operator+(const Vector2<T> &left, Vector2<T> right) {
return Vector2<T>{left.x + right.x, left.y + right.y};
}

template<typename T>
Vector2<T> operator-(const Vector2<T> &left, Vector2<T> right) {
return Vector2<T>{left.x - right.x, left.y - right.y};
}

template<typename T>
Vector2<T> operator+=(Vector2<T> &left, Vector2<T> right) {
left.x += right.x;
left.y += right.y;

return left;
}

template<typename T>
Vector2<T> operator-=(Vector2<T> &left, Vector2<T> right) {
left.x -= right.x;
left.y -= right.y;

return left;
}

template<typename T>
Vector2<T> operator*(const Vector2<T> &left, T right) {
return Vector2<T>{left.x * right, left.y * right};
}

template<typename T>
Vector2<T> operator*(T left, const Vector2<T> &right) {
return Vector2<T>{left * right.x, left * right.y};
}

template<typename T>
Vector2<T> operator/(const Vector2<T> &left, T right) {
return Vector2<T>{left.x / right, left.y / right};
}

template<typename T>
Vector2<T> operator/(T left, const Vector2<T> &right) {
return Vector2<T>{left / right.x, left / right.y};
}

template<typename T>
bool operator==(const Vector2<T> &left, const Vector2<T> &right) {
return left.x == right.x && left.y == right.y;
}

template<typename T>
bool operator!=(const Vector2<T> &left, const Vector2<T> &right) {
return left.x != right.x && left.y != right.y;
}

template<typename T>
inline std::ostream &operator<<(std::ostream &left, const Vector2<T> &right) {
return left << "Vector2(" << right.x << "," << right.y << ")";
}
} // namespace Adagio
36 changes: 20 additions & 16 deletions src/math/Vector2.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,35 @@
#include <string>

namespace Adagio {
template <typename T> struct Vector2 {
T x;
T y;
template<typename T>
struct Vector2 {
T x;
T y;

Vector2();
Vector2();

Vector2(T x, T y);
Vector2(T x, T y);

template <typename U> explicit Vector2(const Vector2<U> &vec);
template<typename U>
explicit Vector2(const Vector2<U> &vec);

[[nodiscard]] double magnitude() const;
[[nodiscard]] double magnitude() const;

[[nodiscard]] std::string to_string() const;
[[nodiscard]] double magnitudeSquared() const;

[[nodiscard]] Vector2<T> normalized() const;
[[nodiscard]] std::string to_string() const;

[[nodiscard]] Vector2<T> floor() const;
[[nodiscard]] Vector2<T> normalized() const;

void clamp(const Vector2<T> &lower, const Vector2<T> &upper);
};
[[nodiscard]] Vector2<T> floor() const;

typedef Vector2<int> Vector2i;
typedef Vector2<unsigned int> Vector2u;
typedef Vector2<float> Vector2f;
typedef Vector2<double> Vector2d;
void clamp(const Vector2<T> &lower, const Vector2<T> &upper);
};

typedef Vector2<int> Vector2i;
typedef Vector2<unsigned int> Vector2u;
typedef Vector2<float> Vector2f;
typedef Vector2<double> Vector2d;
} // namespace Adagio

#include "Vector2-impl.hpp"
Expand Down
62 changes: 62 additions & 0 deletions test/game/systems/DetectCollision.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "../../../src/game/components/events/hooks/MessageInboxHooks.h"
#include "../../../src/game/components/events/MessageInbox.h"
#include "../../../src/game/components/CollisionRadius.h"
#include "../../../src/game/components/Position.h"
#include "../../../src/game/systems/DetectCollision.h"
#include "../EcsTestingHarness.h"
#include <catch2/catch.hpp>

static EcsTestingHarness harness;

struct TagA {
};
struct TagB {
};
struct TestStruct {
bool importantData{false};
};

TEST_CASE("DetectCollision: No components defined", "[system][Collision]") {
harness.reset();
REQUIRE_NOTHROW(DetectCollision<TagA, TagB>(harness.registry, harness.stats,
harness.stateMachine));
REQUIRE_NOTHROW(DetectCollision<TagA, TestStruct>(harness.registry, harness.stats,
harness.stateMachine));
}

TEST_CASE("Detect Collision: Fires if components fully intersect", "[system][Collision]") {
harness.reset();
harness.registry.on_construct<MessageInbox>().connect<&RegisterInboxWithMessageService>();
harness.registry.on_destroy<MessageInbox>().connect<&UnregisterInboxWithMessageService>();
auto entityA = harness.registry.create();
auto entityB = harness.registry.create();
harness.registry.emplace<Position>(entityA, Adagio::Vector2d{0, 0});
auto &positionB = harness.registry.emplace<Position>(entityB, Adagio::Vector2d{0, 0});
harness.registry.emplace<CollisionRadius>(entityA, Adagio::Vector2d{0, 0}, 1);
harness.registry.emplace<CollisionRadius>(entityB, Adagio::Vector2d{0, 0}, 1);
harness.registry.emplace<TagA>(entityA);
harness.registry.emplace<TagB>(entityB);
auto &inbox = harness.registry.emplace<MessageInbox>(entityA);

harness.testSystemFrame(DetectCollision<TagA, TagB>);

REQUIRE(inbox.messages.size() == 1);

SECTION("Include both radii in the equation") {
inbox.messages.shift();
positionB.position.x = 1.5;

harness.testSystemFrame(DetectCollision<TagA, TagB>);

REQUIRE(inbox.messages.size() == 1);
}

SECTION("Do not fire once an entity is too far away") {
inbox.messages.shift();
positionB.position.x = 2.5;

harness.testSystemFrame(DetectCollision<TagA, TagB>);

REQUIRE(inbox.messages.empty());
}
}
Loading

0 comments on commit 4077fe2

Please sign in to comment.