Skip to content

Commit

Permalink
Start converting collision/physics handling into ECS
Browse files Browse the repository at this point in the history
  • Loading branch information
daid committed Nov 3, 2022
1 parent 6a0350d commit 0750acf
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/components/collision.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include <glm/vec2.hpp>


class b2Body;
namespace sp {
class CollisionSystem;

// Position component, to give an entity a position and rotation in the 3D world.
class Position
{
public:
glm::vec2 getPosition() const { return position; }
float getRotation() const { return rotation; }

void setPosition(glm::vec2 v) { position = v; position_user_set = true; multiplayer_dirty = true; }
void setRotation(float angle) { rotation = angle; rotation_user_set = true; multiplayer_dirty = true; }
private:
bool position_user_set = false;
bool rotation_user_set = false;
bool multiplayer_dirty = false;

glm::vec2 position{};
float rotation = 0.0f;

friend class sp::CollisionSystem;
};

// The physics component will give the entity a physical presents in the physic simulation
// this includes collision feedback. A physics component does nothing on it's own, it needs a Position component as well.
// The position component will be updated by the physics system on each physics step. Updating the position component from another location will force the
// physics to move the object to that position.
class Physics
{
public:
enum class Type {
Sensor,
Dynamic,
Static,
};
Type getType() const { return type; }
void setCircle(Type type, float radius) { if (type == this->type && shape == Shape::Circle && size.x == radius) return; this->type = type; shape = Shape::Circle; size.x = radius; physics_dirty = true; multiplayer_dirty = true; }
void setRectangle(Type type, glm::vec2 new_size) { if (type == this->type && shape == Shape::Rectangle && size == new_size) return; this->type = type; shape = Shape::Circle; size = new_size; physics_dirty = true; multiplayer_dirty = true; }
glm::vec2 getSize() const { return size; }

glm::vec2 getVelocity() const { return linear_velocity; }
float getAngularVelocity() const { return angular_velocity; }

void setVelocity(glm::vec2 velocity) { linear_velocity_user_set = true; linear_velocity = velocity; }
void setAngularVelocity(float velocity) { angular_velocity_user_set = true; angular_velocity = velocity; }
private:
bool physics_dirty = true;
bool multiplayer_dirty = false;

Type type = Type::Sensor;
enum class Shape {
Circle,
Rectangle,
} shape = Shape::Circle;
glm::vec2 size{1.0, 1.0};

b2Body* body = nullptr;
glm::vec2 linear_velocity{};
float angular_velocity = 0.0f;
bool linear_velocity_user_set = false;
bool angular_velocity_user_set = false;

friend class sp::CollisionSystem;
};

}
185 changes: 185 additions & 0 deletions src/systems/collision.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#include "systems/collision.h"
#include "components/collision.h"
#include "ecs/query.h"

#include <glm/trigonometric.hpp>


#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-override"
#endif//__GNUC__
#include "Box2D/Box2D.h"
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif//__GNUC__


#define BOX2D_SCALE 20.0f
static inline glm::vec2 b2v(b2Vec2 v)
{
return glm::vec2(v.x * BOX2D_SCALE, v.y * BOX2D_SCALE);
}
static inline b2Vec2 v2b(glm::vec2 v)
{
return b2Vec2(v.x / BOX2D_SCALE, v.y / BOX2D_SCALE);
}


static b2World* world;


