From 0750acf958805696a06e2e85d8fbe3b246320568 Mon Sep 17 00:00:00 2001 From: Daid Date: Thu, 3 Nov 2022 15:58:07 +0100 Subject: [PATCH] Start converting collision/physics handling into ECS --- src/components/collision.h | 72 +++++++++++++++ src/systems/collision.cpp | 185 +++++++++++++++++++++++++++++++++++++ src/systems/collision.h | 26 ++++++ 3 files changed, 283 insertions(+) create mode 100644 src/components/collision.h create mode 100644 src/systems/collision.cpp create mode 100644 src/systems/collision.h diff --git a/src/components/collision.h b/src/components/collision.h new file mode 100644 index 00000000..7b38f55b --- /dev/null +++ b/src/components/collision.h @@ -0,0 +1,72 @@ +#pragma once + +#include + + +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; +}; + +} \ No newline at end of file diff --git a/src/systems/collision.cpp b/src/systems/collision.cpp new file mode 100644 index 00000000..59a37505 --- /dev/null +++ b/src/systems/collision.cpp @@ -0,0 +1,185 @@ +#include "systems/collision.h" +#include "components/collision.h" +#include "ecs/query.h" + +#include + + +#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 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()) { + 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 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()) || !(position = entity_ptr->getComponent())) { + 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 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 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 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; +} + +} \ No newline at end of file diff --git a/src/systems/collision.h b/src/systems/collision.h new file mode 100644 index 00000000..9b8000ca --- /dev/null +++ b/src/systems/collision.h @@ -0,0 +1,26 @@ +#pragma once + +#include "ecs/entity.h" +#include + + +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 queryArea(glm::vec2 lowerBound, glm::vec2 upperBound); +private: + static std::vector handlers; +}; + +} \ No newline at end of file