diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp index 4b7c8b70dcb0caca4e63b676f0d89308371251a0..97f56cf8828b5b22546d403a1d2f04e9be074b86 100644 --- a/demo/demo/rollball.cpp +++ b/demo/demo/rollball.cpp @@ -114,7 +114,14 @@ static void setupPrefabs(fggl::ecs3::World& world, Prefabs& prefabs) { fggl::data::make_cube(meshComponent.mesh); world.set<fggl::data::StaticMesh>(prefabs.collectable, &meshComponent); - auto* callbacks = world.add<fggl::phys::CollisionCallbacks>(prefabs.collectable); + // physics stuff + auto* rb = world.add<fggl::phys::RigidBody>(prefabs.collectable); + rb->shape = new fggl::phys::Box({0.5f, 0.5f, 0.5f}); + rb->type = fggl::phys::BodyType::KINEMATIC; + + // we need both of these for callbacks to trigger. + world.add<fggl::phys::CollisionCallbacks>(prefabs.collectable); + world.add<fggl::phys::CollisionCache>(prefabs.collectable); } } @@ -200,12 +207,21 @@ namespace demo { Prefabs prefabs{}; setupPrefabs(world(), prefabs); + // collectable callbacks + auto* collectableCallbacks = world().get<fggl::phys::CollisionCallbacks>(prefabs.collectable); + collectableCallbacks->onEnter = [this](auto ourID, auto theirID) { + if ( theirID == player) { + this->world().destroy(ourID); + } + }; + // actual scene objects setupCamera(world()); // create a 20x20 grid fggl::math::vec2 size{40.0f, 40.0f}; player = setupEnvironment(world(), prefabs, size); + } void RollBall::update() { diff --git a/fggl/phys/bullet/simulation.cpp b/fggl/phys/bullet/simulation.cpp index 5524cf55640c8afcd4b61d6d269d5079d5cf875e..da1291ae1b00b71e788cb899ab24684ad45aa38f 100644 --- a/fggl/phys/bullet/simulation.cpp +++ b/fggl/phys/bullet/simulation.cpp @@ -37,6 +37,8 @@ namespace fggl::phys::bullet { m_debug = std::make_unique<debug::BulletDebugDrawList>(); m_world->setDebugDrawer( m_debug.get() ); m_debug->setDebugMode(1); + + m_ecs->addDeathListener( [this](auto entity) { this->onEntityDeath(entity);} ); } void BulletPhysicsEngine::step() { @@ -66,6 +68,7 @@ namespace fggl::phys::bullet { m_world->debugDrawWorld(); } + static void build_motation_state(math::Transform* myState, BulletBody* btState) { auto myPos = myState->origin(); btVector3 position{myPos.x, myPos.y, myPos.z}; @@ -203,12 +206,12 @@ namespace fggl::phys::bullet { auto itr = cache->collisions.find(other); if ( itr == cache->collisions.end() ) { if ( callbacks->onEnter != nullptr ) { - callbacks->onEnter(other); + callbacks->onEnter(owner, other); } - cache->collisions.insert( other ); + cache->collisions.insert( other ); } else { if ( callbacks->onStay != nullptr ) { - callbacks->onStay(other); + callbacks->onStay(owner, other); } } } @@ -231,13 +234,23 @@ namespace fggl::phys::bullet { const auto* body0 = contactManifold->getBody0(); const auto* body1 = contactManifold->getBody1(); - handleCollisionCallbacks(m_ecs, body0->getUserIndex(), body1->getUserIndex()); - handleCollisionCallbacks(m_ecs, body1->getUserIndex(), body0->getUserIndex()); + // FIXME rigid body didn't die according to plan! + if ( body0->getUserIndex() == ecs::NULL_ENTITY || body1->getUserIndex() == ecs::NULL_ENTITY) { + continue; + } + + if ( !m_ecs->alive( body0->getUserIndex()) || !m_ecs->alive(body1->getUserIndex()) ) { + continue; + } std::cerr << "contact: " << body0->getUserIndex() << " on " << body1->getUserIndex() << std::endl; + + handleCollisionCallbacks(m_ecs, body0->getUserIndex(), body1->getUserIndex()); + handleCollisionCallbacks(m_ecs, body1->getUserIndex(), body0->getUserIndex()); } // note conacts that have ended + caches = m_ecs->findMatching<CollisionCache>(); // re-fetch, entities can (and probably do) die in handlers. for( auto& ent : caches) { auto* cache = m_ecs->get<CollisionCache>(ent); @@ -251,10 +264,18 @@ namespace fggl::phys::bullet { for (const auto& other : cache->lastFrame) { auto itr = cache->collisions.find(other); if (itr == cache->collisions.end()) { - callbacks->onExit(other); + callbacks->onExit( ent,other); } } } } + void BulletPhysicsEngine::onEntityDeath(ecs::entity_t entity) { + auto* btPhysics = m_ecs->tryGet<BulletBody>(entity); + if ( btPhysics != nullptr) { + btPhysics->body->setUserIndex( ecs::NULL_ENTITY ); + m_world->removeRigidBody( btPhysics->body ); + } + } + } // namespace fggl::phys::bullet \ No newline at end of file diff --git a/fggl/scenes/game.cpp b/fggl/scenes/game.cpp index 92aace65b711366bc0ec61b08e911ed1dc725cca..b2f3ad583a62e6d4d95cee1720429a308d4a4cd1 100644 --- a/fggl/scenes/game.cpp +++ b/fggl/scenes/game.cpp @@ -59,6 +59,8 @@ namespace fggl::scenes { if ( m_phys != nullptr ) { m_phys->step(); } + + m_world->reapEntities(); } void Game::render(fggl::gfx::Graphics &gfx) { diff --git a/include/fggl/ecs3/prototype/world.h b/include/fggl/ecs3/prototype/world.h index 4cc8a5bdbb790b7c82972446685cfe34c3f693a7..5a178f5d7f28e2a7222130e36a680f592b82a280 100644 --- a/include/fggl/ecs3/prototype/world.h +++ b/include/fggl/ecs3/prototype/world.h @@ -6,6 +6,9 @@ #define FGGL_ECS3_PROTOTYPE_WORLD_H #include <map> +#include <functional> +#include <unordered_set> + #include <fggl/ecs3/types.hpp> /** @@ -15,6 +18,8 @@ */ namespace fggl::ecs3::prototype { + using entity_cb = std::function<void(const entity_t)>; + class Entity { public: bool m_abstract; @@ -122,9 +127,39 @@ namespace fggl::ecs3::prototype { return clone; } + bool alive(entity_t entity) const { + if (entity == NULL_ENTITY) { + // tis a silly question, but can be asked by accident. + return false; + } + + if ( m_killList.find( entity ) != m_killList.end() ) { + // getting reaped + return false; + } + + // check liveness + return m_entities.find( entity ) != m_entities.end(); + } + void destroy(entity_t entity) { + assert( entity != NULL_ENTITY && "attempted to kill null entity" ); // TOOD resolve and clean components - m_entities.erase(entity); + //m_entities.erase(entity); + m_killList.insert(entity); + } + + void reapEntities() { + for (const auto& entity : m_killList) { + //auto& entityObj = m_entities.at(entity); + //entityObj.clear(); + for (auto& listener : m_deathListeners) { + listener( entity ); + } + + m_entities.erase(entity); + } + m_killList.clear(); } inline TypeRegistry &types() { @@ -140,6 +175,8 @@ namespace fggl::ecs3::prototype { } std::vector<component_type_t> getComponents(entity_t entityID) { + assert(alive(entityID) && "attempted to get components on dead entity"); + std::vector<component_type_t> components{}; auto &entity = m_entities.at(entityID); auto comps = entity.getComponentIDs(); @@ -168,6 +205,8 @@ namespace fggl::ecs3::prototype { template<typename C> C *add(entity_t entity_id) { + assert( entity_id != NULL_ENTITY && "attempted to add component on null entity" ); + //spdlog::info("component '{}' added to '{}'", C::name, entity_id); auto &entity = m_entities.at(entity_id); auto comp = entity.template add<C>(); @@ -177,6 +216,8 @@ namespace fggl::ecs3::prototype { } void *add(entity_t entity_id, component_type_t component_id) { + assert( entity_id != NULL_ENTITY && "attempted to add component on null entity" ); + auto meta = m_types.meta(component_id); auto &entity = m_entities.at(entity_id); @@ -187,6 +228,8 @@ namespace fggl::ecs3::prototype { template<typename C> C *set(entity_t entity_id, const C *ptr) { + assert( entity_id != NULL_ENTITY && "attempted to set component on null entity" ); + //spdlog::info("component '{}' set on '{}'", C::name, entity_id); auto &entity = m_entities.at(entity_id); auto comp = entity.set<C>(ptr); @@ -196,6 +239,8 @@ namespace fggl::ecs3::prototype { } void *set(entity_t entity_id, component_type_t cid, const void *ptr) { + assert( entity_id != NULL_ENTITY && "attempted to set component on null entity" ); + auto &entity = m_entities.at(entity_id); auto cMeta = m_types.meta(cid); @@ -206,6 +251,8 @@ namespace fggl::ecs3::prototype { template<typename C> C* tryGet(entity_t entity_id) const { + assert( entity_id != NULL_ENTITY && "attempted to tryGet component on null entity" ); + try { const auto &entity = m_entities.at(entity_id); try { @@ -214,13 +261,15 @@ namespace fggl::ecs3::prototype { return nullptr; } } catch ( std::out_of_range& e) { - std::cerr << "someone requested an component that didn't exist, entity was: " << entity_id << std::endl; + std::cerr << "someone requested an entity that didn't exist, entity was: " << entity_id << std::endl; return nullptr; } } template<typename C> C *get(entity_t entity_id) const { + assert( entity_id != NULL_ENTITY && "attempted to get component on null entity" ); + C* ptr = tryGet<C>(entity_id); if (ptr == nullptr) { std::cerr << "entity " << entity_id << " does not have component "<< C::name << std::endl; @@ -230,6 +279,8 @@ namespace fggl::ecs3::prototype { template<typename C> void remove(entity_t entity_id) { + assert( entity_id != NULL_ENTITY && "attempted to remove component on null entity" ); + try { auto &entity = m_entities.at(entity_id); try { @@ -247,10 +298,16 @@ namespace fggl::ecs3::prototype { return entity.get(t); } + void addDeathListener(const entity_cb& callback) { + m_deathListeners.emplace_back(callback); + } + private: + std::vector< entity_cb > m_deathListeners; TypeRegistry &m_types; entity_t m_next; std::map<entity_t, Entity> m_entities; + std::unordered_set<entity_t> m_killList; }; diff --git a/include/fggl/phys/bullet/types.hpp b/include/fggl/phys/bullet/types.hpp index 78896b78d3e84526bda061f6eab566531fe30fd5..04d8385edc93c41c441b6c0a58fc5434e6d011ad 100644 --- a/include/fggl/phys/bullet/types.hpp +++ b/include/fggl/phys/bullet/types.hpp @@ -62,6 +62,8 @@ namespace fggl::phys::bullet { ~BulletPhysicsEngine() override; void step() override; + + void onEntityDeath(ecs::entity_t entity); private: ecs3::World* m_ecs; BulletConfiguration m_config; diff --git a/include/fggl/phys/callbacks.hpp b/include/fggl/phys/callbacks.hpp index cc14483fb26359aa77ef5a50566fa6357d74b70c..ac8a02b062cfcf65a97969cfe44b47d2094840a3 100644 --- a/include/fggl/phys/callbacks.hpp +++ b/include/fggl/phys/callbacks.hpp @@ -30,7 +30,7 @@ namespace fggl::phys { - using CollisionCB = std::function<void(ecs3::entity_t)>; + using CollisionCB = std::function<void(ecs3::entity_t, ecs3::entity_t)>; struct CollisionCallbacks { constexpr static const char* name = "phys::Callbacks";