diff --git a/CMakeLists.txt b/CMakeLists.txt index a3c43afa8408714259ec920c47f0c70b913604d2..181ae3f6bba89fcf9f6298b7ed6fbd16f89ce774 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) freetype/2.11.1 bullet3/3.22a openal/1.21.1 + yaml-cpp/0.7.0 GENERATORS cmake_find_package cmake_find_package_multi diff --git a/demo/data/rollball.yml b/demo/data/rollball.yml new file mode 100644 index 0000000000000000000000000000000000000000..0a58e7fdf0dc848b9c31cc057987ed669b6d0279 --- /dev/null +++ b/demo/data/rollball.yml @@ -0,0 +1,64 @@ +--- +prefabs: + - name: "wallX" + components: + Transform: + StaticMesh: + pipeline: phong + shape: + type: cube + 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: cube + 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: cube # we don't (currently) support planes... + scale: [40, 0.5, 40] + phys::Body: + type: static + shape: + type: box # we don't (currently) support planes... + extents: [20, 0.25, 20] + - name: player + components: + Transform: + StaticMesh: + pipeline: phong + shape: + type: sphere + phys::Body: + shape: + type: sphere + radius: 1 + - name: collectable + components: + Transform: + StaticMesh: + pipeline: phong + shape: + type: cube + phys::Body: + type: kinematic + shape: + type: box \ No newline at end of file diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp index 97f56cf8828b5b22546d403a1d2f04e9be074b86..bbf1ae4bcaf914b05c3da6036fb463d8419c2ed8 100644 --- a/demo/demo/rollball.cpp +++ b/demo/demo/rollball.cpp @@ -23,6 +23,8 @@ #include "fggl/data/procedural.hpp" #include "fggl/gfx/camera.hpp" #include "fggl/input/camera_input.h" +#include "fggl/util/service.h" +#include "fggl/ecs3/prototype/loader.hpp" struct Prefabs { fggl::ecs3::entity_t wallX; @@ -34,90 +36,22 @@ struct Prefabs { static void setupPrefabs(fggl::ecs3::World& world, Prefabs& prefabs) { - { - // walls - prefabs.wallX = world.create(true); - world.add<fggl::math::Transform>(prefabs.wallX); - - fggl::data::StaticMesh meshComponent; - meshComponent.pipeline = "phong"; + auto storage = fggl::util::ServiceLocator::instance().get<fggl::data::Storage>(); + fggl::ecs3::load_prototype_file(world, *storage, "rollball.yml"); - // create a procedural cube, but scale the model to be a long wall - auto transform = glm::scale(fggl::data::OFFSET_NONE, {1.0f, 5.f, 41.0f}); - fggl::data::make_cube(meshComponent.mesh, transform ); - world.set<fggl::data::StaticMesh>(prefabs.wallX, &meshComponent); - - auto* rb = world.add<fggl::phys::RigidBody>(prefabs.wallX); - rb->mass = fggl::phys::MASS_STATIC; // flag the object as static - rb->shape = new fggl::phys::Box({0.5F, 2.5F, 20.5F}); - } - - { - // walls - prefabs.wallZ = world.create(true); - world.add<fggl::math::Transform>(prefabs.wallZ); - - fggl::data::StaticMesh meshComponent; - meshComponent.pipeline = "phong"; - - // create a procedural cube, but scale the model to be a long wall - auto transform = glm::scale(fggl::data::OFFSET_NONE, {39.f, 5.f, 1.0f}); - fggl::data::make_cube(meshComponent.mesh, transform ); - world.set<fggl::data::StaticMesh>(prefabs.wallZ, &meshComponent); - - auto* rb = world.add<fggl::phys::RigidBody>(prefabs.wallZ); - rb->mass = fggl::phys::MASS_STATIC; // flag the object as static - rb->shape = new fggl::phys::Box({39.0F / 2, 2.5F, 0.5F}); - } - - { - // floor - prefabs.floor = world.create(true); - world.add<fggl::math::Transform>(prefabs.floor); - - // create a procedural cube, but scale it to be plane-like - // TODO proper planes - fggl::data::StaticMesh meshComponent; - meshComponent.pipeline = "phong"; - auto transform = glm::scale(fggl::data::OFFSET_NONE, {40.f, 0.5f, 40.0f}); - fggl::data::make_cube(meshComponent.mesh, transform ); - world.set<fggl::data::StaticMesh>(prefabs.floor, &meshComponent); - - // physics: a static cube - auto* rb = world.add<fggl::phys::RigidBody>(prefabs.floor); - rb->mass = fggl::phys::MASS_STATIC; // flag the object as static - rb->shape = new fggl::phys::Box({20.0F, 0.5F, 20.0F}); - } + prefabs.wallX = world.findProtoype("wallX"); + prefabs.wallZ = world.findProtoype("wallZ"); + prefabs.floor = world.findProtoype("floor"); { // player (cube because my sphere function doesn't exist yet - prefabs.player = world.create(true); - world.add<fggl::math::Transform>(prefabs.player); + prefabs.player = world.findProtoype("player"); world.add<fggl::phys::Dynamics>(prefabs.player); - - auto rb = world.add<fggl::phys::RigidBody>(prefabs.player); - rb->shape = new fggl::phys::Sphere(1.0f); - - fggl::data::StaticMesh meshComponent; - meshComponent.pipeline = "phong"; - fggl::data::make_sphere(meshComponent.mesh); - world.set<fggl::data::StaticMesh>(prefabs.player, &meshComponent); } { // collectable - prefabs.collectable = world.create(true); - world.add<fggl::math::Transform>(prefabs.collectable); - - fggl::data::StaticMesh meshComponent; - meshComponent.pipeline = "phong"; - fggl::data::make_cube(meshComponent.mesh); - world.set<fggl::data::StaticMesh>(prefabs.collectable, &meshComponent); - - // 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; + prefabs.collectable = world.findProtoype("collectable"); // we need both of these for callbacks to trigger. world.add<fggl::phys::CollisionCallbacks>(prefabs.collectable); diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index 7305b5b474370d83bb0b468c004e0c758b9c75b0..c9529e9d8bca4f0f182b53f5f22a79e3d8a6006b 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -60,6 +60,10 @@ target_sources(${PROJECT_NAME} math/shapes.cpp ) +# yaml-cpp for configs and storage +find_package(yaml-cpp) +target_link_libraries(fggl PUBLIC yaml-cpp) + # spdlog for cleaner logging find_package(spdlog) target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog) diff --git a/include/fggl/ecs/component.hpp b/include/fggl/ecs/component.hpp index 4e0e4918e2320f26b6863329e924888c587cd04b..f011adcb4df793b7aba2d5adee095b494095c4b9 100644 --- a/include/fggl/ecs/component.hpp +++ b/include/fggl/ecs/component.hpp @@ -8,12 +8,19 @@ #include <string> #include <new> #include <utility> +#include <iostream> #include <vector> #include <unordered_map> +#include "yaml-cpp/yaml.h" namespace fggl::ecs { + template<typename T> + bool restore_config(T* comp, const YAML::Node& node) { + return false; + } + class ComponentBase { public: using data_t = unsigned char; @@ -28,6 +35,8 @@ namespace fggl::ecs { // virtual virtual void *construct() const = 0; + virtual void *restore(const YAML::Node& config) const = 0; + virtual void *copyConstruct(const void *src) = 0; virtual const char *name() const = 0; @@ -56,6 +65,16 @@ namespace fggl::ecs { new(data) C(); } + virtual void* restore(const YAML::Node& config) const override { + C* ptr = new C(); + bool restored = restore_config<C>(ptr, config); + if ( !restored ) { + std::cerr << "error restoring " << name() << std::endl; + assert( false && "failed to restore configuration when loading type!" ); + } + return ptr; + } + void *copyConstruct(const void *src) override { const C *srcPtr = (C *) src; return new C(*srcPtr); diff --git a/include/fggl/ecs3/prototype/loader.hpp b/include/fggl/ecs3/prototype/loader.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5086ab61c8afb96f3a68af3ca9d1d558011fc2b1 --- /dev/null +++ b/include/fggl/ecs3/prototype/loader.hpp @@ -0,0 +1,213 @@ +/* + * ${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 04/06/22. +// + +#ifndef FGGL_ECS3_PROTOTYPE_LOADER_HPP +#define FGGL_ECS3_PROTOTYPE_LOADER_HPP + +#include "yaml-cpp/yaml.h" +#include "fggl/data/storage.hpp" +#include "fggl/ecs3/ecs.hpp" + +#include "fggl/math/types.hpp" + +namespace fggl::ecs3 { + + + void load_prototype_node( ecs3::World& world, const YAML::Node& node) { + auto prototype = world.create(true); + auto& typeReg = world.types(); + + // store the prototype's name for future use + auto* nameComp = world.get<ecs3::EntityMeta>( prototype ); + nameComp->typeName = node["name"].as<std::string>(); + + // grab the components and iterate them + if ( node["components"] ) { + world.createFromSpec( prototype, node["components"]); + } + } + + void load_prototype_file( ecs3::World& world, data::Storage& storage, const std::string& name ) { + auto path = storage.resolvePath( data::Data, name ); + + auto root = YAML::LoadFile( path ); + for (const auto& node : root["prefabs"] ) { + load_prototype_node(world, node); + } + } + +} // namespace fggl::ecs3 + +namespace YAML { + + template<> + struct convert<fggl::math::vec3> { + static Node encode(const fggl::math::vec3& rhs){ + Node node; + node.push_back(rhs.x); + node.push_back(rhs.y); + node.push_back(rhs.z); + return node; + } + + static bool decode(const Node& node, fggl::math::vec3& rhs) { + if ( !node.IsSequence() || node.size() != 3) { + return false; + } + + rhs.x = node[0].as<float>(); + rhs.y = node[1].as<float>(); + rhs.z = node[2].as<float>(); + return true; + } + }; + + template<> + struct convert<fggl::phys::BodyType> { + const static std::string TYPE_KINEMATIC; + const static std::string TYPE_STATIC; + const static std::string TYPE_DYN; + + static Node encode(const fggl::phys::BodyType& rhs) { + switch (rhs) { + case fggl::phys::BodyType::KINEMATIC: + return Node(TYPE_KINEMATIC); + case fggl::phys::BodyType::STATIC: + return Node(TYPE_STATIC); + default: + case fggl::phys::BodyType::DYNAMIC: + return Node(TYPE_DYN); + } + } + static bool decode(const Node& node, fggl::phys::BodyType& rhs) { + const auto value = node.as<std::string>(); + if ( value == TYPE_KINEMATIC ) { + rhs = fggl::phys::BodyType::KINEMATIC; + } else if ( value == TYPE_STATIC ) { + rhs = fggl::phys::BodyType::STATIC; + } else { + rhs = fggl::phys::BodyType::DYNAMIC; + } + return true; + } + }; + + const std::string convert<fggl::phys::BodyType>::TYPE_KINEMATIC = "kinematic"; + const std::string convert<fggl::phys::BodyType>::TYPE_STATIC = "static"; + const std::string convert<fggl::phys::BodyType>::TYPE_DYN = "dynamic"; + +} + +namespace fggl::ecs { + + template<> + bool restore_config(math::Transform* comp, const YAML::Node& node) { + return true; + } + + static void process_mesh(data::Mesh& out, const YAML::Node& shape) { + auto transform = data::OFFSET_NONE; + if ( shape["offset"] ) { + auto scaleFactor = shape["offset"].as<math::vec3>(); + transform = glm::translate(transform, scaleFactor); + } + + if ( shape["scale"] ) { + auto scaleFactor = shape["scale"].as<math::vec3>(); + transform = glm::scale(transform, scaleFactor); + } + + // now the shape itself + auto shapeType = shape["type"].as<std::string>(); + if ( shapeType == "cube" ) { + data::make_cube(out, transform); + } else if ( shapeType == "sphere" ) { + int stacks = shape["stacks"].as<int>(16); + int slices = shape["slices"].as<int>(16); + data::make_sphere(out, transform, stacks, slices); + } + } + + template<> + bool restore_config(data::StaticMesh* meshComp, const YAML::Node& node) { + if ( !node["pipeline"] ) { + return false; + } + + meshComp->pipeline = node["pipeline"].as<std::string>(); + + if ( node["shape"] ) { + data::Mesh mesh; + if (node["shape"].IsSequence()) { + // is a composite shape + for (const auto &shapeNode : node["shape"]) { + process_mesh(mesh, shapeNode); + } + } else { + // is a basic shape + process_mesh(mesh, node["shape"]); + } + + // optimise and load + mesh.removeDups(); + meshComp->mesh = mesh; + } else { + return false; + } + + return true; + } + + + template<> + bool restore_config(phys::RigidBody* body, const YAML::Node& node) { + body->type = node["type"].as<fggl::phys::BodyType>(fggl::phys::BodyType::DYNAMIC); + if ( body->type == fggl::phys::BodyType::STATIC) { + body->mass = phys::MASS_STATIC; + } else { + body->mass = node["mass"].as<float>( phys::MASS_DEFAULT ); + } + + // shape detection + if ( node["shape"] ) { + auto type = node["shape"]["type"].as< std::string >(); + + if (type == "box") { + // assume unit box if extents are missing + auto extents = node["shape"]["extents"].as<math::vec3>(phys::UNIT_EXTENTS); + body->shape = new phys::Box(extents); + } else if (type == "sphere") { + auto radius = node["shape"]["radius"].as<float>(0.5F); + body->shape = new phys::Sphere(radius); + } else { + return false; + } + } + + return true; + } + +} + +#endif //FGGL_ECS3_PROTOTYPE_LOADER_HPP diff --git a/include/fggl/ecs3/prototype/world.h b/include/fggl/ecs3/prototype/world.h index 5a178f5d7f28e2a7222130e36a680f592b82a280..697f00304608996072bbacf5227b4cbdad0f93f3 100644 --- a/include/fggl/ecs3/prototype/world.h +++ b/include/fggl/ecs3/prototype/world.h @@ -10,6 +10,7 @@ #include <unordered_set> #include <fggl/ecs3/types.hpp> +#include <yaml-cpp/yaml.h> /** * A component based implementation of a game world. @@ -45,6 +46,12 @@ namespace fggl::ecs3::prototype { return ptr; } + void* add(const std::shared_ptr<ComponentBase>& compMeta, const YAML::Node& config) { + void* ptr = compMeta->restore(config); + m_components[ compMeta->id() ] = ptr; + return ptr; + } + template<typename C> C *set(const C *ptr) { C *newPtr = new C(*ptr); @@ -111,6 +118,7 @@ namespace fggl::ecs3::prototype { auto *meta = entity.add<ecs3::EntityMeta>(); meta->id = nextID; meta->abstract = abstract; + meta->typeName = ""; return nextID; } @@ -127,6 +135,27 @@ namespace fggl::ecs3::prototype { return clone; } + void addFromConfig(entity_t entity, component_type_t type, const YAML::Node& node) { + auto meta = m_types.meta(type); + + auto& entityObj = m_entities.at(entity); + entityObj.add(meta, node); + + m_types.fireAdd(this, entity, meta->id()); + } + + void createFromSpec(entity_t entity, const YAML::Node& compConfig) { + if ( compConfig ) { + for (const auto& it : compConfig ) { + const auto name = it.first.as<std::string>(); + auto& config = it.second; + + auto compType = m_types.find( name.c_str() ); + addFromConfig(entity, compType, config); + } + } + } + bool alive(entity_t entity) const { if (entity == NULL_ENTITY) { // tis a silly question, but can be asked by accident. @@ -302,6 +331,21 @@ namespace fggl::ecs3::prototype { m_deathListeners.emplace_back(callback); } + entity_t findProtoype(const std::string& name) const { + for ( const auto& [entity, obj] : m_entities ) { + if ( !obj.m_abstract ){ + continue; + } + + auto* metaData = obj.get<EntityMeta>(); + if ( metaData->typeName == name){ + return entity; + } + } + + return NULL_ENTITY; + } + private: std::vector< entity_cb > m_deathListeners; TypeRegistry &m_types; diff --git a/include/fggl/ecs3/types.hpp b/include/fggl/ecs3/types.hpp index 6c011d3602a100117bfe143c10fbad3327b94639..906cbe7f94a6ee5b1819a5ee1656cb506b377d2e 100644 --- a/include/fggl/ecs3/types.hpp +++ b/include/fggl/ecs3/types.hpp @@ -38,6 +38,7 @@ namespace fggl::ecs3 { constexpr static const char name[] = "meta"; entity_t id; bool abstract; + std::string typeName; }; struct RecordIdentifier { @@ -186,6 +187,7 @@ namespace fggl::ecs3 { inline component_type_t find(const char *name) const { for (auto &[type, meta] : m_types) { + std::cerr << meta->name() << std::endl; if (std::strcmp(name, meta->name()) == 0) { return type; } diff --git a/include/fggl/math/types.hpp b/include/fggl/math/types.hpp index 6125c37223b421329c547550b1b5d6d78e4619ef..4b635c1eb242b918992f6eb0efe52b0a745c9f9f 100644 --- a/include/fggl/math/types.hpp +++ b/include/fggl/math/types.hpp @@ -166,7 +166,6 @@ namespace fggl::math { } - // feels a bit strange to be doing this... namespace glm { inline bool operator<(const vec3 &lhs, const vec3 &rhs) { diff --git a/include/fggl/phys/types.hpp b/include/fggl/phys/types.hpp index 3b15ec6fe7e10346232459aebaf285d0e78b9f91..c3b68de411c6975473b06ee8dc47533cbd7ead2b 100644 --- a/include/fggl/phys/types.hpp +++ b/include/fggl/phys/types.hpp @@ -30,6 +30,7 @@ namespace fggl::phys { constexpr float MASS_STATIC = 0.0F; constexpr float MASS_DEFAULT = 1.0F; + constexpr math::vec3 UNIT_EXTENTS{0.5F, 0.5F, 0.5F}; enum class ShapeType { UNSET,