From bc3f2c9db54c787a333f744e82112d862dd14127 Mon Sep 17 00:00:00 2001 From: Riyyi Date: Fri, 5 Jan 2024 20:50:49 +0100 Subject: [PATCH] Component+Scene+System: Implement parent-child transform components --- README.org | 2 + assets/scene/scene1.json | 36 ++++++- src/inferno/component/textareacomponent.h | 4 +- src/inferno/component/transformcomponent.h | 9 +- src/inferno/scene/scene.cpp | 118 ++++++++++++--------- src/inferno/scene/scene.h | 3 +- src/inferno/system/transformsystem.cpp | 30 +++++- src/inferno/system/transformsystem.h | 11 +- 8 files changed, 153 insertions(+), 60 deletions(-) diff --git a/README.org b/README.org index 8743370..797345e 100644 --- a/README.org +++ b/README.org @@ -48,3 +48,5 @@ $ cmake .. && make - [[https://www.glfw.org/docs/latest/build_guide.html#build_link_cmake_source][Build GLFW using CMake]] - [[https://learnopengl.com][Learn OpenGL]] - [[https://www.youtube.com/playlist?list=PLlrATfBNZ98dC-V-N3m0Go4deliWHPFwT][Game Engine]] by The Cherno +- [[https://www.youtube.com/watch?v=mnIQEQoHHCU][OpenGL 3D Game Tutorial 32: Font Rendering]] +- [[https://youtu.be/d8cfgcJR9Tk][OpenGL 3D Game Tutorial 33: Distance Field Text Rendering]] diff --git a/assets/scene/scene1.json b/assets/scene/scene1.json index 2b57ba1..70e7871 100644 --- a/assets/scene/scene1.json +++ b/assets/scene/scene1.json @@ -44,14 +44,44 @@ "id": { "id": 3424242 }, "tag": { "tag": "Quad 3" }, "transform" : { - "translate": [ 2.2, 0.0, 0.0 ], - "rotate": [ 0.0, 0.0, 0.0 ], + "translate": [ 2.2, 1.0, 0.0 ], + "rotate": [ 0.0, 0.0, -20.0 ], "scale": [ 1.0, 1.0, 1.0 ] }, "sprite": { "color": [ 1.0, 1.0, 1.0, 1.0 ], "texture": "assets/gfx/test-inverted.png" - } + }, + "children": [ + { + "id": { "id": 4345472 }, + "tag": { "tag": "Quad 4" }, + "transform" : { + "translate": [ 0.85, 0.0, 0.0 ], + "rotate": [ 0.0, 0.0, 0.0 ], + "scale": [ 0.5, 0.5, 1.0 ] + }, + "sprite": { + "color": [ 1.0, 1.0, 1.0, 1.0 ], + "texture": "assets/gfx/test-inverted.png" + }, + "children": [ + { + "id": { "id": 5234723 }, + "tag": { "tag": "Quad 5" }, + "transform" : { + "translate": [ 1.0, 0.0, 0.0 ], + "rotate": [ 0.0, 0.0, -20.0 ], + "scale": [ 0.5, 0.5, 1.0 ] + }, + "sprite": { + "color": [ 1.0, 1.0, 1.0, 1.0 ], + "texture": "assets/gfx/test-inverted.png" + } + } + ] + } + ] }, { "id": { "id": 675754 }, diff --git a/src/inferno/component/textareacomponent.h b/src/inferno/component/textareacomponent.h index b80d326..4e682f7 100644 --- a/src/inferno/component/textareacomponent.h +++ b/src/inferno/component/textareacomponent.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022-2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -18,7 +18,7 @@ namespace Inferno { struct TextAreaComponent { std::string content; std::string font; - unsigned char fontSize { 0 }; + unsigned char fontSize { 12 }; float lineSpacing { 1.0f }; uint32_t width { 0 }; diff --git a/src/inferno/component/transformcomponent.h b/src/inferno/component/transformcomponent.h index 5463608..c57d437 100644 --- a/src/inferno/component/transformcomponent.h +++ b/src/inferno/component/transformcomponent.h @@ -1,11 +1,16 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ #pragma once +#include // uint32_t +#include + +#include "entt/entity/entity.hpp" // entt::null +#include "entt/entity/fwd.hpp" // entt::entity #include "glm/ext/matrix_float4x4.hpp" // glm::mat4 #include "glm/ext/vector_float3.hpp" // glm::vec3 #include "ruc/format/format.h" @@ -17,6 +22,8 @@ struct TransformComponent { glm::vec3 translate { 0.0f, 0.0f, 0.0f }; glm::vec3 rotate { 0.0f, 0.0f, 0.0f }; glm::vec3 scale { 1.0f, 1.0f, 1.0f }; + entt::entity parent { entt::null }; + glm::mat4 transform { 1.0f }; // Identity matrix }; diff --git a/src/inferno/scene/scene.cpp b/src/inferno/scene/scene.cpp index 5e1c80a..d408988 100644 --- a/src/inferno/scene/scene.cpp +++ b/src/inferno/scene/scene.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022-2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -8,6 +8,7 @@ #include // uint32_t #include // std::numeric_limits +#include "entt/entity/entity.hpp" // ent::entity #include "ruc/file.h" #include "ruc/format/log.h" #include "ruc/json/json.h" @@ -62,54 +63,7 @@ void Scene::initialize() VERIFY(entityJson.type() == ruc::Json::Type::Array); const auto& entities = entityJson.asArray(); for (size_t i = 0; i < entities.size(); ++i) { - - uint32_t entity = createEntity(); - - VERIFY(entities.at(i).type() == ruc::Json::Type::Object); - const auto& components = entities.at(i); - - // ID is required - VERIFY(components.exists("id"), "id not found"); - auto& id = getComponent(entity); - components.at("id").getTo(id); - - if (components.exists("tag")) { - auto& tag = getComponent(entity); - components.at("tag").getTo(tag); - } - if (components.exists("transform")) { - auto& transform = getComponent(entity); - components.at("transform").getTo(transform); - } - if (components.exists("camera")) { - auto& camera = addComponent(entity); - components.at("camera").getTo(camera); - } - if (components.exists("lua-scripts")) { - VERIFY(components.at("lua-scripts").type() == ruc::Json::Type::Array); - const auto& scripts = components.at("lua-scripts").asArray(); - for (size_t j = 0; j < scripts.size(); ++j) { - auto& script = addComponent(entity); - scripts.at(j).getTo(script); - } - } - if (components.exists("native-scripts")) { - VERIFY(components.at("native-scripts").type() == ruc::Json::Type::Array); - const auto& scripts = components.at("native-scripts").asArray(); - for (size_t j = 0; j < scripts.size(); ++j) { - auto& script = addComponent(entity); - scripts.at(j).getTo(script); - script.bind(); - } - } - if (components.exists("sprite")) { - auto& sprite = addComponent(entity); - components.at("sprite").getTo(sprite); - } - if (components.exists("text")) { - auto& text = addComponent(entity); - components.at("text").getTo(text); - } + loadEntity(entities.at(i)); } } @@ -138,6 +92,8 @@ void Scene::destroy() TransformSystem::destroy(); } +// ----------------------------------------- + uint32_t Scene::createEntity(const std::string& name) { return createEntityWithUID(UID(), name); @@ -150,6 +106,68 @@ uint32_t Scene::createEntityWithUID(UID id, const std::string& name) addComponent(entity, name.empty() ? "Unnamed Entity" : name); addComponent(entity); + TransformSystem::the().add(entity); + + return entity; +} + +uint32_t Scene::loadEntity(ruc::Json components, uint32_t parentEntity) +{ + VERIFY(components.type() == ruc::Json::Type::Object); + + uint32_t entity = createEntity(); + + // At minimum, ID is required + VERIFY(components.exists("id"), "id not found"); + auto& id = getComponent(entity); + components.at("id").getTo(id); + + if (components.exists("tag")) { + auto& tag = getComponent(entity); + components.at("tag").getTo(tag); + } + if (components.exists("transform")) { + auto& transform = getComponent(entity); + components.at("transform").getTo(transform); + transform.parent = static_cast(parentEntity); + } + if (components.exists("camera")) { + auto& camera = addComponent(entity); + components.at("camera").getTo(camera); + } + if (components.exists("lua-scripts")) { + VERIFY(components.at("lua-scripts").type() == ruc::Json::Type::Array); + const auto& scripts = components.at("lua-scripts").asArray(); + for (size_t i = 0; i < scripts.size(); ++i) { + auto& script = addComponent(entity); + scripts.at(i).getTo(script); + } + } + if (components.exists("native-scripts")) { + VERIFY(components.at("native-scripts").type() == ruc::Json::Type::Array); + const auto& scripts = components.at("native-scripts").asArray(); + for (size_t i = 0; i < scripts.size(); ++i) { + auto& script = addComponent(entity); + scripts.at(i).getTo(script); + script.bind(); + } + } + if (components.exists("sprite")) { + auto& sprite = addComponent(entity); + components.at("sprite").getTo(sprite); + } + if (components.exists("text")) { + auto& text = addComponent(entity); + components.at("text").getTo(text); + } + if (components.exists("children")) { + VERIFY(components.at("children").type() == ruc::Json::Type::Array); + const auto& children = components.at("children").asArray(); + for (size_t i = 0; i < children.size(); ++i) { + loadEntity(components.at("children")[i], entity); + } + } + return entity; } @@ -172,6 +190,8 @@ void Scene::destroyEntity(uint32_t entity) m_registry->destroy(entt::entity { entity }); } +// ----------------------------------------- + glm::mat4 Scene::cameraProjectionView() { return CameraSystem::the().projectionView(); diff --git a/src/inferno/scene/scene.h b/src/inferno/scene/scene.h index b1347f7..3bf5590 100644 --- a/src/inferno/scene/scene.h +++ b/src/inferno/scene/scene.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -31,6 +31,7 @@ public: uint32_t createEntity(const std::string& name = ""); uint32_t createEntityWithUID(UID id, const std::string& name = ""); + uint32_t loadEntity(ruc::Json components, uint32_t parentEntity = entt::null); uint32_t findEntity(std::string_view name); void destroyEntity(uint32_t entity); diff --git a/src/inferno/system/transformsystem.cpp b/src/inferno/system/transformsystem.cpp index 3443f44..ca32135 100644 --- a/src/inferno/system/transformsystem.cpp +++ b/src/inferno/system/transformsystem.cpp @@ -1,9 +1,13 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ +#include // std::sort +#include // uint32_t + +#include "entt/entity/fwd.hpp" // entt:entity #include "glm/ext/matrix_transform.hpp" // glm::translate, glm::rotate, glm::scale, glm::radians #include "ruc/format/log.h" @@ -25,7 +29,7 @@ void TransformSystem::update() { auto view = m_registry->view(); - for (auto entity : view) { + for (auto entity : m_hierarchy) { auto& component = view.get(entity); @@ -42,7 +46,29 @@ void TransformSystem::update() // Scale component.transform = glm::scale(component.transform, component.scale); + + // Apply the parent transform to the child transform + if (component.parent != entt::null) { + auto& parent = view.get(component.parent); + component.transform = parent.transform * component.transform; + } } } +void TransformSystem::add(uint32_t entity) +{ + m_hierarchy.push_back(static_cast(entity)); +} + +void TransformSystem::sort() +{ + std::sort(m_hierarchy.begin(), m_hierarchy.end(), + [](entt::entity a, entt::entity b) { + return (a == entt::null && b == entt::null) ? false + : (a == entt::null) ? true + : (b == entt::null) ? false + : a < b; + }); +} + } // namespace Inferno diff --git a/src/inferno/system/transformsystem.h b/src/inferno/system/transformsystem.h index b6e6b79..cbdad66 100644 --- a/src/inferno/system/transformsystem.h +++ b/src/inferno/system/transformsystem.h @@ -1,12 +1,14 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ #pragma once -#include // std::shared_ptr +#include // uint32_t +#include // std::shared_ptr +#include #include "entt/entity/registry.hpp" // entt::entity, entt::registry #include "ruc/singleton.h" @@ -20,9 +22,14 @@ public: void update(); + void add(uint32_t entity); + void sort(); + void setRegistry(std::shared_ptr registry) { m_registry = registry; }; private: + std::vector m_hierarchy; + std::shared_ptr m_registry; };