namespace sp {

std::vector<CollisionHandler*> CollisionSystem::handlers;

namespace {
struct Collision
{
sp::ecs::Entity A;
sp::ecs::Entity B;
float collision_force;
};
}

void CollisionSystem::update(float delta)
{
if (!world)
world = new b2World(b2Vec2(0, 0));
if (delta <= 0.0f)
return;
world->Step(delta, 4, 8);

// Go over each entity with physics, and create/update bodies if needed.
for(auto [entity, position, physics] : sp::ecs::Query<Position, Physics>()) {
if (physics.physics_dirty)
{
physics.physics_dirty = false;
sp::ecs::Entity* ptr;
if (physics.body) {
ptr = (sp::ecs::Entity*)physics.body->GetUserData();
world->DestroyBody(physics.body);
} else {
ptr = new sp::ecs::Entity();
*ptr = entity;
}

b2BodyDef bodyDef;
bodyDef.type = physics.type == Physics::Type::Dynamic ? b2_dynamicBody : b2_kinematicBody;
bodyDef.userData = ptr;
bodyDef.allowSleep = false;
bodyDef.position = v2b(position.position);
bodyDef.angle = glm::radians(position.rotation);
physics.body = world->CreateBody(&bodyDef);

b2FixtureDef shapeDef;
shapeDef.density = 1.f;
shapeDef.friction = 0.f;
shapeDef.isSensor = physics.type == Physics::Type::Sensor;

if (physics.shape == Physics::Shape::Circle) {
b2CircleShape shape;
shape.m_radius = physics.size.x / BOX2D_SCALE;
shapeDef.shape = &shape;
physics.body->CreateFixture(&shapeDef);
} else {
b2PolygonShape shape;
shape.SetAsBox(physics.size.x / 2.f / BOX2D_SCALE, physics.size.y / 2.f / BOX2D_SCALE, {0, 0}, 0);
shapeDef.shape = &shape;
physics.body->CreateFixture(&shapeDef);
}
}
if (position.position_user_set && physics.body) {
physics.body->SetTransform(v2b(position.position), physics.body->GetAngle());
position.position_user_set = false;
}
if (position.rotation_user_set && physics.body) {
physics.body->SetTransform(physics.body->GetPosition(), glm::radians(position.rotation));
position.rotation_user_set = false;
}
if (physics.linear_velocity_user_set && physics.body) {
physics.body->SetLinearVelocity(v2b(physics.linear_velocity));
physics.linear_velocity_user_set = false;
}
if (physics.angular_velocity_user_set && physics.body) {
physics.body->SetAngularVelocity(glm::radians(physics.angular_velocity));
physics.angular_velocity_user_set = false;
}
}

// Go over each body in the physics world, and update the entity, or delete the body if the entity is gone.
std::vector<b2Body*> remove_list;
for(b2Body* body = world->GetBodyList(); body; body = body->GetNext()) {
sp::ecs::Entity* entity_ptr = (sp::ecs::Entity*)body->GetUserData();
Position* position;
Physics* physics;
if (!*entity_ptr || !(physics = entity_ptr->getComponent<Physics>()) || !(position = entity_ptr->getComponent<Position>())) {
delete entity_ptr;
remove_list.push_back(body);
} else {
position->position = b2v(body->GetPosition());
position->rotation = glm::degrees(body->GetAngle());
physics->linear_velocity = b2v(body->GetLinearVelocity());
physics->angular_velocity = glm::degrees(body->GetAngularVelocity());
position->multiplayer_dirty = true; //TODO make this condition and like, not sending every tick.
}
}
for(auto body : remove_list) {
world->DestroyBody(body);
}

// Find all the collisions and process them.
std::vector<Collision> collisions;
for(b2Contact* contact = world->GetContactList(); contact; contact = contact->GetNext())
{
if (contact->IsTouching() && contact->IsEnabled())
{
float force = 0.0f;
for (int n = 0; n < contact->GetManifold()->pointCount; n++)
{
force += contact->GetManifold()->points[n].normalImpulse * BOX2D_SCALE;
}
auto a = (sp::ecs::Entity*)contact->GetFixtureA()->GetBody()->GetUserData();
auto b = (sp::ecs::Entity*)contact->GetFixtureB()->GetBody()->GetUserData();

for(auto handler : handlers)
{
if (!*a || !*b)
break;
handler->collision(*a, *b, force);
}
}
}
}

class QueryCallback : public b2QueryCallback
{
public:
std::vector<sp::ecs::Entity> list;

/// Called for each fixture found in the query AABB.
/// @return false to terminate the query.
virtual bool ReportFixture(b2Fixture* fixture) override
{
auto ptr = (sp::ecs::Entity*)fixture->GetBody()->GetUserData();
if (*ptr)
list.push_back(*ptr);
return true;
}
};

std::vector<sp::ecs::Entity> CollisionSystem::queryArea(glm::vec2 lowerBound, glm::vec2 upperBound)
{
QueryCallback callback;
b2AABB aabb;
aabb.lowerBound = v2b(lowerBound);
aabb.upperBound = v2b(upperBound);
if (aabb.lowerBound.x > aabb.upperBound.x)
std::swap(aabb.upperBound.x, aabb.lowerBound.x);
if (aabb.lowerBound.y > aabb.upperBound.y)
std::swap(aabb.upperBound.y, aabb.lowerBound.y);
world->QueryAABB(&callback, aabb);
return callback.list;
}

}
26 changes: 26 additions & 0 deletions src/systems/collision.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include "ecs/entity.h"
#include <glm/vec2.hpp>


namespace sp {

class CollisionHandler
{
public:
virtual void collision(sp::ecs::Entity a, sp::ecs::Entity b, float force) = 0;
};

class CollisionSystem
{
public:
static void update(float delta);
static void addHandler(CollisionHandler* handler) { handlers.push_back(handler); }

static std::vector<sp::ecs::Entity> queryArea(glm::vec2 lowerBound, glm::vec2 upperBound);
private:
static std::vector<CollisionHandler*> handlers;
};

}

1 comment on commit 0750acf

@daid-tinyci
Copy link

@daid-tinyci daid-tinyci bot commented on 0750acf Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TinyCI build failure:

[/home/tinyci/builds/daid/SeriousProton/_build_native:make -j 2] returned [2]:


[ 15%] Built target spfreetype2

[ 16%] Built target glad

[ 29%] Built target box2d

[ 37%] Built target lua

[ 43%] Built target basisu-encoder

