Browse Source

Component+Scene+System: Implement parent-child transform components

master
Riyyi 4 months ago
parent
commit
bc3f2c9db5
  1. 2
      README.org
  2. 36
      assets/scene/scene1.json
  3. 4
      src/inferno/component/textareacomponent.h
  4. 9
      src/inferno/component/transformcomponent.h
  5. 118
      src/inferno/scene/scene.cpp
  6. 3
      src/inferno/scene/scene.h
  7. 30
      src/inferno/system/transformsystem.cpp
  8. 11
      src/inferno/system/transformsystem.h

2
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]]

36
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 },

4
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 };

9
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 <cstdint> // uint32_t
#include <optional>
#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
};

118
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 <cstdint> // uint32_t
#include <limits> // 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<IDComponent>(entity);
components.at("id").getTo(id);
if (components.exists("tag")) {
auto& tag = getComponent<TagComponent>(entity);
components.at("tag").getTo(tag);
}
if (components.exists("transform")) {
auto& transform = getComponent<TransformComponent>(entity);
components.at("transform").getTo(transform);
}
if (components.exists("camera")) {
auto& camera = addComponent<CameraComponent>(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<LuaScriptComponent>(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<NativeScriptComponent>(entity);
scripts.at(j).getTo(script);
script.bind();
}
}
if (components.exists("sprite")) {
auto& sprite = addComponent<SpriteComponent>(entity);
components.at("sprite").getTo(sprite);
}
if (components.exists("text")) {
auto& text = addComponent<TextAreaComponent>(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<TagComponent>(entity, name.empty() ? "Unnamed Entity" : name);
addComponent<TransformComponent>(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<IDComponent>(entity);
components.at("id").getTo(id);
if (components.exists("tag")) {
auto& tag = getComponent<TagComponent>(entity);
components.at("tag").getTo(tag);
}
if (components.exists("transform")) {
auto& transform = getComponent<TransformComponent>(entity);
components.at("transform").getTo(transform);
transform.parent = static_cast<entt::entity>(parentEntity);
}
if (components.exists("camera")) {
auto& camera = addComponent<CameraComponent>(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<LuaScriptComponent>(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<NativeScriptComponent>(entity);
scripts.at(i).getTo(script);
script.bind();
}
}
if (components.exists("sprite")) {
auto& sprite = addComponent<SpriteComponent>(entity);
components.at("sprite").getTo(sprite);
}
if (components.exists("text")) {
auto& text = addComponent<TextAreaComponent>(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();

3
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);

30
src/inferno/system/transformsystem.cpp

@ -1,9 +1,13 @@
/*
* Copyright (C) 2022 Riyyi
* Copyright (C) 2022,2024 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <algorithm> // std::sort
#include <cstdint> // 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<TransformComponent>();
for (auto entity : view) {
for (auto entity : m_hierarchy) {
auto& component = view.get<TransformComponent>(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<TransformComponent>(component.parent);
component.transform = parent.transform * component.transform;
}
}
}
void TransformSystem::add(uint32_t entity)
{
m_hierarchy.push_back(static_cast<entt::entity>(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

11
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 <memory> // std::shared_ptr
#include <cstdint> // uint32_t
#include <memory> // std::shared_ptr
#include <vector>
#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<entt::registry> registry) { m_registry = registry; };
private:
std::vector<entt::entity> m_hierarchy;
std::shared_ptr<entt::registry> m_registry;
};

Loading…
Cancel
Save