From 74352320b05a82b87fe0b7b91d8b075b582bc62c Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Sat, 30 Apr 2022 19:45:22 +0100 Subject: [PATCH] add player input and camera tracking to rollball example --- demo/demo/rollball.cpp | 76 +++++++++++++++++++--- demo/include/rollball.hpp | 3 + fggl/phys/bullet/simulation.cpp | 99 ++++++++++++++++++++++++++--- include/fggl/ecs3/prototype/world.h | 14 +++- include/fggl/phys/bullet/bullet.hpp | 5 +- include/fggl/phys/bullet/types.hpp | 5 ++ include/fggl/phys/callbacks.hpp | 50 +++++++++++++++ include/fggl/phys/types.hpp | 7 ++ 8 files changed, 238 insertions(+), 21 deletions(-) create mode 100644 include/fggl/phys/callbacks.hpp diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp index 24b50bf..4f81014 100644 --- a/demo/demo/rollball.cpp +++ b/demo/demo/rollball.cpp @@ -93,6 +93,7 @@ static void setupPrefabs(fggl::ecs3::World& world, Prefabs& prefabs) { // player (cube because my sphere function doesn't exist yet prefabs.player = world.create(true); world.add<fggl::math::Transform>(prefabs.player); + world.add<fggl::phys::Dynamics>(prefabs.player); world.add<fggl::phys::RigidBody>(prefabs.player); fggl::data::StaticMesh meshComponent; @@ -110,6 +111,8 @@ static void setupPrefabs(fggl::ecs3::World& world, Prefabs& prefabs) { meshComponent.pipeline = "phong"; fggl::data::make_cube(meshComponent.mesh); world.set<fggl::data::StaticMesh>(prefabs.collectable, &meshComponent); + + auto* callbacks = world.add<fggl::phys::CollisionCallbacks>(prefabs.collectable); } } @@ -136,7 +139,7 @@ static void setupCamera(fggl::ecs3::World& world) { } } -static void setupEnvironment(fggl::ecs3::World& world, const Prefabs prefabs, fggl::math::vec2& size) { +static fggl::ecs3::entity_t setupEnvironment(fggl::ecs3::World& world, const Prefabs prefabs, fggl::math::vec2& size) { { auto northWall = world.copy(prefabs.wallX); auto* transform = world.get<fggl::math::Transform>(northWall); @@ -167,17 +170,18 @@ static void setupEnvironment(fggl::ecs3::World& world, const Prefabs prefabs, fg transform->origin({0.0f, -2.5f, 0.0f}); } + auto player = fggl::ecs3::NULL_ENTITY; { // player just starts off as the prefab dictates - auto player = world.copy(prefabs.player); + player = world.copy(prefabs.player); } { // collectables std::array<fggl::math::vec3, 3> collectPos {{ - {-5.0f, 0.0f, 12.0f}, - {15.0f, 0.0f, 0.5f}, - {6.0f, 0.0f, -15.0f} + {-5.0f, -0.5f, 12.0f}, + {15.0f, -0.5f, 0.5f}, + {6.0f, -0.5f, -15.0f} }}; // build the collectables @@ -187,6 +191,8 @@ static void setupEnvironment(fggl::ecs3::World& world, const Prefabs prefabs, fg transform->origin(pos); } } + + return player; } enum camera_type { cam_free, cam_arcball }; @@ -209,9 +215,9 @@ static void process_camera(fggl::ecs3::World& ecs, const fggl::input::Input& inp motion -= (forward * delta); camTransform->origin( camTransform->origin() + motion ); - if ( cam_mode == cam_arcball || input.mouse.down( fggl::input::MouseButton::MIDDLE ) ) { + if ( input.mouse.down( fggl::input::MouseButton::MIDDLE ) ) { fggl::input::process_arcball(ecs, input, cam); - } else if ( cam_mode == cam_free ) { + } else { fggl::input::process_freecam(ecs, input, cam); } fggl::input::process_edgescroll( ecs, input, cam ); @@ -233,12 +239,64 @@ namespace demo { // create a 20x20 grid fggl::math::vec2 size{40.0f, 40.0f}; - setupEnvironment(world(), prefabs, size); + player = setupEnvironment(world(), prefabs, size); } void RollBall::update() { Game::update(); - process_camera(world(), input()); + auto& input = this->input(); + + if ( input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_F1))) { + process_camera(world(), input); + } else { + if ( player != fggl::ecs3::NULL_ENTITY ) { + auto &world = this->world(); + + const fggl::math::vec3 forward = fggl::math::FORWARD; + const fggl::math::vec3 right = fggl::math::RIGHT; + fggl::math::vec3 force = fggl::math::VEC3_ZERO; + bool moved = false; + + if (input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_W))) { + force += forward; + moved = true; + } + + if (input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_S))) { + force -= forward; + moved = true; + } + + if (input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_A))) { + force -= right; + moved = true; + } + + if (input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_D))) { + force += right; + moved = true; + } + + // setup dynamic force + if ( moved ) { + float forceFactor = 3.0F; + force = glm::normalize(force) * forceFactor; + auto *dynamics = world.get<fggl::phys::Dynamics>(player); + dynamics->force += force; + } + + { + auto cameras = world.findMatching<fggl::gfx::Camera>(); + fggl::ecs3::entity_t cam = cameras[0]; + auto *camComp = world.get<fggl::gfx::Camera>(cam); + camComp->target = world.get<fggl::math::Transform>(player)->origin(); + + auto *camTransform = world.get<fggl::math::Transform>(cam); + camTransform->origin( camComp->target - (fggl::math::FORWARD * 15.0F) + (fggl::math::UP * 15.0F) ); + } + + } + } } void RollBall::render(fggl::gfx::Graphics &gfx) { diff --git a/demo/include/rollball.hpp b/demo/include/rollball.hpp index 755c9fb..73821df 100644 --- a/demo/include/rollball.hpp +++ b/demo/include/rollball.hpp @@ -38,6 +38,9 @@ namespace demo { void update() override; void render(fggl::gfx::Graphics& gfx) override; + + private: + fggl::ecs3::entity_t player = fggl::ecs3::NULL_ENTITY; }; } diff --git a/fggl/phys/bullet/simulation.cpp b/fggl/phys/bullet/simulation.cpp index 381ac73..9e028e0 100644 --- a/fggl/phys/bullet/simulation.cpp +++ b/fggl/phys/bullet/simulation.cpp @@ -37,8 +37,24 @@ namespace fggl::phys::bullet { void BulletPhysicsEngine::step() { checkForPhys(); + + auto dynamicEnts = m_ecs->findMatching<phys::Dynamics>(); + for (auto& ent : dynamicEnts) { + auto* dynamicComp = m_ecs->get<phys::Dynamics>(ent); + auto* bulletProxy = m_ecs->get<phys::bullet::BulletBody>(ent); + if ( bulletProxy == nullptr ) { + std::cerr << "entity has dynamics but isn't a physics object!" << std::endl; + continue; + } + + const auto forceVec = dynamicComp->force; + bulletProxy->body->applyCentralForce( {forceVec.x, forceVec.y, forceVec.z} ); + dynamicComp->force = math::VEC3_ZERO; + } + m_world->stepSimulation(60.0f); syncToECS(); + dealWithCollisions(); } static void build_motation_state(math::Transform* myState, BulletBody* btState) { @@ -107,19 +123,21 @@ namespace fggl::phys::bullet { colShape, localInt ); - btComp->body = new btRigidBody(bodyCI); + auto* body = new btRigidBody(bodyCI); + body->setUserIndex(static_cast<int>(ent)); + + btComp->body = body; if (!fgBody->isStatic()) { - // FIXME this is for testing, needs to be removed - btComp->body->setRestitution(0.5f); - btComp->body->setRollingFriction(0.0f); + body->setRestitution(0.5f); + body->setRollingFriction(0.0f); } - m_world->addRigidBody(btComp->body); - if ( !fgBody->isStatic() ) { - btComp->body->applyTorque({50.0f, 0.5f, 0.5f}); - btComp->body->applyImpulse({5.0f, 0.5f, 0.5f}, {0.0f, 0.0f, 0.0f}); + if ( fgBody->type == BodyType::KINEMATIC ) { + body->setCollisionFlags( body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT ); } + + m_world->addRigidBody(btComp->body ); } } @@ -165,4 +183,69 @@ namespace fggl::phys::bullet { delete m_config.collisionConfiguration; } + static void handleCollisionCallbacks(ecs3::World* world, ecs3::entity_t owner, ecs3::entity_t other) { + auto* callbacks = world->tryGet<CollisionCallbacks>(owner); + if ( callbacks == nullptr ) { + return; + } + + auto* cache = world->tryGet<CollisionCache>(owner); + if ( cache != nullptr ) { + auto itr = cache->collisions.find(other); + if ( itr == cache->collisions.end() ) { + if ( callbacks->onEnter != nullptr ) { + callbacks->onEnter(other); + } + cache->collisions.insert( other ); + } else { + if ( callbacks->onStay != nullptr ) { + callbacks->onStay(other); + } + } + } + } + + void BulletPhysicsEngine::dealWithCollisions() { + // flush collision caches + auto caches = m_ecs->findMatching<CollisionCache>(); + for( auto& ent : caches) { + auto* cache = m_ecs->get<CollisionCache>(ent); + std::swap(cache->collisions, cache->lastFrame); + cache->collisions.clear(); + } + + // deal with the number of manifolds + const auto numManifolds = m_config.dispatcher->getNumManifolds(); + for ( int i=0; i< numManifolds; ++i) { + auto* contactManifold = m_config.dispatcher->getManifoldByIndexInternal(i); + + const auto* body0 = contactManifold->getBody0(); + const auto* body1 = contactManifold->getBody1(); + + handleCollisionCallbacks(m_ecs, body0->getUserIndex(), body1->getUserIndex()); + handleCollisionCallbacks(m_ecs, body1->getUserIndex(), body0->getUserIndex()); + + std::cerr << "contact: " << body0->getUserIndex() << " on " << body1->getUserIndex() << std::endl; + } + + // note conacts that have ended + for( auto& ent : caches) { + auto* cache = m_ecs->get<CollisionCache>(ent); + + // if we don't have an exit callback, it's pointless checking the cache + auto* callbacks = m_ecs->tryGet<CollisionCallbacks>(ent); + if ( callbacks == nullptr || callbacks->onExit == nullptr){ + continue; + } + + // check if a collision has ended + for (const auto& other : cache->lastFrame) { + auto itr = cache->collisions.find(other); + if (itr == cache->collisions.end()) { + callbacks->onExit(other); + } + } + } + } + } // namespace fggl::phys::bullet \ No newline at end of file diff --git a/include/fggl/ecs3/prototype/world.h b/include/fggl/ecs3/prototype/world.h index 75969c3..7b63348 100644 --- a/include/fggl/ecs3/prototype/world.h +++ b/include/fggl/ecs3/prototype/world.h @@ -205,13 +205,12 @@ namespace fggl::ecs3::prototype { } template<typename C> - C *get(entity_t entity_id) const { + C* tryGet(entity_t entity_id) const { try { - auto &entity = m_entities.at(entity_id); + const auto &entity = m_entities.at(entity_id); try { return entity.get<C>(); } catch ( std::out_of_range& e ) { - std::cerr << "entity " << entity_id << " does not have component "<< C::name << std::endl; return nullptr; } } catch ( std::out_of_range& e) { @@ -220,6 +219,15 @@ namespace fggl::ecs3::prototype { } } + template<typename C> + C *get(entity_t entity_id) const { + C* ptr = tryGet<C>(entity_id); + if (ptr == nullptr) { + std::cerr << "entity " << entity_id << " does not have component "<< C::name << std::endl; + } + return ptr; + } + template<typename C> void remove(entity_t entity_id) { try { diff --git a/include/fggl/phys/bullet/bullet.hpp b/include/fggl/phys/bullet/bullet.hpp index d377796..b50326c 100644 --- a/include/fggl/phys/bullet/bullet.hpp +++ b/include/fggl/phys/bullet/bullet.hpp @@ -48,8 +48,11 @@ namespace fggl::phys::bullet { return "phys::Bullet3"; } - void onLoad(ecs3::ModuleManager &manager, ecs3::TypeRegistry &types) override { + void onLoad(ecs3::ModuleManager & /*manager*/, ecs3::TypeRegistry &types) override { // dependencies + types.make<phys::CollisionCallbacks>(); + types.make<phys::CollisionCache>(); + types.make<phys::RigidBody>(); types.make<phys::Dynamics>(); diff --git a/include/fggl/phys/bullet/types.hpp b/include/fggl/phys/bullet/types.hpp index 31d9a94..f06b858 100644 --- a/include/fggl/phys/bullet/types.hpp +++ b/include/fggl/phys/bullet/types.hpp @@ -75,6 +75,11 @@ namespace fggl::phys::bullet { * Sync the bullet world state back to the ECS. */ void syncToECS(); + + /** + * Deal with physics collisions + */ + void dealWithCollisions(); }; } // namespace fggl::phys::bullet diff --git a/include/fggl/phys/callbacks.hpp b/include/fggl/phys/callbacks.hpp new file mode 100644 index 0000000..cc14483 --- /dev/null +++ b/include/fggl/phys/callbacks.hpp @@ -0,0 +1,50 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// +// Created by webpigeon on 30/04/22. +// + +#ifndef FGGL_PHYS_CALLBACKS_HPP +#define FGGL_PHYS_CALLBACKS_HPP + +#include <functional> +#include <unordered_set> + +namespace fggl::phys { + + using CollisionCB = std::function<void(ecs3::entity_t)>; + + struct CollisionCallbacks { + constexpr static const char* name = "phys::Callbacks"; + CollisionCB onEnter = nullptr; + CollisionCB onExit = nullptr; + CollisionCB onStay = nullptr; + }; + + struct CollisionCache { + constexpr static const char* name = "phys::Cache"; + std::unordered_set<ecs3::entity_t> collisions; + std::unordered_set<ecs3::entity_t> lastFrame; + }; + +} // namespace fggl::phys + +#endif //FGGL_PHYS_CALLBACKS_HPP diff --git a/include/fggl/phys/types.hpp b/include/fggl/phys/types.hpp index 4f06ade..3b15ec6 100644 --- a/include/fggl/phys/types.hpp +++ b/include/fggl/phys/types.hpp @@ -25,6 +25,8 @@ #ifndef FGGL_PHYS_TYPES_HPP #define FGGL_PHYS_TYPES_HPP +#include "fggl/phys/callbacks.hpp" + namespace fggl::phys { constexpr float MASS_STATIC = 0.0F; constexpr float MASS_DEFAULT = 1.0F; @@ -52,10 +54,15 @@ namespace fggl::phys { explicit inline Sphere(float rad) : PhyShape(ShapeType::SPHERE), radius(rad) {} }; + enum class BodyType { + STATIC, KINEMATIC, DYNAMIC + }; + struct RigidBody { constexpr static const char* name = "phys::Body"; float mass = MASS_DEFAULT; PhyShape* shape = nullptr; + BodyType type = BodyType::DYNAMIC; [[nodiscard]] inline bool isStatic() const { -- GitLab