[ 83%] Built target opus

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/engine.cpp.o

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/multiplayer.cpp.o

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/multiplayer_client.cpp.o

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/multiplayer_server.cpp.o

[ 85%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/networkRecorder.cpp.o

[ 86%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/windowManager.cpp.o

[ 86%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/ecs/entity.cpp.o

In file included from /data/tinyci_builds/daid/SeriousProton/src/ecs/component.h:3,

                 from /data/tinyci_builds/daid/SeriousProton/src/ecs/entity.h:6,

                 from /data/tinyci_builds/daid/SeriousProton/src/ecs/entity.cpp:1:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:60:35: error: ‘size_t’ has not been declared

   60 |         Iterator(SparseSet& _set, size_t _dense_index) : set(_set), dense_index(_dense_index) {}

      |                                   ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:70:9: error: ‘size_t’ does not name a type

   70 |         size_t dense_index;

      |         ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:5:1: note: ‘size_t’ is defined in header ‘<cstddef>’; did you forget to ‘#include <cstddef>’?

    4 | #include <vector>

  +++ |+#include <cstddef>

    5 | #include <limits>

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:76:5: error: ‘size_t’ does not name a type

   76 |     size_t size() { return data.size(); }

      |     ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:76:5: note: ‘size_t’ is defined in header ‘<cstddef>’; did you forget to ‘#include <cstddef>’?

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In constructor ‘sp::SparseSet<T>::Iterator::Iterator(sp::SparseSet<T>&, int)’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:60:69: error: class ‘sp::SparseSet<T>::Iterator’ does not have any field named ‘dense_index’

   60 |         Iterator(SparseSet& _set, size_t _dense_index) : set(_set), dense_index(_dense_index) {}

      |                                                                     ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘bool sp::SparseSet<T>::Iterator::operator!=(const sp::SparseSet<T>::Iterator&) const’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:62:63: error: ‘dense_index’ was not declared in this scope

   62 |         bool operator!=(const Iterator& other) const { return dense_index != other.dense_index; }

      |                                                               ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘void sp::SparseSet<T>::Iterator::operator++()’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:63:29: error: ‘dense_index’ was not declared in this scope

   63 |         void operator++() { dense_index--; }

      |                             ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘std::pair<unsigned int, T&> sp::SparseSet<T>::Iterator::operator*()’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:64:65: error: ‘dense_index’ was not declared in this scope

   64 |         std::pair<uint32_t, T&> operator*() { return {set.dense[dense_index], set.data[dense_index]}; }

      |                                                                 ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘std::pair<unsigned int, const T&> sp::SparseSet<T>::Iterator::operator*() const’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:65:77: error: ‘dense_index’ was not declared in this scope

   65 |         std::pair<uint32_t, const T&> operator*() const { return {set.dense[dense_index], set.data[dense_index]}; }

      |                                                                             ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘bool sp::SparseSet<T>::Iterator::atEnd()’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:67:31: error: ‘dense_index’ was not declared in this scope

   67 |         bool atEnd() { return dense_index == std::numeric_limits<size_t>::max(); }

      |                               ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:67:66: error: ‘size_t’ was not declared in this scope; did you mean ‘std::size_t’?

   67 |         bool atEnd() { return dense_index == std::numeric_limits<size_t>::max(); }

      |                                                                  ^~~~~~

      |                                                                  std::size_t

In file included from /usr/include/c++/12/limits:42,

                 from /data/tinyci_builds/daid/SeriousProton/src/ecs/entity.h:4:

/usr/include/x86_64-linux-gnu/c++/12/bits/c++config.h:298:33: note: ‘std::size_t’ declared here

  298 |   typedef __SIZE_TYPE__         size_t;

      |                                 ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:67:72: error: template argument 1 is invalid

   67 |         bool atEnd() { return dense_index == std::numeric_limits<size_t>::max(); }

      |                                                                        ^

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘sp::SparseSet<T>::Iterator sp::SparseSet<T>::end()’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:74:65: error: ‘size_t’ was not declared in this scope; did you mean ‘std::size_t’?

   74 |     Iterator end() { return Iterator(*this, std::numeric_limits<size_t>::max()); }

      |                                                                 ^~~~~~

      |                                                                 std::size_t

/usr/include/x86_64-linux-gnu/c++/12/bits/c++config.h:298:33: note: ‘std::size_t’ declared here

  298 |   typedef __SIZE_TYPE__         size_t;

      |                                 ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:74:71: error: template argument 1 is invalid

   74 |     Iterator end() { return Iterator(*this, std::numeric_limits<size_t>::max()); }

      |                                                                       ^

make[2]: *** [CMakeFiles/seriousproton_objects.dir/build.make:852: CMakeFiles/seriousproton_objects.dir/src/ecs/entity.cpp.o] Error 1

make[2]: *** Waiting for unfinished jobs....

make[1]: *** [CMakeFiles/Makefile2:238: CMakeFiles/seriousproton_objects.dir/all] Error 2

make: *** [Makefile:136: all] Error 2

Please sign in to comment.