Implement Transform propagation in flecs

This commit is contained in:
2025-04-20 23:52:34 +02:00
parent b78c214229
commit 293f2bacc5
18 changed files with 174 additions and 181 deletions

View File

@@ -11,10 +11,8 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
find_package(EnTT CONFIG REQUIRED)
find_package(flecs CONFIG REQUIRED) find_package(flecs CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED) find_package(glm CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(glfw3 REQUIRED) find_package(glfw3 REQUIRED)
find_package(spdlog REQUIRED) find_package(spdlog REQUIRED)
find_package(fx-gltf REQUIRED) find_package(fx-gltf REQUIRED)
@@ -22,9 +20,8 @@ find_package(fx-gltf REQUIRED)
add_subdirectory(${PROJECT_SOURCE_DIR}/lib) add_subdirectory(${PROJECT_SOURCE_DIR}/lib)
add_library(fever_core add_library(fever_core
src/asset/asset_manager.cpp src/asset/asset.cpp
src/components/transform.cpp src/transform/transform.cpp
# src/core/application.cpp
src/core/camera.cpp src/core/camera.cpp
src/core/glad.cpp src/core/glad.cpp
src/core/graphics/framebuffer.cpp src/core/graphics/framebuffer.cpp
@@ -50,12 +47,10 @@ target_link_libraries(
glad glad
stb stb
glfw glfw
EnTT::EnTT
flecs::flecs flecs::flecs
spdlog::spdlog spdlog::spdlog
glm::glm glm::glm
fx-gltf::fx-gltf fx-gltf::fx-gltf
nlohmann_json::nlohmann_json
) )
add_subdirectory(${PROJECT_SOURCE_DIR}/apps) add_subdirectory(${PROJECT_SOURCE_DIR}/apps)

View File

@@ -2,7 +2,7 @@
#include "flycam.h" #include "flycam.h"
#include "components/name.h" #include "components/name.h"
#include "components/transform.h" #include "transform/transform.h"
#include "core/camera.h" #include "core/camera.h"
#include "core/light.h" #include "core/light.h"
#include "window/window.h" #include "window/window.h"

View File

@@ -1,10 +1,11 @@
#include <asset/asset_manager.h> #include <asset/asset.h>
#include <flecs.h>
#include <input/input.h> #include <input/input.h>
#include <log/log.h> #include <log/log.h>
#include <transform/transform.h>
#include <window/window.h> #include <window/window.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <flecs.h>
#include <iostream> #include <iostream>
int main() int main()
@@ -16,12 +17,7 @@ int main()
world.import <Window::WindowModule>(); world.import <Window::WindowModule>();
world.import <Input::InputModule>(); world.import <Input::InputModule>();
world.import <Asset::AssetModule>(); world.import <Asset::AssetModule>();
world.import <TransformModule>();
Asset::AssetManager manager;
manager.register_asset_type<int>(world);
auto handle0 = manager.load<int>(world, "hi");
auto handle1 = manager.load<int>(world, "hi");
auto handle2 = manager.load<int>(world, "hi2");
#ifndef NDEBUG #ifndef NDEBUG
world.import <flecs::stats>(); world.import <flecs::stats>();
@@ -59,6 +55,13 @@ int main()
.kind(flecs::PostUpdate) .kind(flecs::PostUpdate)
.each([](std::shared_ptr<GLFWwindow>& glfw_window) { glfwSwapBuffers(glfw_window.get()); }); .each([](std::shared_ptr<GLFWwindow>& glfw_window) { glfwSwapBuffers(glfw_window.get()); });
auto parent = world.entity("TestParent").add<Transform>().add<GlobalTransform>();
parent.get_mut<Transform>()->translation += glm::vec3(1.0);
auto child = world.entity("TestChild").add<Transform>().add<GlobalTransform>();
child.get_mut<Transform>()->translation += glm::vec3(1.0);
child.child_of(parent);
while (world.progress()) { while (world.progress()) {
} }
} }

9
src/asset/asset.cpp Normal file
View File

@@ -0,0 +1,9 @@
#include "asset.h"
namespace Asset {
AssetModule::AssetModule([[maybe_unused]] flecs::world& world)
{
}
} // namespace Asset

42
src/asset/asset.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <flecs.h>
#include <memory>
#include <string>
#include <unordered_map>
namespace Asset {
struct AssetModule
{
AssetModule(flecs::world& world);
};
template <typename T> struct AssetCache
{
std::unordered_map<std::string, std::shared_ptr<T>> cache;
};
template <typename T> struct AssetLoader
{
template <typename Args> std::shared_ptr<T> operator()(Args&&...)
{
static_assert(sizeof(T) == 0, "AssetLoader not implemented for this type.");
}
};
template <typename T> std::shared_ptr<T> load(flecs::world& world, std::string asset_path)
{
auto& asset_cache = world.ensure<AssetCache<T>>();
auto asset_it = asset_cache.cache.find(asset_path);
if (asset_it != asset_cache.cache.end())
return asset_it->second;
AssetLoader<T> asset_loader;
auto loaded_asset = asset_loader(asset_path);
asset_cache.cache.emplace(asset_path, loaded_asset);
return loaded_asset;
}
} // namespace Asset

