Component+Scene+System: Implement parent-child transform components
This commit is contained in:
@@ -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]]
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
+69
-49
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user