From dd614ba718cd406e250d2d73c03dbdb112816c9e Mon Sep 17 00:00:00 2001 From: harrand Date: Sun, 3 Sep 2023 18:21:16 +0100 Subject: [PATCH] [transform_hierarchy] added tz::transform_hierarchy --- CMakeLists.txt | 2 + src/tz/core/data/transform_hierarchy.hpp | 45 +++++++ src/tz/core/data/transform_hierarchy.inl | 151 +++++++++++++++++++++++ test/core/CMakeLists.txt | 4 + test/core/transform_hierarchy_test.cpp | 80 ++++++++++++ 5 files changed, 282 insertions(+) create mode 100644 src/tz/core/data/transform_hierarchy.hpp create mode 100644 src/tz/core/data/transform_hierarchy.inl create mode 100644 test/core/transform_hierarchy_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fbf9ff3748..f451d8e53e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,8 @@ add_library(topaz STATIC src/tz/core/data/polymorphic_list.inl src/tz/core/data/quat.cpp src/tz/core/data/quat.hpp + src/tz/core/data/transform_hierarchy.hpp + src/tz/core/data/transform_hierarchy.inl src/tz/core/data/trs.cpp src/tz/core/data/trs.hpp src/tz/core/data/vector.cpp diff --git a/src/tz/core/data/transform_hierarchy.hpp b/src/tz/core/data/transform_hierarchy.hpp new file mode 100644 index 0000000000..021157da50 --- /dev/null +++ b/src/tz/core/data/transform_hierarchy.hpp @@ -0,0 +1,45 @@ +#ifndef TZ_CORE_DATA_TRANSFORM_HIERARCHY_HPP +#define TZ_CORE_DATA_TRANSFORM_HIERARCHY_HPP +#include "tz/core/data/trs.hpp" +#include "tz/core/data/handle.hpp" +#include +#include + +namespace tz +{ + template + struct transform_node + { + mutable T data; + mutable tz::trs local_transform = {}; + std::optional parent = std::nullopt; + std::vector children = {}; + }; + + template + class transform_hierarchy + { + public: + transform_hierarchy() = default; + std::size_t size() const; + bool empty() const{return this->size() == 0;} + std::vector get_root_node_ids() const; + + // returns id of the new node. + unsigned int add_node(tz::trs local_transform = {}, T data = {}, std::optional parent = std::nullopt); + // returns offset to be applied to the previous hierarchy's set of nodes to get their corresponding node ids + // within this hierarchy + unsigned int add_hierarchy(const transform_hierarchy& tree); + unsigned int add_hierarchy_onto(const transform_hierarchy& tree, unsigned int node_id); + const transform_node& get_node(unsigned int id) const; + tz::trs get_global_transform(unsigned int id) const; + + void dbgui(); + private: + void dbgui_node(unsigned int node_id); + std::vector> nodes = {}; + }; +} + +#include "tz/core/data/transform_hierarchy.inl" +#endif // TZ_CORE_DATA_TRANSFORM_HIERARCHY_HPP \ No newline at end of file diff --git a/src/tz/core/data/transform_hierarchy.inl b/src/tz/core/data/transform_hierarchy.inl new file mode 100644 index 0000000000..1bdd9eaba8 --- /dev/null +++ b/src/tz/core/data/transform_hierarchy.inl @@ -0,0 +1,151 @@ +#include "tz/dbgui/dbgui.hpp" +#include "tz/core/matrix_transform.hpp" +#include + +namespace tz +{ + template + concept has_dbgui_method = requires(T t) + { + {t.dbgui()} -> std::same_as; + }; + + template + std::size_t transform_hierarchy::size() const + { + return this->nodes.size(); + } + + template + std::vector transform_hierarchy::get_root_node_ids() const + { + std::vector ret; + // for each element, check if any element has that as a child. + // if nobody does, its a root node. + for(std::size_t i = 0; i < this->size(); i++) + { + bool found_child = false; + for(std::size_t j = 0; j < this->size(); j++) + { + if(i == j) continue; + const auto& jnode = this->nodes[j]; + auto iter = std::find(jnode.children.begin(), jnode.children.end(), i); + if(iter != jnode.children.end()) + { + // i is a child of j + // skip this one, its not a root node. + found_child = true; + break; + } + } + if(!found_child) + { + ret.push_back(i); + } + } + return ret; + } + + template + unsigned int transform_hierarchy::add_node(tz::trs local_transform, T data, std::optional parent) + { + auto id = this->nodes.size(); + this->nodes.push_back({.data = data, .local_transform = local_transform, .parent = parent}); + if(parent.has_value()) + { + this->nodes[parent.value()].children.push_back(id); + } + return id; + } + + template + unsigned int transform_hierarchy::add_hierarchy(const transform_hierarchy& tree) + { + unsigned int offset = this->size(); + for(std::size_t i = 0; i < tree.size(); i++) + { + auto node = tree.get_node(i); + if(node.parent.has_value()) + { + node.parent.value() += offset; + } + this->add_node(node.local_transform, node.data, node.parent); + } + return offset; + } + + template + unsigned int transform_hierarchy::add_hierarchy_onto(const transform_hierarchy& tree, unsigned int node_id) + { + unsigned int offset = this->size(); + for(std::size_t i = 0; i < tree.size(); i++) + { + auto node = tree.get_node(i); + if(node.parent.has_value()) + { + node.parent.value() += offset; + } + else + { + node.parent = node_id; + } + this->add_node(node.local_transform, node.data, node.parent); + } + return offset; + } + + template + const transform_node& transform_hierarchy::get_node(unsigned int id) const + { + return this->nodes[id]; + } + + template + tz::trs transform_hierarchy::get_global_transform(unsigned int id) const + { + const auto& n = this->get_node(id); + tz::trs global = n.local_transform; + std::optional p = n.parent; + while(p.has_value()) + { + const auto& pn = this->get_node(p.value()); + global = get_global_transform(p.value()).combined(global); + p = pn.parent; + } + return global; + } + + template + void transform_hierarchy::dbgui() + { + for(unsigned int root : this->get_root_node_ids()) + { + this->dbgui_node(root); + } + } + + template + void transform_hierarchy::dbgui_node(unsigned int node_id) + { + const transform_node& node = this->get_node(node_id); + std::string node_name = "Node " + std::to_string(node_id); + if(ImGui::TreeNode(node_name.c_str())) + { + if constexpr(has_dbgui_method) + { + node.data.dbgui(); + } + tz::mat4 local = node.local_transform.matrix(); + tz::mat4 global = this->get_global_transform(node_id).matrix(); + ImGui::Text("Local Transform"); + tz::dbgui_model(local); + ImGui::Text("Global Transform"); + tz::dbgui_model(global); + for(unsigned int child_idx : node.children) + { + this->dbgui_node(child_idx); + } + ImGui::TreePop(); + } + } +} \ No newline at end of file diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index f271040867..7b72e1c064 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -31,6 +31,10 @@ add_tz_test(NAME tz_text_test TEXT_FILES core/test_file.txt ) +add_tz_test(NAME tz_transform_hierarchy_test + SOURCE_FILES transform_hierarchy_test.cpp + ) + add_tz_test(NAME tz_types_test SOURCE_FILES types_test.cpp ) diff --git a/test/core/transform_hierarchy_test.cpp b/test/core/transform_hierarchy_test.cpp new file mode 100644 index 0000000000..9b58029769 --- /dev/null +++ b/test/core/transform_hierarchy_test.cpp @@ -0,0 +1,80 @@ +#include "tz/core/data/transform_hierarchy.hpp" +#include "tz/core/debug.hpp" + +void empty_hierarchy_tests() +{ + tz::transform_hierarchy empty; + tz::assert(empty.empty()); +} + +void root_nodes_local_equals_global() +{ + tz::transform_hierarchy t; + unsigned int root1 = t.add_node(); + unsigned int root2 = t.add_node(); + unsigned int child1 = t.add_node({}, {}, root1); + unsigned int child11 = t.add_node({}, {}, child1); + unsigned int child2 = t.add_node({}, {}, root2); + + tz::assert(t.get_root_node_ids().size() == 2, "Expected 2 root nodes, but got %zu", t.get_root_node_ids().size()); + tz::assert(t.get_root_node_ids() == std::vector{0u, 1u} || t.get_root_node_ids() == std::vector{1u, 0u}); +} + +void parent_moves_child() +{ + tz::transform_hierarchy t; + unsigned int parent = t.add_node(); + unsigned int child = t.add_node({.translate = {1.0f, 0.0f, 0.0f}}, {}, parent); + tz::assert(t.get_global_transform(child).translate == tz::vec3{1.0f, 0.0f, 0.0f}); + t.get_node(child).local_transform.translate[0] += 1.0f; + tz::assert(t.get_global_transform(child).translate == tz::vec3{2.0f, 0.0f, 0.0f}); + t.get_node(parent).local_transform.translate[0]+= 5.0f; + tz::assert(t.get_global_transform(child).translate == tz::vec3{7.0f, 0.0f, 0.0f}); +} + +void combine_hierarchies() +{ + tz::transform_hierarchy lhs; + unsigned int lhsroot = lhs.add_node({}, "LHS ROOT"); + + tz::transform_hierarchy rhs; + unsigned int rhsroot = rhs.add_node({}, "RHS ROOT"); + unsigned int rhschild = rhs.add_node({}, "RHS CHILD", rhsroot); + tz::assert(rhs.get_node(rhschild).parent == rhsroot); + + unsigned int offset = lhs.add_hierarchy(rhs); + tz::assert(lhs.get_node(lhsroot).data == "LHS ROOT"); + tz::assert(lhs.get_node(rhsroot + offset).data == "RHS ROOT"); + tz::assert(lhs.get_node(rhschild + offset).data == "RHS CHILD"); + tz::assert(lhs.get_node(rhschild + offset).parent == (rhsroot + offset)); +} + +void combine_hierarchy_into_node() +{ + tz::transform_hierarchy lhs; + unsigned int lhsroot = lhs.add_node({}, "LHS ROOT"); + unsigned int lhschild = lhs.add_node({}, "LHS CHILD", lhsroot); + + tz::transform_hierarchy rhs; + unsigned int rhsroot = rhs.add_node({}, "RHS ROOT"); + unsigned int rhschild = rhs.add_node({}, "RHS CHILD", rhsroot); + tz::assert(!rhs.get_node(rhsroot).parent.has_value()); + tz::assert(rhs.get_node(rhschild).parent == rhsroot); + + unsigned int offset = lhs.add_hierarchy_onto(rhs, lhschild); + tz::assert(lhs.get_node(lhsroot).data == "LHS ROOT"); + tz::assert(lhs.get_node(rhsroot + offset).data == "RHS ROOT"); + tz::assert(lhs.get_node(rhschild + offset).data == "RHS CHILD"); + tz::assert(lhs.get_node(rhschild + offset).parent == (rhsroot + offset)); + tz::assert(lhs.get_node(rhsroot + offset).parent.has_value()); + tz::assert(lhs.get_node(rhsroot + offset).parent == lhschild); +} + +int main() +{ + empty_hierarchy_tests(); + root_nodes_local_equals_global(); + parent_moves_child(); + combine_hierarchies(); + combine_hierarchy_into_node(); +} \ No newline at end of file