View File

@@ -1,10 +0,0 @@
#include "asset_manager.h"
namespace Asset {
AssetModule::AssetModule(flecs::world& world)
{
auto asset_manager = world.component<AssetManager>();
}
} // namespace Asset

View File

@@ -1,61 +0,0 @@
#pragma once
#include <flecs.h>
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
namespace Asset {
struct AssetModule
{
AssetModule(flecs::world& world);
};
template <typename T> struct AssetCache
{
std::unordered_map<std::string, std::shared_ptr<T>> cache;
};
template <typename T> struct AssetLoader
{
template <typename Args> std::shared_ptr<T> operator()(Args&&...)
{
static_assert(sizeof(T) == 0, "AssetLoader not implemented for this type.");
}
};
template <> struct AssetLoader<int>
{
std::shared_ptr<int> operator()(std::string_view path)
{
static int i = 0;
return std::make_shared<int>(i++);
}
};
struct AssetManager
{
template <typename T> void register_asset_type(flecs::world& world)
{
world.set<AssetCache<T>>({});
}
template <typename T> std::shared_ptr<T> load(flecs::world& world, std::string asset_path)
{
auto asset_cache = world.get_mut<AssetCache<T>>();
auto asset_it = asset_cache->cache.find(asset_path);
if (asset_it != asset_cache->cache.end())
return asset_it->second;
AssetLoader<T> asset_loader;
auto loaded_asset = asset_loader(asset_path);
asset_cache->cache.emplace(asset_path, loaded_asset);
return loaded_asset;
}
};
} // namespace Asset

View File

@@ -1,8 +0,0 @@
#pragma once
#include <string>
struct Name
{
std::string name;
};

View File

@@ -1,37 +0,0 @@
#include "transform.h"
#include "relationship.h"
void GlobalTransform::update(entt::registry &registry)
{
// Update GlobalTransform components
// TODO: Only do this when the Transform changed.
auto root_transform_view =
registry.view<Transform const, GlobalTransform>(entt::exclude<Parent>);
auto transform_view = registry.view<Transform const, GlobalTransform, Parent const>();
for (auto [entity, transform, global_transform] : root_transform_view.each()) {
global_transform = transform;
auto parent_global_transform = global_transform;
if (auto *children = registry.try_get<Children>(entity)) {
for (auto child : children->children) {
std::function<void(entt::entity entity, GlobalTransform parent_global_transform)>
transform_propagate =
[&registry, &transform_propagate, &transform_view](
entt::entity entity, GlobalTransform parent_global_transform) {
auto [transform, global_transform, parent] = transform_view.get(entity);
global_transform.transform = parent_global_transform.transform *
GlobalTransform(transform).transform;
if (auto *children = registry.try_get<Children>(entity)) {
for (auto child : children->children) {
transform_propagate(child, global_transform);
}
}
};
transform_propagate(child, parent_global_transform);
}
}
}
}

View File

@@ -1,41 +0,0 @@
#pragma once
#include <entt/entt.hpp>
#include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>
struct Transform
{
glm::vec3 translation{};
glm::quat orientation{};
glm::vec3 scale{1.0, 1.0, 1.0};
};
struct GlobalTransform
{
GlobalTransform() = default;
GlobalTransform(Transform const &transform)
{
// Translate * Rotate * Scale * vertex_vec;
// First scaling, then rotation, then translation
// Translate
glm::mat4 translation_matrix = glm::translate(glm::mat4(1.0F), transform.translation);
// Rotate
glm::mat4 rotation_matrix = glm::toMat4(transform.orientation);
// Scale
glm::mat4 scale_matrix = glm::scale(glm::mat4(1.0F), transform.scale);
this->transform = translation_matrix * rotation_matrix * scale_matrix;
}
glm::mat4 transform{};
[[nodiscard]] auto position() const -> glm::vec3 { return transform[3]; };
static void update(entt::registry &registry);
};

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "components/transform.h" #include "transform/transform.h"
#include <entt/entt.hpp> #include <entt/entt.hpp>
#include <glm/glm.hpp> #include <glm/glm.hpp>

View File

