Skip to content

Commit

Permalink
[transform_hierarchy] many more features, including iteration, cleari…
Browse files Browse the repository at this point in the history
…ng and exporting of nodes
  • Loading branch information
harrand committed Sep 3, 2023
1 parent dd614ba commit 4df0874
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 4 deletions.
19 changes: 19 additions & 0 deletions src/tz/core/data/transform_hierarchy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ namespace tz
std::vector<unsigned int> 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<typename T = void*>
class transform_hierarchy
{
public:
transform_hierarchy() = default;
std::size_t size() const;
bool empty() const{return this->size() == 0;}
void clear();
std::vector<unsigned int> get_root_node_ids() const;

// returns id of the new node.
Expand All @@ -31,9 +37,22 @@ namespace tz
// within this hierarchy
unsigned int add_hierarchy(const transform_hierarchy<T>& tree);
unsigned int add_hierarchy_onto(const transform_hierarchy<T>& tree, unsigned int node_id);
transform_hierarchy<T> export_node(unsigned int id) const;
const transform_node<T>& 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<unsigned int> 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<unsigned int> auto callback) const;
// invoke callback for each ancestor (parent, their parent, etc...) of the node `id`
void iterate_ancestors(unsigned int id, tz::action<unsigned int> 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<unsigned int> auto callback) const;

void dbgui();
private:
void dbgui_node(unsigned int node_id);
Expand Down
86 changes: 82 additions & 4 deletions src/tz/core/data/transform_hierarchy.inl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "tz/dbgui/dbgui.hpp"
#include "tz/core/profile.hpp"
#include "tz/core/matrix_transform.hpp"
#include <type_traits>

Expand All @@ -16,6 +17,12 @@ namespace tz
return this->nodes.size();
}

template<typename T>
void transform_hierarchy<T>::clear()
{
this->nodes.clear();
}

template<typename T>
std::vector<unsigned int> transform_hierarchy<T>::get_root_node_ids() const
{
Expand Down Expand Up @@ -94,6 +101,31 @@ namespace tz
return offset;
}

template<typename T>
void expand_children(const transform_hierarchy<T>& src, transform_hierarchy<T>& 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<typename T>
transform_hierarchy<T> transform_hierarchy<T>::export_node(unsigned int id) const
{
auto node = this->get_node(id);
transform_hierarchy<T> ret;
unsigned int new_root = ret.add_node(this->get_global_transform(id), node.data);
expand_children<T>(*this, ret, id, new_root);
return ret;
}

template<typename T>
const transform_node<T>& transform_hierarchy<T>::get_node(unsigned int id) const
{
Expand All @@ -115,6 +147,49 @@ namespace tz
return global;
}

template<typename T>
void transform_hierarchy<T>::iterate_children(unsigned int id, tz::action<unsigned int> auto callback) const
{
const auto& node = this->get_node(id);
for(unsigned int child : node.children)
{
callback(child);
}
}

template<typename T>
void transform_hierarchy<T>::iterate_descendants(unsigned int id, tz::action<unsigned int> auto callback) const
{
const auto& node = this->get_node(id);
for(unsigned int child : node.children)
{
callback(child);
this->iterate_descendants(child, callback);
}
}

template<typename T>
void transform_hierarchy<T>::iterate_ancestors(unsigned int id, tz::action<unsigned int> 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<typename T>
void transform_hierarchy<T>::iterate_nodes(tz::action<unsigned int> 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<typename T>
void transform_hierarchy<T>::dbgui()
{
Expand All @@ -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);
Expand Down
43 changes: 43 additions & 0 deletions test/core/transform_hierarchy_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,54 @@ void combine_hierarchy_into_node()
tz::assert(lhs.get_node(rhsroot + offset).parent == lhschild);
}

void export_hierarchy()
{
tz::transform_hierarchy<std::string> 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<std::string> 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<std::string> 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();
root_nodes_local_equals_global();
parent_moves_child();
combine_hierarchies();
combine_hierarchy_into_node();
export_hierarchy();
iterate_nodes();
}

0 comments on commit 4df0874

Please sign in to comment.