From e48d3e2840b65b9db9e46d98b1196c5a2d8da4ed Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Mon, 1 Aug 2022 15:05:52 +0100 Subject: [PATCH] add service-based physics --- demo/data/rollball.yml | 4 +- demo/demo/rollball.cpp | 20 +++- demo/include/rollball.hpp | 3 + fggl/entity/loader/loader.cpp | 7 +- fggl/entity/module.cpp | 2 + include/fggl/entity/entity.hpp | 7 ++ include/fggl/entity/loader/loader.hpp | 8 +- include/fggl/entity/loader/spec.hpp | 1 + include/fggl/phys/service.hpp | 37 +++++++ integrations/bullet/CMakeLists.txt | 1 + .../include/fggl/phys/bullet/module.hpp | 5 +- .../include/fggl/phys/bullet/motion.hpp | 12 +- .../include/fggl/phys/bullet/service.hpp | 33 ++++++ .../bullet/include/fggl/phys/bullet/types.hpp | 2 + integrations/bullet/src/module.cpp | 69 +----------- integrations/bullet/src/service.cpp | 103 ++++++++++++++++++ integrations/bullet/src/simulation.cpp | 11 +- 17 files changed, 241 insertions(+), 84 deletions(-) create mode 100644 include/fggl/phys/service.hpp create mode 100644 integrations/bullet/include/fggl/phys/bullet/service.hpp create mode 100644 integrations/bullet/src/service.cpp diff --git a/demo/data/rollball.yml b/demo/data/rollball.yml index df682f1..ea445d5 100644 --- a/demo/data/rollball.yml +++ b/demo/data/rollball.yml @@ -86,6 +86,4 @@ prefabs: phys::Body: type: kinematic shape: - type: box - phys::Callbacks: - phys::Cache: \ No newline at end of file + type: box \ No newline at end of file diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp index 88cc926..d3661ca 100644 --- a/demo/demo/rollball.cpp +++ b/demo/demo/rollball.cpp @@ -120,14 +120,18 @@ namespace demo { constexpr fggl::math::vec3 COLOUR_BLUE{0.0F, 0.0F, 1.0F}; constexpr float MOVE_FORCE{3.0F}; - RollBall::RollBall(fggl::App &app) : Game(app) { + RollBall::RollBall(fggl::App &app) : Game(app), m_phys(nullptr) { } void RollBall::activate() { Game::activate(); - fggl::debug::log(fggl::debug::Level::info, "RollBall::activate()"); + // attach physics + auto* physService = m_owner.service<fggl::phys::PhysicsProvider>(); + auto* entFactory = m_owner.service<fggl::entity::EntityFactory>(); + m_phys = physService->create(&world(), entFactory); + auto* assetLoader = m_owner.service<fggl::assets::Loader>(); assetLoader->load("rollball.yml", fggl::entity::PROTOTYPE_ASSET); @@ -150,6 +154,17 @@ namespace demo { setup_environment(world(), m_owner.service<fggl::entity::EntityFactory>(), WORLD_SIZE, state); } + void RollBall::deactivate() { + // we need to clean up physics + if ( m_phys != nullptr ) { + delete m_phys; + m_phys = nullptr; + } + + // now let the rest of the scene do its bit + Game::deactivate(); + } + fggl::math::vec3 calc_move_vector(const fggl::input::Input& input) { constexpr fggl::math::vec3 forward = fggl::math::FORWARD; constexpr fggl::math::vec3 right = fggl::math::RIGHT; @@ -176,6 +191,7 @@ namespace demo { void RollBall::update() { Game::update(); + m_phys->step(); const float deltaTime = 1 / 60.0F; diff --git a/demo/include/rollball.hpp b/demo/include/rollball.hpp index d18bb4b..1b39f03 100644 --- a/demo/include/rollball.hpp +++ b/demo/include/rollball.hpp @@ -26,6 +26,7 @@ #define DEMO_ROLLBALL_HPP #include "fggl/scenes/game.hpp" +#include "fggl/phys/service.hpp" namespace demo { @@ -54,6 +55,7 @@ namespace demo { explicit RollBall(fggl::App& app); void activate() override; + void deactivate() override; void update() override; void render(fggl::gfx::Graphics& gfx) override; @@ -61,6 +63,7 @@ namespace demo { private: constexpr static fggl::math::vec3 HINT_COLOUR{0.5f, 0.0f, 0.0f}; RollState state; + fggl::phys::PhysicsEngine* m_phys; fggl::math::vec3 cameraOffset = {-15.0F, 15.0F, 0.0F}; void closestPickup(fggl::entity::EntityManager& world); diff --git a/fggl/entity/loader/loader.cpp b/fggl/entity/loader/loader.cpp index 62750d2..fb29fa8 100644 --- a/fggl/entity/loader/loader.cpp +++ b/fggl/entity/loader/loader.cpp @@ -32,7 +32,7 @@ namespace fggl::entity { auto name = prefab["name"].as<fggl::util::GUID>(); #ifndef NDEBUG - debug::log(debug::Level::info, "found prefab: {}", fggl::util::guidToString(name) ); + debug::info("found prefab: {}", fggl::util::guidToString(name) ); #endif // setup the components @@ -43,6 +43,11 @@ namespace fggl::entity { ComponentSpec compSpec{}; compSpec.config = compEntry.second; entity.components[compId] = compSpec; + entity.ordering.push_back(compId); + + #ifndef NDEBUG + debug::trace("{} has component {}", fggl::util::guidToString(name), fggl::util::guidToString(compId)); + #endif } factory->define( name, entity ); diff --git a/fggl/entity/module.cpp b/fggl/entity/module.cpp index ee6b1c0..7efd1ec 100644 --- a/fggl/entity/module.cpp +++ b/fggl/entity/module.cpp @@ -26,6 +26,8 @@ namespace fggl::entity { transform.origin( spec.get<math::vec3>( "origin", math::VEC3_ZERO ) ); transform.euler( spec.get<math::vec3>( "rotation", math::VEC3_ZERO ) ); transform.scale( spec.get<math::vec3>( "scale", math::VEC3_ONES ) ); + + debug::trace("created transform for entity {}", (uint64_t)entity); } void install_component_factories(entity::EntityFactory* factory) { diff --git a/include/fggl/entity/entity.hpp b/include/fggl/entity/entity.hpp index 05c3ba5..72d2d4a 100644 --- a/include/fggl/entity/entity.hpp +++ b/include/fggl/entity/entity.hpp @@ -20,6 +20,8 @@ #define FGGL_ENTITY_ENTITY_HPP #include <cstdint> + +#include "fggl/debug/logging.hpp" #include "fggl/vendor/entt.hpp" namespace fggl::entity { @@ -43,6 +45,11 @@ namespace fggl::entity { template<typename Component> Component& get(EntityID entity) { + #ifndef NDEBUG + if ( !has<Component>(entity) ) { + debug::error("Entity {} has no component of type {}", (uint64_t)entity, typeid(Component).name()); + } + #endif return m_registry.get<Component>(entity); } diff --git a/include/fggl/entity/loader/loader.hpp b/include/fggl/entity/loader/loader.hpp index 8da4709..47381dd 100644 --- a/include/fggl/entity/loader/loader.hpp +++ b/include/fggl/entity/loader/loader.hpp @@ -47,17 +47,19 @@ namespace fggl::entity { // invoke each component factory as required auto entity = manager.create(); - for (auto &[name, data] : prototype.components) { + for ( auto& component : prototype.ordering ) { try { - m_factories.at(name)(data, manager, entity); + auto& data = prototype.components.at(component); + m_factories.at(component)(data, manager, entity); } catch (std::out_of_range& ex) { #ifndef NDEBUG - debug::log(debug::Level::error, "EntityFactory: Unknown component factory type '{}'", fggl::util::guidToString(name)); + debug::log(debug::Level::error, "EntityFactory: Unknown component factory type '{}'", fggl::util::guidToString(component)); #endif manager.destroy(entity); return fggl::entity::INVALID; } } + return entity; } catch (std::out_of_range& ex) { #ifndef NDEBUG diff --git a/include/fggl/entity/loader/spec.hpp b/include/fggl/entity/loader/spec.hpp index 4dd926c..c9ef555 100644 --- a/include/fggl/entity/loader/spec.hpp +++ b/include/fggl/entity/loader/spec.hpp @@ -46,6 +46,7 @@ namespace fggl::entity { }; struct EntitySpec { + std::vector<util::GUID> ordering; std::map<util::GUID, ComponentSpec> components; }; diff --git a/include/fggl/phys/service.hpp b/include/fggl/phys/service.hpp new file mode 100644 index 0000000..54b84f9 --- /dev/null +++ b/include/fggl/phys/service.hpp @@ -0,0 +1,37 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 01/08/22. +// + +#ifndef FGGL_PHYS_SERVICE_HPP +#define FGGL_PHYS_SERVICE_HPP + +#include "fggl/modules/module.hpp" +#include "fggl/entity/module.hpp" +#include "fggl/phys/types.hpp" + +namespace fggl::phys { + + class PhysicsProvider { + public: + constexpr static const modules::ModuleService service = modules::make_service("fggl::phys::service"); + + virtual PhysicsEngine* create(entity::EntityManager* entityManager, entity::EntityFactory* factory) = 0; + }; + +} + +#endif //FGGL_PHYS_SERVICE_HPP diff --git a/integrations/bullet/CMakeLists.txt b/integrations/bullet/CMakeLists.txt index f128ed8..28134aa 100644 --- a/integrations/bullet/CMakeLists.txt +++ b/integrations/bullet/CMakeLists.txt @@ -35,5 +35,6 @@ else() src/module.cpp src/simulation.cpp src/phys_draw.cpp + src/service.cpp ) endif() diff --git a/integrations/bullet/include/fggl/phys/bullet/module.hpp b/integrations/bullet/include/fggl/phys/bullet/module.hpp index 6950919..2f81006 100644 --- a/integrations/bullet/include/fggl/phys/bullet/module.hpp +++ b/integrations/bullet/include/fggl/phys/bullet/module.hpp @@ -23,22 +23,21 @@ #include "fggl/entity/module.hpp" #include "fggl/phys/types.hpp" +#include "fggl/phys/service.hpp" #include "fggl/phys/bullet/types.hpp" namespace fggl::phys::bullet { - constexpr modules::ModuleService BIND_BULLET = modules::make_service("bind_bullet"); constexpr util::GUID CONFIG_PHYS_BODY = util::make_guid("phys::body"); struct Bullet { constexpr static const char* name = "fggl::phys::bullet"; constexpr static const std::array<modules::ModuleService, 1> provides = { - BIND_BULLET + phys::PhysicsProvider::service }; constexpr static const std::array<modules::ModuleService, 1> depends = { entity::EntityFactory::service }; - static bool factory(modules::ModuleService name, modules::Services& serviceManager); }; diff --git a/integrations/bullet/include/fggl/phys/bullet/motion.hpp b/integrations/bullet/include/fggl/phys/bullet/motion.hpp index 7a38a22..07c1a25 100644 --- a/integrations/bullet/include/fggl/phys/bullet/motion.hpp +++ b/integrations/bullet/include/fggl/phys/bullet/motion.hpp @@ -34,6 +34,7 @@ #define FGGL_PHYS_BULLET_MOTION_HPP #include "types.hpp" +#include "fggl/debug/logging.hpp" namespace fggl::phys::bullet { @@ -41,11 +42,16 @@ namespace fggl::phys::bullet { public: FgglMotionState(entity::EntityManager* world, entity::EntityID entity) : m_world(world), m_entity(entity) { } - virtual ~FgglMotionState() = default; + ~FgglMotionState() override = default; void getWorldTransform(btTransform& worldTrans) const override { - const auto& transform = m_world->get<fggl::math::Transform>(m_entity); - worldTrans.setFromOpenGLMatrix( glm::value_ptr(transform.model()) ); + const auto* transform = m_world->tryGet<fggl::math::Transform>(m_entity); + if ( transform == nullptr ) { + debug::warning("BT: attempted to get transform of entity without transform component."); + return; + } + + worldTrans.setFromOpenGLMatrix(glm::value_ptr(transform->model())); } void setWorldTransform(const btTransform& worldTrans) override { diff --git a/integrations/bullet/include/fggl/phys/bullet/service.hpp b/integrations/bullet/include/fggl/phys/bullet/service.hpp new file mode 100644 index 0000000..77adc64 --- /dev/null +++ b/integrations/bullet/include/fggl/phys/bullet/service.hpp @@ -0,0 +1,33 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 01/08/22. +// + +#ifndef FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_BULLETSERVICE_H +#define FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_BULLETSERVICE_H + +#include "fggl/phys/service.hpp" + +namespace fggl::phys::bullet { + + class BulletProvider : public phys::PhysicsProvider { + public: + PhysicsEngine* create(entity::EntityManager* entityManager, entity::EntityFactory* factory) override; + }; + +} + +#endif //FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_BULLETSERVICE_H diff --git a/integrations/bullet/include/fggl/phys/bullet/types.hpp b/integrations/bullet/include/fggl/phys/bullet/types.hpp index 4d6857d..b692413 100644 --- a/integrations/bullet/include/fggl/phys/bullet/types.hpp +++ b/integrations/bullet/include/fggl/phys/bullet/types.hpp @@ -83,6 +83,8 @@ namespace fggl::phys::bullet { std::vector<entity::EntityID> raycastAll(math::vec3 from, math::vec3 to) override; std::vector<entity::EntityID> sweep(PhyShape& shape, math::Transform& from, math::Transform& to) override; + void addBody(entity::EntityID entity, BulletBody& body); + bool debugDraw = false; private: entity::EntityManager* m_ecs; diff --git a/integrations/bullet/src/module.cpp b/integrations/bullet/src/module.cpp index 7703f1d..e880506 100644 --- a/integrations/bullet/src/module.cpp +++ b/integrations/bullet/src/module.cpp @@ -17,76 +17,13 @@ // #include "fggl/phys/bullet/module.hpp" -#include "fggl/phys/bullet/motion.hpp" +#include "fggl/phys/bullet/service.hpp" namespace fggl::phys::bullet { - constexpr float UNIT_EXTENT = 0.5F; - - btCollisionShape* create_bullet_shape(const YAML::Node& node) { - auto type = node["type"].as<std::string>("MISSING"); - if ( type == "sphere" ) { - auto radius = node["radius"].as<float>(UNIT_EXTENT); - return new btSphereShape(radius); - } else if ( type == "box" ) { - auto extents = node["extents"].as<math::vec3>(math::vec3(UNIT_EXTENT)); - return new btBoxShape(btVector3(extents.x, extents.y, extents.z)); - } else { - debug::warning("unknown shape type: {}, assuming unit sphere", type); - return new btSphereShape(0.5F); - } - } - - void setup_body(BodyType type, btRigidBody& body) { - if ( type != BodyType::STATIC ) { - body.setRestitution(0.5F); - body.setRollingFriction(0.0F); - - // kinematic bodies cannot sleep - if (type == BodyType::KINEMATIC) { - auto flags = (body.getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); - body.setCollisionFlags(flags); - body.setActivationState(DISABLE_DEACTIVATION); - } - } - } - - void add_bt_body(const entity::ComponentSpec& spec, entity::EntityManager& manager, const entity::EntityID& id) { - auto& body = manager.add<BulletBody>(id); - body.motion = new FgglMotionState(&manager, id); - - auto type = spec.get<BodyType>("type", BodyType::DYNAMIC); - auto mass = 0.0F; - - // non-static objects can have mass - if ( type != BodyType::STATIC ) { - mass = spec.get<float>("mass", 1.0F); - } - - // calculate inertia - btCollisionShape* shape = create_bullet_shape(spec.config["shape"]); - btVector3 inertia(0, 0, 0); - if ( type != BodyType::STATIC ) { - shape->calculateLocalInertia(mass, inertia); - } - - // construction information for the simulation - btRigidBody::btRigidBodyConstructionInfo constructionInfo { - mass, - body.motion, - shape, - inertia - }; - body.body = new btRigidBody(constructionInfo); - setup_body(type, *body.body); - - // add the body to the simulation - } - bool Bullet::factory(modules::ModuleService service, modules::Services& services) { - if ( service == BIND_BULLET ) { - auto* entityFactory = services.get<entity::EntityFactory>(); - entityFactory->bind(CONFIG_PHYS_BODY, add_bt_body); + if ( service == phys::PhysicsProvider::service ) { + services.bind<phys::PhysicsProvider, BulletProvider>(); } return false; } diff --git a/integrations/bullet/src/service.cpp b/integrations/bullet/src/service.cpp new file mode 100644 index 0000000..a91c777 --- /dev/null +++ b/integrations/bullet/src/service.cpp @@ -0,0 +1,103 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 01/08/22. +// + +#include "fggl/phys/bullet/service.hpp" +#include "fggl/phys/bullet/types.hpp" +#include "fggl/phys/bullet/motion.hpp" + +namespace fggl::phys::bullet { + + constexpr float UNIT_EXTENT = 0.5F; + + btCollisionShape* create_bullet_shape(const YAML::Node& node) { + auto type = node["type"].as<std::string>("MISSING"); + if ( type == "sphere" ) { + auto radius = node["radius"].as<float>(UNIT_EXTENT); + return new btSphereShape(radius); + } else if ( type == "box" ) { + auto extents = node["extents"].as<math::vec3>(math::vec3(UNIT_EXTENT)); + return new btBoxShape(btVector3(extents.x, extents.y, extents.z)); + } else { + debug::warning("unknown shape type: {}, assuming unit sphere", type); + return new btSphereShape(0.5F); + } + } + + void setup_body(BodyType type, btRigidBody& body) { + if ( type != BodyType::STATIC ) { + body.setRestitution(0.5F); + body.setRollingFriction(0.0F); + + // kinematic bodies cannot sleep + if (type == BodyType::KINEMATIC) { + auto flags = (body.getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); + body.setCollisionFlags(flags); + body.setActivationState(DISABLE_DEACTIVATION); + } + } + } + + void add_bt_body(BulletPhysicsEngine* engine, const entity::ComponentSpec& spec, entity::EntityManager& manager, const entity::EntityID& id) { + auto& body = manager.add<BulletBody>(id); + body.motion = new FgglMotionState(&manager, id); + + auto type = spec.get<BodyType>("type", BodyType::DYNAMIC); + auto mass = 0.0F; + + // non-static objects can have mass + if ( type != BodyType::STATIC ) { + mass = spec.get<float>("mass", 1.0F); + } + + // calculate inertia + btCollisionShape* shape = create_bullet_shape(spec.config["shape"]); + btVector3 inertia(0, 0, 0); + if ( type != BodyType::STATIC ) { + shape->calculateLocalInertia(mass, inertia); + } + + // construction information for the simulation + btRigidBody::btRigidBodyConstructionInfo constructionInfo { + mass, + body.motion, + shape, + inertia + }; + body.body = new btRigidBody(constructionInfo); + setup_body(type, *body.body); + + // add the body to the simulation + engine->addBody(id, body); + if ( type == BodyType::DYNAMIC ) { + manager.add<phys::Dynamics>(id); + } + } + + PhysicsEngine* BulletProvider::create(entity::EntityManager* entityManager, entity::EntityFactory* factory) { + auto* engine = new BulletPhysicsEngine(entityManager); + + // FIXME: this won't work across multiple scenes - reactive approach might be better... + factory->bind(util::make_guid("phys::Body"), [engine](const entity::ComponentSpec& config, entity::EntityManager& manager, const entity::EntityID& id) { + add_bt_body(engine, config, manager, id); + }); + engine->debugDraw = true; + + return engine; + } + +} // namespace fggl::phys::bullet \ No newline at end of file diff --git a/integrations/bullet/src/simulation.cpp b/integrations/bullet/src/simulation.cpp index 4ee06d6..df589cb 100644 --- a/integrations/bullet/src/simulation.cpp +++ b/integrations/bullet/src/simulation.cpp @@ -49,7 +49,7 @@ namespace fggl::phys::bullet { m_debug = std::make_unique<debug::BulletDebugDrawList>(); m_world->setDebugDrawer( m_debug.get() ); - m_debug->setDebugMode(1); + m_debug->setDebugMode(btIDebugDraw::DBG_DrawWireframe); // callbacks (for handling bullet -> ecs) @@ -58,7 +58,8 @@ namespace fggl::phys::bullet { } void BulletPhysicsEngine::step() { - checkForPhys(); + // this is now handled on creation + //checkForPhys(); auto dynamicEnts = m_ecs->find<phys::Dynamics, phys::bullet::BulletBody>(); for (auto& ent : dynamicEnts) { @@ -82,10 +83,14 @@ namespace fggl::phys::bullet { } } + void BulletPhysicsEngine::addBody(entity::EntityID /*entity*/, BulletBody &body) { + m_world->addRigidBody(body.body); + } + inline btCollisionShape* shape_to_bullet(const phys::RigidBody& fgglBody) { if ( fgglBody.shape == nullptr ) { // they forgot to put a shape, we'll assume a unit sphere to avoid crashes... - return new btSphereShape(0.5f); + return new btSphereShape(0.5F); } switch (fgglBody.shape->type) { -- GitLab