From 743f906bc734a4c0f3d3b6a8c9e9e2bdd0221f34 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Fri, 25 Oct 2024 22:31:09 -0400 Subject: [PATCH] Implemented a simple collision hash table. --- collider.cpp | 38 +++++++++++++++++++ collider.hpp | 32 ++++++++++++++++ components.hpp | 1 + map.hpp | 5 ++- meson.build | 3 ++ status.txt | 1 + tests/collider.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 collider.cpp create mode 100644 collider.hpp create mode 100644 tests/collider.cpp diff --git a/collider.cpp b/collider.cpp new file mode 100644 index 0000000..a68f43f --- /dev/null +++ b/collider.cpp @@ -0,0 +1,38 @@ +#include "collider.hpp" + +using DinkyECS::Entity; + +void SpatialHashTable::insert(Point pos, Entity ent) { + table[pos] = ent; +} + +void SpatialHashTable::remove(Point pos) { + table.erase(pos); +} + +void SpatialHashTable::move(Point from, Point to, Entity ent) { + remove(from); + insert(to, ent); +} + +bool SpatialHashTable::occupied(Point at) { + return table[at]; +} + +std::tuple SpatialHashTable::neighbors(Point cell) { + FoundList result; + + // Check the current cell and its 8 neighbors + // BUG: this can sign underflow, assert it won't + for (size_t x = cell.x - 1; x <= cell.x + 1; x++) { + for (size_t y = cell.y - 1; y <= cell.y + 1; y++) { + Point neighborCell = {x, y}; + auto it = table.find(neighborCell); + if (it != table.end()) { + result.insert(result.end(), it->second); + } + } + } + + return std::tuple(!result.empty(), result); +} diff --git a/collider.hpp b/collider.hpp new file mode 100644 index 0000000..60ff2af --- /dev/null +++ b/collider.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include "map.hpp" +#include "dinkyecs.hpp" +#include + +struct PointHash { + size_t operator()(const Point& p) const { + return std::hash()(p.x) ^ std::hash()(p.y); + } +}; + +typedef std::vector FoundList; + +class SpatialHashTable { + public: + SpatialHashTable() {} + + // disable copying, I think? + SpatialHashTable(SpatialHashTable &other) = delete; + + void insert(Point pos, DinkyECS::Entity obj); + void move(Point from, Point to, DinkyECS::Entity ent); + void remove(Point pos); + bool occupied(Point pos); + + std::tuple neighbors(Point position); + + private: + std::unordered_map table; +}; diff --git a/components.hpp b/components.hpp index e650364..4890267 100644 --- a/components.hpp +++ b/components.hpp @@ -1,5 +1,6 @@ #pragma once #include "dinkyecs.hpp" +#include "map.hpp" #include namespace Components { diff --git a/map.hpp b/map.hpp index 0c9fbdc..3453505 100644 --- a/map.hpp +++ b/map.hpp @@ -15,10 +15,13 @@ #define PLAYER_TILE "☺" #define ENEMY_TILE "Ω" - struct Point { size_t x = 0; size_t y = 0; + + bool operator==(const Point& other) const { + return other.x == x && other.y == y; + } }; struct Room { diff --git a/meson.build b/meson.build index 8e616fd..c415373 100644 --- a/meson.build +++ b/meson.build @@ -17,9 +17,11 @@ runtests = executable('runtests', [ 'dbc.cpp', 'map.cpp', 'rand.cpp', + 'collider.cpp', 'tests/fsm.cpp', 'tests/dbc.cpp', 'tests/map.cpp', + 'tests/collider.cpp', ], dependencies: dependencies) @@ -29,6 +31,7 @@ roguish = executable('roguish', [ 'map.cpp', 'gui.cpp', 'rand.cpp', + 'collider.cpp', 'systems.cpp', ], dependencies: dependencies) diff --git a/status.txt b/status.txt index 0646a82..8b60880 100644 --- a/status.txt +++ b/status.txt @@ -6,3 +6,4 @@ TODO: * Work on collision detection with either a coordinate map or morton codes. * Bring back sounds, check out SoLoud. +* getNearby does size_t - int diff --git a/tests/collider.cpp b/tests/collider.cpp new file mode 100644 index 0000000..ae39691 --- /dev/null +++ b/tests/collider.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include "collider.hpp" +#include "dinkyecs.hpp" + +using DinkyECS::Entity; +using namespace fmt; + +TEST_CASE("confirm basic collision operations", "[collision]") { + DinkyECS::World world; + Entity player = world.entity(); + Entity enemy = world.entity(); + + SpatialHashTable coltable; + coltable.insert({11,11}, player); + coltable.insert({21,21}, enemy); + + { // not found + auto [found, nearby] = coltable.neighbors({1,1}); + REQUIRE(!found); + REQUIRE(nearby.empty()); + } + + { // found + auto [found, nearby] = coltable.neighbors({10,10}); + + REQUIRE(found); + REQUIRE(nearby[0] == player); + } + + { // removed + coltable.remove({11,11}); + auto [found, nearby] = coltable.neighbors({10,10}); + REQUIRE(!found); + REQUIRE(nearby.empty()); + } + + coltable.insert({11,11}, player); // setup for the move test + + { // moving + coltable.move({11,11}, {12, 12}, player); + auto [found, nearby] = coltable.neighbors({10,10}); + REQUIRE(!found); + REQUIRE(nearby.empty()); + } + + { // find it after move + auto [found, nearby] = coltable.neighbors({11,11}); + REQUIRE(found); + REQUIRE(nearby[0] == player); + } + + { + REQUIRE(coltable.occupied({12,12})); + REQUIRE(coltable.occupied({21,21})); + REQUIRE(!coltable.occupied({1,10})); + } +} + + + +TEST_CASE("confirm multiple entities moving", "[collision]") { + DinkyECS::World world; + Entity player = world.entity(); + Entity e1 = world.entity(); + Entity e2 = world.entity(); + Entity e3 = world.entity(); + + SpatialHashTable coltable; + coltable.insert({11,11}, player); + coltable.insert({10,10}, e2); + coltable.insert({11,10}, e3); + coltable.insert({21,21}, e1); + + { // find e3 and e2 + auto [found, nearby] = coltable.neighbors({11, 11}); + REQUIRE(found); + REQUIRE(nearby.size() == 3); + // BUG: replace this with std::find/std::search + REQUIRE(nearby[0] == e2); + REQUIRE(nearby[1] == e3); + REQUIRE(nearby[2] == player); + } + + coltable.move({11,11}, {20,20}, player); + { // should only find the e1 + auto [found, nearby] = coltable.neighbors({20,20}); + REQUIRE(found); + REQUIRE(nearby.size() == 2); + // BUG: replace this with std::find/std::search + REQUIRE(nearby[0] == player); + REQUIRE(nearby[1] == e1); + } +}