diff --git a/src/tz/core/data/transform_hierarchy.hpp b/src/tz/core/data/transform_hierarchy.hpp index 021157da50..ecbaa1882f 100644 --- a/src/tz/core/data/transform_hierarchy.hpp +++ b/src/tz/core/data/transform_hierarchy.hpp @@ -16,6 +16,11 @@ namespace tz std::vector children = {}; }; + /** + * @ingroup tz_core + * Represents a hierarchy of 3D transformations, with a payload applied. + * Useful to represent a scene graph, or nodes for 3D skeletal animation. + **/ template class transform_hierarchy { @@ -23,6 +28,7 @@ namespace tz transform_hierarchy() = default; std::size_t size() const; bool empty() const{return this->size() == 0;} + void clear(); std::vector get_root_node_ids() const; // returns id of the new node. @@ -31,9 +37,22 @@ namespace tz // within this hierarchy unsigned int add_hierarchy(const transform_hierarchy& tree); unsigned int add_hierarchy_onto(const transform_hierarchy& tree, unsigned int node_id); + transform_hierarchy export_node(unsigned int id) const; const transform_node& get_node(unsigned int id) const; tz::trs get_global_transform(unsigned int id) const; + // invoke callback for each child of the node `id` + void iterate_children(unsigned int id, tz::action auto callback) const; + // invoke callback for each descendant (children and their children, recursively) of the node `id` + void iterate_descendants(unsigned int id, tz::action auto callback) const; + // invoke callback for each ancestor (parent, their parent, etc...) of the node `id` + void iterate_ancestors(unsigned int id, tz::action auto callback) const; + // iterate through all nodes in order, starting with root nodes. depth first traversal. + // callback *will* be called a number of times equal to `this->size()`. in graph theory it + // could be invoked more, however a transform hierarchy means a node has 1 parent max, so + // it will always be exactly equal. + void iterate_nodes(tz::action auto callback) const; + void dbgui(); private: void dbgui_node(unsigned int node_id); diff --git a/src/tz/core/data/transform_hierarchy.inl b/src/tz/core/data/transform_hierarchy.inl index 1bdd9eaba8..c45ab277cd 100644 --- a/src/tz/core/data/transform_hierarchy.inl +++ b/src/tz/core/data/transform_hierarchy.inl @@ -1,4 +1,5 @@ #include "tz/dbgui/dbgui.hpp" +#include "tz/core/profile.hpp" #include "tz/core/matrix_transform.hpp" #include @@ -16,6 +17,12 @@ namespace tz return this->nodes.size(); } + template + void transform_hierarchy::clear() + { + this->nodes.clear(); + } + template std::vector transform_hierarchy::get_root_node_ids() const { @@ -94,6 +101,31 @@ namespace tz return offset; } + template + void expand_children(const transform_hierarchy& src, transform_hierarchy& hier, unsigned int src_id, unsigned int node_id) + { + const auto& src_node = src.get_node(src_id); + const auto& node = hier.get_node(node_id); + for(std::size_t i = 0; i < src_node.children.size(); i++) + { + // get the children of the source node. make a copy for the dest node and recursce on children. + unsigned int src_child = src_node.children[i]; + const auto& src_child_node = src.get_node(src_child); + unsigned int child = hier.add_node(src_child_node.local_transform, src_child_node.data, node_id); + expand_children(src, hier, src_child, child); + } + } + + template + transform_hierarchy transform_hierarchy::export_node(unsigned int id) const + { + auto node = this->get_node(id); + transform_hierarchy ret; + unsigned int new_root = ret.add_node(this->get_global_transform(id), node.data); + expand_children(*this, ret, id, new_root); + return ret; + } + template const transform_node& transform_hierarchy::get_node(unsigned int id) const { @@ -115,6 +147,49 @@ namespace tz return global; } + template + void transform_hierarchy::iterate_children(unsigned int id, tz::action auto callback) const + { + const auto& node = this->get_node(id); + for(unsigned int child : node.children) + { + callback(child); + } + } + + template + void transform_hierarchy::iterate_descendants(unsigned int id, tz::action auto callback) const + { + const auto& node = this->get_node(id); + for(unsigned int child : node.children) + { + callback(child); + this->iterate_descendants(child, callback); + } + } + + template + void transform_hierarchy::iterate_ancestors(unsigned int id, tz::action auto callback) const + { + auto maybe_parent = this->get_node(id).parent; + if(maybe_parent.has_value()) + { + callback(maybe_parent.value()); + this->iterate_ancestors(maybe_parent.value(), callback); + } + } + + template + void transform_hierarchy::iterate_nodes(tz::action auto callback) const + { + auto root_node_ids = this->get_root_node_ids(); + for(unsigned int root_node : root_node_ids) + { + callback(root_node); + this->iterate_descendants(root_node, callback); + } + } + template void transform_hierarchy::dbgui() { @@ -137,10 +212,13 @@ namespace tz } 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); + if(local != tz::mat4::identity()) + { + 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); diff --git a/test/core/transform_hierarchy_test.cpp b/test/core/transform_hierarchy_test.cpp index 9b58029769..8c196d9a42 100644 --- a/test/core/transform_hierarchy_test.cpp +++ b/test/core/transform_hierarchy_test.cpp @@ -70,6 +70,47 @@ void combine_hierarchy_into_node() tz::assert(lhs.get_node(rhsroot + offset).parent == lhschild); } +void export_hierarchy() +{ + tz::transform_hierarchy lhs; + unsigned int lhsroot = lhs.add_node({.translate = {10.0f, 0.0f, 0.0f}}, "LHS ROOT"); + unsigned int lhschild = lhs.add_node({}, "LHS CHILD", lhsroot); + unsigned int lhsgrandchild = lhs.add_node({}, "LHS GRANDCHILD", lhschild); + unsigned int lhsgreatgrandchild = lhs.add_node({}, "LHS GREAT-GRANDCHILD", lhsgrandchild); + tz::assert(lhs.size() == 4); + + tz::transform_hierarchy exported = lhs.export_node(lhschild); + tz::assert(exported.size() == 3); + tz::assert(exported.get_root_node_ids().size() == 1); + unsigned int exported_root = exported.get_root_node_ids().front(); + tz::assert(exported.get_node(exported_root).data == "LHS CHILD"); + tz::assert(exported.get_node(exported_root).children.size() == 1); + auto grandchild_new_idx = exported.get_node(exported_root).children.front(); + tz::assert(exported.get_node(grandchild_new_idx).data == "LHS GRANDCHILD"); + tz::assert(exported.get_node(grandchild_new_idx).children.size() == 1); + auto greatgrandchild_new_idx = exported.get_node(grandchild_new_idx).children.front(); + tz::assert(exported.get_node(greatgrandchild_new_idx).data == "LHS GREAT-GRANDCHILD"); +} + +void iterate_nodes() +{ + std::size_t count = 0; + + tz::transform_hierarchy lhs; + unsigned int lhsroot = lhs.add_node({.translate = {10.0f, 0.0f, 0.0f}}, "LHS ROOT"); + unsigned int lhschild = lhs.add_node({}, "LHS CHILD", lhsroot); + unsigned int lhsgrandchild = lhs.add_node({}, "LHS GRANDCHILD", lhschild); + unsigned int lhsgreatgrandchild = lhs.add_node({}, "LHS GREAT-GRANDCHILD", lhsgrandchild); + tz::assert(lhs.size() == 4); + + lhs.iterate_nodes([&count](unsigned int id) + { + count++; + (void)id; + }); + tz::assert(count >= lhs.size()); +} + int main() { empty_hierarchy_tests(); @@ -77,4 +118,6 @@ int main() parent_moves_child(); combine_hierarchies(); combine_hierarchy_into_node(); + export_hierarchy(); + iterate_nodes(); } \ No newline at end of file