@@ -1,5 +1,5 @@
#include "light.h" #include "light.h"
#include "components/transform.h" #include "transform/transform.h"
static auto light_active(float illuminance) -> bool static auto light_active(float illuminance) -> bool
{ {

View File

@@ -1,5 +1,4 @@
#include "gltf.h" #include "gltf.h"
#include "components/name.h"
#include "components/relationship.h" #include "components/relationship.h"
#include "core/camera.h" #include "core/camera.h"
@@ -38,7 +37,7 @@ auto Gltf::spawn_scene(std::size_t index,
std::function<entt::entity(GltfNode const&, entt::entity)> spawn_node = std::function<entt::entity(GltfNode const&, entt::entity)> spawn_node =
[&registry, &spawn_node](GltfNode const& node, entt::entity parent) { [&registry, &spawn_node](GltfNode const& node, entt::entity parent) {
auto entity = registry.create(); auto entity = registry.create();
registry.emplace<Name>(entity, node.name); // registry.emplace<Name>(entity, node.name);
registry.emplace<Transform>(entity, node.transform); registry.emplace<Transform>(entity, node.transform);
registry.emplace<GlobalTransform>(entity, GlobalTransform{}); registry.emplace<GlobalTransform>(entity, GlobalTransform{});
registry.emplace<Parent>(entity, Parent{.parent = parent}); registry.emplace<Parent>(entity, Parent{.parent = parent});

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "components/transform.h" #include "transform/transform.h"
#include "core/graphics/material.h" #include "core/graphics/material.h"
#include "core/graphics/mesh.h" #include "core/graphics/mesh.h"

View File

@@ -1,5 +1,4 @@
#include "gltf_loader.h" #include "gltf_loader.h"
#include "components/name.h"
#include "components/relationship.h" #include "components/relationship.h"
#include "core/camera.h" #include "core/camera.h"
#include "entt/entity/fwd.hpp" #include "entt/entity/fwd.hpp"

View File

@@ -0,0 +1,36 @@
#include "transform.h"
void GlobalTransform::update(entt::registry& registry)
{
// Update GlobalTransform components
// TODO: Only do this when the Transform changed.
// auto root_transform_view =
// registry.view<Transform const, GlobalTransform>(entt::exclude<Parent>);
// auto transform_view = registry.view<Transform const, GlobalTransform, Parent const>();
// for (auto [entity, transform, global_transform] : root_transform_view.each()) {
// global_transform = transform;
// auto parent_global_transform = global_transform;
// if (auto* children = registry.try_get<Children>(entity)) {
// for (auto child : children->children) {
// std::function<void(entt::entity entity, GlobalTransform parent_global_transform)>
// transform_propagate =
// [&registry, &transform_propagate, &transform_view](
// entt::entity entity, GlobalTransform parent_global_transform) {
// auto [transform, global_transform, parent] = transform_view.get(entity);
// global_transform.transform = parent_global_transform.transform *
// GlobalTransform(transform).transform;
// if (auto* children = registry.try_get<Children>(entity)) {
// for (auto child : children->children) {
// transform_propagate(child, global_transform);
// }
// }
// };
// transform_propagate(child, parent_global_transform);
// }
// }
// }
}

68
src/transform/transform.h Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include <entt/entt.hpp>
#include <flecs.h>
#include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>
struct Transform
{
glm::vec3 translation{};
glm::quat orientation{};
glm::vec3 scale{1.0, 1.0, 1.0};
};
struct GlobalTransform
{
GlobalTransform() = default;
GlobalTransform(Transform const& transform)
{
// Translate * Rotate * Scale * vertex_vec;
// First scaling, then rotation, then translation
// Translate
glm::mat4 translation_matrix = glm::translate(glm::mat4(1.0F), transform.translation);
// Rotate
glm::mat4 rotation_matrix = glm::toMat4(transform.orientation);
// Scale
glm::mat4 scale_matrix = glm::scale(glm::mat4(1.0F), transform.scale);
this->transform = translation_matrix * rotation_matrix * scale_matrix;
}
glm::mat4 transform{};
[[nodiscard]] auto position() const -> glm::vec3 { return transform[3]; };
static void update(entt::registry& registry);
};
// namespace Transform {
struct TransformModule
{
TransformModule(flecs::world& world)
{
world.system<GlobalTransform, Transform const>("PropagateTransform")
.kind(flecs::PostUpdate)
.each([](flecs::entity e,
GlobalTransform& global_transform,
Transform const& local_transform) {
// There is no guarantee that the parent is iterated first. So there could be a two
// frame delay.
if (e.parent() == flecs::entity::null()) {
global_transform.transform = GlobalTransform(local_transform).transform;
} else {
auto parent_global_transform = e.parent().get_mut<GlobalTransform>();
global_transform.transform = parent_global_transform->transform *
GlobalTransform(local_transform).transform;
}
});
}
};
// } // namespace Transform

View File

@@ -6,7 +6,7 @@
}, },
"dependencies": [ "dependencies": [
"cxxopts", "cxxopts",
"entt", "entt",
"fmt", "fmt",
"fx-gltf", "fx-gltf",
{ {
@@ -16,7 +16,6 @@
] ]
}, },
"glm", "glm",
"nlohmann-json",
"spdlog", "spdlog",
"fx-gltf", "fx-gltf",
"flecs" "flecs"