diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 4c8f3dd202b14636fd571d4cf2e6248d56c5dd4b..27ae4180c0106f017e390fb8a3156700e7d96b06 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(demo demo/main.cpp demo/GameScene.cpp demo/rollball.cpp + demo/topdown.cpp ) diff --git a/demo/data/topdown.yml b/demo/data/topdown.yml new file mode 100644 index 0000000000000000000000000000000000000000..defd6c044a4be342a64fa5e4ab40e4d2d9811122 --- /dev/null +++ b/demo/data/topdown.yml @@ -0,0 +1,76 @@ +--- +prefabs: + - name: "wallX" + components: + Transform: + StaticMesh: + pipeline: phong + shape: + type: box + scale: [1.0, 5.0, 41] + phys::Body: + type: static + shape: + type: box + extents: [0.5, 2.5, 20.5] + # Wall Z shorter to avoid z-fighting + - name: "wallZ" + components: + Transform: + StaticMesh: + pipeline: phong + shape: + type: box + scale: [39, 5, 1] + phys::Body: + type: static + shape: + type: box + extents: [ 19.5, 2.5, 0.5 ] + - name: "floor" + components: + Transform: + StaticMesh: + pipeline: phong + shape: + type: box # we don't (currently) support planes... + scale: [39, 0.5, 39] + phys::Body: + type: static + shape: + type: box # we don't (currently) support planes... + extents: [19.5, 0.25, 19.5] + - name: player + components: + Transform: + StaticMesh: + pipeline: phong + shape: + type: sphere + gfx::material: + ambient: [0.25, 0.25, 0.25] + diffuse: [0.4, 0.4, 0.4] + specular: [0.774597,0.774597,0.774597] + shininess: 0.6 + phys::Body: + shape: + type: sphere + radius: 1 + - name: collectable + components: + Transform: + StaticMesh: + pipeline: phong + shape: + type: box + gfx::material: + ambient: [0.0215, 0.1754, 0.0215] + diffuse: [1, 1, 1] + specular: [0.0633, 0.727811, 0.633] + shininess: 0.6 + phys::Body: + type: kinematic + shape: + type: box + phys::Callbacks: + phys::Cache: \ No newline at end of file diff --git a/demo/demo/main.cpp b/demo/demo/main.cpp index 3bc9aff741ec7032b3a2995a5b8ccf59c695b157..2783ab2fd9fb226744895002b4f2e2616df5bb5c 100644 --- a/demo/demo/main.cpp +++ b/demo/demo/main.cpp @@ -41,6 +41,7 @@ #include "GameScene.h" #include "rollball.hpp" +#include "topdown.hpp" static void setup_menu(fggl::App& app) { auto *menu = app.addState<fggl::scenes::BasicMenu>("menu"); @@ -58,6 +59,12 @@ static void setup_menu(fggl::App& app) { app.change_state("rollball"); }); + menu->add("Top Down", [&app]() { + auto* audio = app.service<fggl::audio::AudioService>(); + audio->play("click.ogg", false); + app.change_state("topdown"); + }); + menu->add("quit", [&app]() { auto* audio = app.service<fggl::audio::AudioService>(); audio->play("click.ogg", false); @@ -100,6 +107,7 @@ int main(int argc, const char* argv[]) { setup_menu(app); app.addState<GameScene>("game"); app.addState<demo::RollBall>("rollball"); + app.addState<demo::TopDown>("topdown"); return app.run(argc, argv); } diff --git a/demo/demo/topdown.cpp b/demo/demo/topdown.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25f7a239a7d11217244bb59a85c47ec7af47cdd7 --- /dev/null +++ b/demo/demo/topdown.cpp @@ -0,0 +1,161 @@ +/* + * 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 03/07/22. +// + +#include "topdown.hpp" + +#include "fggl/data/storage.hpp" +#include "fggl/gfx/camera.hpp" +#include "fggl/input/camera_input.hpp" +#include "fggl/ecs3/prototype/loader.hpp" + +namespace demo { + + static void create_topdown_camera(fggl::ecs3::World& world) { + auto prototype = world.create(false); + + // setup camera position/transform + auto* transform = world.add<fggl::math::Transform>(prototype); + if ( transform != nullptr) { + transform->origin(glm::vec3(10.0f, 50.0f, 10.0f)); + } + + // setup camera components + auto* camera = world.add<fggl::gfx::Camera>(prototype); + camera->target = glm::vec3(0.0f, 0.0f, 0.0f); + + auto* cameraKeys = world.add<fggl::input::FreeCamKeys>(prototype); + if ( cameraKeys != nullptr ) { + cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W); + cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S); + cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A); + cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D); + cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q); + cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E); + } + } + + static void place_cover_boxes(fggl::ecs3::World& world) { + std::array<fggl::math::vec3,8> boxPos = {{ + {-10.0F, 0.0F, -10.0F}, + {-10.0F, 0.0F, 10.0F}, + { 10.0F, 0.0F, -10.0F}, + { 10.0F, 0.0F, 10.0F}, + {-10.0F, 0.0F, 0.0F}, + { 0.0F, 0.0F, -10.0F}, + { 10.0F, 0.0F, 0.0F}, + { 0.0F, 0.0F, 10.0F}, + }}; + for (auto pos : boxPos) { + auto box = world.createFromPrototype("collectable"); + auto* transform = world.get<fggl::math::Transform>(box); + transform->origin(pos); + } + } + + static void build_arena(fggl::ecs3::World& world) { + { + auto floor = world.createFromPrototype("floor"); + auto* transform = world.get<fggl::math::Transform>(floor); + transform->origin({0.0F, -2.5F, 0.0F}); + fggl::debug::log("created floor: {}", floor); + } + + fggl::math::vec2 size{40.0F, 40.0F}; + for (auto side : {-1.0F, 1.0F}) + { + { + auto northWall = world.createFromPrototype("wallX"); + auto *transform = world.get<fggl::math::Transform>(northWall); + transform->origin({size.x / 2 * side, 0.0F, 0.0F}); + } + { + auto westWall = world.createFromPrototype("wallZ"); + auto* transform = world.get<fggl::math::Transform>(westWall); + transform->origin({0.0F, 0.0F, size.y/2 * side }); + } + } + + place_cover_boxes(world); + } + +static void process_camera(fggl::ecs3::World& ecs, const fggl::input::Input& input) { + auto cameras = ecs.findMatching<fggl::gfx::Camera>(); + if ( !cameras.empty() ) { + fggl::ecs3::entity_t cam = cameras[0]; + fggl::input::process_scroll(ecs, input, cam); + fggl::input::process_freecam(ecs, input, cam); + fggl::input::process_edgescroll(ecs, input, cam); + } +} + +static void populate_sample_level(fggl::ecs3::World& world) { + create_topdown_camera(world); + + build_arena(world); +} + +TopDown::TopDown(fggl::App& app) : fggl::scenes::Game(app) { + +} + +void TopDown::activate() { + Game::activate(); + + auto* storage = m_owner.service<fggl::data::Storage>(); + fggl::ecs3::load_prototype_file(world(), *storage, "topdown.yml"); + + // create a sample level + populate_sample_level(world()); +} + +void TopDown::update() { + Game::update(); + + process_camera(world(), input()); + if ( input().mouse.pressed(fggl::input::MouseButton::LEFT) ) { + pick_object(); + } +} + +void TopDown::pick_object() { + auto cameras = world().findMatching<fggl::gfx::Camera>(); + if ( cameras.empty() ) { + return; + } + + fggl::ecs3::entity_t cam = cameras[0]; + + fggl::math::vec2 position { + input().mouse.axis(fggl::input::MouseAxis::X), + input().mouse.axis(fggl::input::MouseAxis::Y), + }; + + auto ray = fggl::gfx::get_camera_ray(world(), cam, position); + auto hit = phys().raycast(ray); + if ( hit != fggl::ecs3::NULL_ENTITY) { + fggl::debug::log("hit: {}", hit); + } else { + fggl::debug::log("no hit"); + } +} + +void TopDown::render(fggl::gfx::Graphics& gfx) { + Game::render(gfx); +} + +} \ No newline at end of file diff --git a/demo/include/topdown.hpp b/demo/include/topdown.hpp new file mode 100644 index 0000000000000000000000000000000000000000..af4642ff3268f25c2127b22a4ffdd6f48b5d4171 --- /dev/null +++ b/demo/include/topdown.hpp @@ -0,0 +1,45 @@ +/* + * 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 03/07/22. +// + +#ifndef DEMO_TOPDOWN_HPP +#define DEMO_TOPDOWN_HPP + +#include "fggl/scenes/game.hpp" + +namespace demo { + + class TopDown : public fggl::scenes::Game { + + public: + explicit TopDown(fggl::App& app); + void activate() override; + + void update() override; + void render(fggl::gfx::Graphics& gfx) override; + + private: + constexpr static fggl::math::vec3 HINT_COLOUR{0.5f, 0.0f, 0.0f}; + fggl::math::vec3 cameraOffset = {-15.0F, 15.0F, 0.0F}; + + void pick_object(); + + }; + +} + +#endif //DEMO_TOPDOWN_HPP diff --git a/fggl/gfx/ogl4/models.cpp b/fggl/gfx/ogl4/models.cpp index 4cf44b4660563b7147ab6c946ee0d9df41919e59..ed30c5c2cd53d1b200d3df41436e6f1b0d9ba666 100644 --- a/fggl/gfx/ogl4/models.cpp +++ b/fggl/gfx/ogl4/models.cpp @@ -137,25 +137,29 @@ namespace fggl::gfx::ogl4 { // TODO lighting needs to not be this... math::vec3 lightPos{0.0f, 10.0f, 0.0f}; + std::shared_ptr<ogl::Shader> shader = nullptr; auto renderables = world.findMatching<StaticModel>(); for ( const auto& entity : renderables ){ - auto* transform = world.get<math::Transform>(entity); + // ensure that the model pipeline actually exists... StaticModel* model = world.get<StaticModel>(entity); - - // grouping by shader would mean we only need to send the model matrix... - // TODO clean shader API - auto shader = model->pipeline; - if ( shader == nullptr ) { + if ( model->pipeline == nullptr ) { spdlog::warn("shader was null, aborting render"); continue; } - // setup shader uniforms - shader->use(); + // check if we switched shaders + if ( shader != model->pipeline ) { + // new shader - need to re-send the view and projection matrices + shader = model->pipeline; + shader->use(); + shader->setUniformMtx(shader->uniform("view"), viewMatrix); + shader->setUniformMtx(shader->uniform("projection"), projectionMatrix); + } + + // set model transform + auto* transform = world.get<math::Transform>(entity); shader->setUniformMtx(shader->uniform("model"), transform->model()); - shader->setUniformMtx(shader->uniform("view"), viewMatrix); - shader->setUniformMtx(shader->uniform("projection"), projectionMatrix); // material detection with fallback auto* material = &gfx::DEFAULT_MATERIAL; diff --git a/fggl/input/camera_input.cpp b/fggl/input/camera_input.cpp index 24afeab09dfef63d47a14b943bfd4cfea2cfd9ce..e0b04140ad97d30ec5ff7975fdcdfd6466d35fb9 100644 --- a/fggl/input/camera_input.cpp +++ b/fggl/input/camera_input.cpp @@ -54,6 +54,21 @@ namespace fggl::input { camTransform->origin(finalPos); } + void process_scroll(fggl::ecs3::World &ecs, const Input &input, fggl::ecs::entity_t cam, float minZoom, float maxZoom){ + auto* camTransform = ecs.get<fggl::math::Transform>(cam); + auto* camComp = ecs.get<fggl::gfx::Camera>(cam); + + const glm::vec3 dir = ( camTransform->origin() - camComp->target ); + const glm::vec3 forward = glm::normalize( dir ); + + glm::vec3 motion(0.0F); + float delta = input.mouse.axis( fggl::input::MouseAxis::SCROLL_Y ); + if ( (glm::length( dir ) < maxZoom && delta < 0.0f) || (glm::length( dir ) > minZoom && delta > 0.0f) ) { + motion -= (forward * delta); + camTransform->origin(camTransform->origin() + motion); + } + } + void process_freecam(fggl::ecs3::World &ecs, const Input &input, fggl::ecs::entity_t cam) { float rotationValue = 0.0f; glm::vec3 translation(0.0f); diff --git a/include/fggl/assets/loader.hpp b/include/fggl/assets/loader.hpp new file mode 100644 index 0000000000000000000000000000000000000000..be85581b13aa1d9da64a987f9edb86a613aef866 --- /dev/null +++ b/include/fggl/assets/loader.hpp @@ -0,0 +1,118 @@ +/* + * 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 03/07/22. +// + +#ifndef FGGL_ASSETS_LOADER_HPP +#define FGGL_ASSETS_LOADER_HPP + +#include <map> +#include <string> +#include <memory> +#include <functional> +#include <queue> +#include <variant> + +#include "fggl/assets/types.hpp" +#include "fggl/data/storage.hpp" + +namespace fggl::assets { + + enum class LoadType { + DIRECT, // given pointer to persistent memory + STAGED, // given pointer to temp memory + PATH // given filesystem::path + }; + + struct ResourceRequest { + AssetGUID m_guid; + AssetType m_type; + }; + + class Loader { + public: + constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::Loader"); + explicit inline Loader(data::Storage* storage) : m_storage(storage), m_parent(nullptr) {} + explicit Loader(Loader* parent, data::Storage* storage) : m_parent(parent), m_storage(storage) {}; + + // no move, no copy. + Loader(const Loader&) = delete; + Loader& operator=(const Loader&) = delete; + Loader(Loader&&) = delete; + Loader& operator=(Loader&&) = delete; + + inline void setFactory(AssetType type, Checkin fn, LoadType loading = LoadType::DIRECT) { + m_factories[type] = std::make_pair(fn, loading); + } + inline void unsetFactory(AssetType type) { + m_factories.erase(type); + } + + inline void request(const AssetGUID& guid, const AssetType& type) { + m_requests.push(ResourceRequest{guid, type}); + } + + void load(const AssetGUID guid, const AssetType& type) { + auto path = m_storage->resolvePath(data::StorageType::Data, guid); + + auto& config = m_factories.at(type); + switch (config.second) { + case LoadType::DIRECT: + // TODO we load the data into main memory and give a pointer to it. + break; + case LoadType::STAGED: + // TODO we load the data into temp memory and give a pointer to it. + break; + case LoadType::PATH: + config.first(guid, AssetData(&path)); + break; + } + } + + void progress() { + if (m_requests.empty()) { + return; + } + auto& request = m_requests.front(); + load(request.m_guid, request.m_type); + m_requests.pop(); + } + + [[nodiscard]] + inline bool done() const { + return m_requests.empty(); + } + + /** + * Complete all remaining loading requests, blocking until complete. + */ + inline void finish() { + while ( !done() ) { + progress(); + } + } + + private: + Loader* m_parent = nullptr; + using Config = std::pair<Checkin, LoadType>; + data::Storage* m_storage; + std::queue<ResourceRequest> m_requests; + std::map<AssetType, Config> m_factories; + }; + +} // namespace fggl::assets + +#endif //FGGL_ASSETS_LOADER_HPP diff --git a/include/fggl/assets/manager.hpp b/include/fggl/assets/manager.hpp index 40cdf39cabffa672bed972415952e77b9d77d43f..9963ec48497f1ffb92aa0d0131511303db03396e 100644 --- a/include/fggl/assets/manager.hpp +++ b/include/fggl/assets/manager.hpp @@ -24,42 +24,54 @@ #include <functional> #include <memory> -#include "fggl/data/storage.hpp" +#include "fggl/assets/types.hpp" #include "fggl/util/safety.hpp" namespace fggl::assets { - struct AssetTag{}; - using AssetType = util::OpaqueName<std::string_view, AssetTag>; - - struct AssetData { - void* data; - std::size_t size; - }; - - struct AssetCallbacks { - std::function<void(const AssetType&, AssetData)> init; - std::function<void(const AssetType&, AssetData)> destroy; - }; - - struct Asset{}; - class AssetManager { public: - constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::AssetModule"); + constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::Manager"); using AssetGUID = std::string; - AssetManager(data::Storage* storage) : m_storage(storage) {} + AssetManager() = default; virtual ~AssetManager() = default; - void load(const AssetType&, const AssetGUID& name); - void loadToTemp(const AssetType&, const AssetGUID& name); - void unload(const AssetGUID& name); + // no move, no copy. + AssetManager(const AssetManager&) = delete; + AssetManager& operator=(const AssetManager&) = delete; + AssetManager(AssetManager&&) = delete; + AssetManager& operator=(AssetManager&&) = delete; + + inline AssetRefRaw getRaw(const AssetGUID& guid) const { + return m_registry.at(guid).asset; + } + + template<typename T> + AssetRef<T> get(const AssetGUID& guid) const { + return std::dynamic_pointer_cast<T>(getRaw(guid)); + } + + inline void require(const AssetGUID& guid) { + m_registry.at(guid).refCount++; + } + + void release(const AssetGUID& guid) { + m_registry.at(guid).refCount--; + } private: - data::Storage* m_storage; - std::map<AssetGUID, std::shared_ptr<Asset>> m_registry; - std::map<AssetType, AssetCallbacks> m_callbacks; + struct AssetRecord { + std::shared_ptr<Asset> asset = nullptr; + std::size_t refCount; + + inline ~AssetRecord(){ + if ( asset != nullptr ) { + asset->release(); + } + } + }; + std::map<AssetGUID, AssetRecord> m_registry; }; diff --git a/include/fggl/assets/module.hpp b/include/fggl/assets/module.hpp index a4079cba04eacd6fab0f359dcebc8983fc8f7437..a925f64969182d7186e20d3a2e0e5a3fce9517be 100644 --- a/include/fggl/assets/module.hpp +++ b/include/fggl/assets/module.hpp @@ -22,12 +22,14 @@ #include "fggl/modules/module.hpp" #include "fggl/data/module.hpp" #include "fggl/assets/manager.hpp" +#include "fggl/assets/loader.hpp" namespace fggl::assets { struct AssetFolders { constexpr static const char* name = "fggl::assets::Folders"; - constexpr static const std::array<modules::ModuleService, 1> provides = { + constexpr static const std::array<modules::ModuleService, 2> provides = { + Loader::service, AssetManager::service }; constexpr static const std::array<modules::ModuleService, 1> depends = { @@ -37,9 +39,13 @@ namespace fggl::assets { }; bool asset_factory(modules::ModuleService service, modules::Services& services) { - if (service == AssetManager::service) { + if ( service == Loader::service) { auto storage = services.get<data::Storage>(); - services.create<AssetManager>(storage); + services.create<Loader>(storage); + return true; + } + if (service == AssetManager::service) { + services.create<AssetManager>(); return true; } return false; diff --git a/include/fggl/assets/types.hpp b/include/fggl/assets/types.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d943d632c6a61216e1809c9d83db574ca0a76b7a --- /dev/null +++ b/include/fggl/assets/types.hpp @@ -0,0 +1,50 @@ +/* + * 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 03/07/22. +// + +#ifndef FGGL_ASSETS_TYPES_HPP +#define FGGL_ASSETS_TYPES_HPP + +#include <filesystem> +#include "fggl/util/safety.hpp" + +namespace fggl::assets{ + using AssetType = util::OpaqueName<std::string_view, struct AssetTag>; + using AssetGUID = std::string; + using AssetPath = std::filesystem::path; + + struct Asset { + AssetType m_type; + virtual void release() = 0; + virtual bool active() = 0; + }; + + struct MemoryBlock { + void* data; + std::size_t size; + }; + + using AssetRefRaw = std::shared_ptr<Asset>; + + template<typename T> + using AssetRef = std::shared_ptr<T>; + + using AssetData = std::variant<MemoryBlock, AssetPath*, FILE*>; + using Checkin = std::function<AssetRefRaw(const AssetGUID&, const AssetData&)>; +} + +#endif //FGGL_ASSETS_TYPES_HPP diff --git a/include/fggl/ecs3/prototype/world.hpp b/include/fggl/ecs3/prototype/world.hpp index db733e07337995f06b4a679564852218f0599c9a..9e0a62a9fa44ab8de5e0f6cf38cc0665908af84c 100644 --- a/include/fggl/ecs3/prototype/world.hpp +++ b/include/fggl/ecs3/prototype/world.hpp @@ -139,7 +139,12 @@ namespace fggl::ecs3::prototype { } inline entity_t createFromPrototype(const std::string& name) { - return copy(findPrototype(name) ); + auto prototype = findPrototype(name); + if ( prototype == NULL_ENTITY) { + debug::log(debug::Level::warning, "attempted to create from non-existant prototype: {}", name); + return NULL_ENTITY; + } + return copy( prototype ); } entity_t copy(entity_t prototype) { diff --git a/include/fggl/gfx/camera.hpp b/include/fggl/gfx/camera.hpp index a03d430b334a3435e8f70b99d69396b9f20aeecd..c1fcf989dcd5a2ff2c651cf5e97d75c26557fb9d 100644 --- a/include/fggl/gfx/camera.hpp +++ b/include/fggl/gfx/camera.hpp @@ -28,6 +28,36 @@ namespace fggl::gfx { float farPlane = 100.0f; }; + inline math::mat4 calc_proj_matrix(const Camera* camera) { + return glm::perspective(camera->fov, camera->aspectRatio, camera->nearPlane, camera->farPlane); + } + + inline math::Ray get_camera_ray(const ecs3::World& world, const ecs3::entity_t camera, math::vec2 position) { + auto* const camTransform = world.get<fggl::math::Transform>(camera); + auto* const camComp = world.get<fggl::gfx::Camera>(camera); + + const auto projMatrix = fggl::gfx::calc_proj_matrix(camComp); + const auto viewMatrix = fggl::math::calc_view_matrix(camTransform); + + glm::vec4 startNDC { + position.x, + position.y, + -1.0f, + 1.0f + }; + glm::vec4 endNDC { + position.x, + position.y, + 0.0f, + 1.0f + }; + + fggl::math::mat4 M = glm::inverse( projMatrix * viewMatrix ); + glm::vec3 start = M * startNDC; + glm::vec3 end = M * endNDC; + return { start, glm::normalize(end - start) }; + } + }; #endif diff --git a/include/fggl/input/camera_input.hpp b/include/fggl/input/camera_input.hpp index 1550a65a149187162f7d82b3a7817d1ba4732de3..db85605464e4d9021c2cbe19eb373f546a940e2f 100644 --- a/include/fggl/input/camera_input.hpp +++ b/include/fggl/input/camera_input.hpp @@ -38,6 +38,8 @@ namespace fggl::input { scancode_t rotate_ccw; }; + void process_scroll(fggl::ecs3::World &ecs, const Input &input, fggl::ecs::entity_t cam, float minZoom = 10.0F, float maxZoom = 50.0F); + /** * Process the camera based on rotation around a fixed point. * diff --git a/include/fggl/math/types.hpp b/include/fggl/math/types.hpp index 834955723d4bdb27e04248a5bcaa13868c1a1f33..ff88aaed52d8318d43a63d398c1b3e9a31816535 100644 --- a/include/fggl/math/types.hpp +++ b/include/fggl/math/types.hpp @@ -113,6 +113,11 @@ namespace fggl::math { return modelMatrix(offset, glm::quat(eulerAngles)); } + struct Ray { + vec3 origin; + vec3 direction; + }; + struct Transform { constexpr static const char name[] = "Transform"; @@ -217,6 +222,15 @@ namespace fggl::math { vec3 m_scale; }; + + inline math::mat4 calc_view_matrix(const Transform* transform) { + return glm::lookAt(transform->origin(), transform->origin() + transform->forward(), transform->up()); + } + + inline math::mat4 calc_view_matrix(const Transform* transform, vec3 target) { + return glm::lookAt(transform->origin(), target, transform->up()); + } + } // feels a bit strange to be doing this... diff --git a/include/fggl/phys/types.hpp b/include/fggl/phys/types.hpp index bbc2b468716caa194f08d254e991dd3470d46597..8d6bdee5989c67aa08eb0983c7646457e1612e10 100644 --- a/include/fggl/phys/types.hpp +++ b/include/fggl/phys/types.hpp @@ -106,6 +106,12 @@ namespace fggl::phys { // query methods (first cut - unstable APIs) virtual std::vector<ContactPoint> scanCollisions(ecs3::entity_t entity) = 0; virtual ecs3::entity_t raycast(math::vec3 from, math::vec3 to) = 0; + + inline ecs3::entity_t raycast(math::Ray ray, float maxDist = 1000.0F) { + return raycast(ray.origin, ray.origin + ray.direction * maxDist); + } + + virtual std::vector<ecs3::entity_t> raycastAll(math::vec3 from, math::vec3 to) = 0; virtual std::vector<ecs3::entity_t> sweep(PhyShape& shape, math::Transform& from, math::Transform& to) = 0; diff --git a/include/fggl/scenes/game.hpp b/include/fggl/scenes/game.hpp index 6d88372dfa32ee2960d9744f53de7d7c92146274..59970604fc2a67dd6245462cac6b1a349403b069 100644 --- a/include/fggl/scenes/game.hpp +++ b/include/fggl/scenes/game.hpp @@ -40,6 +40,10 @@ namespace fggl::scenes { return *m_world; } + inline auto phys() -> phys::PhysicsEngine& { + return *m_phys; + } + inline auto input() -> input::Input& { return *m_input; }