Skip to content

Commit

Permalink
[transform_hierarchy] added tz::transform_hierarchy
Browse files Browse the repository at this point in the history
  • Loading branch information
harrand committed Sep 3, 2023
1 parent 7a214b6 commit dd614ba
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions src/tz/core/data/transform_hierarchy.hpp
Original file line number Diff line number Diff line change
@@ -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 <optional>
#include <cstddef>

namespace tz
{
template<typename T>
struct transform_node
{
mutable T data;
mutable tz::trs local_transform = {};
std::optional<unsigned int> parent = std::nullopt;
std::vector<unsigned int> children = {};
};

template<typename T = void*>
class transform_hierarchy
{
public:
transform_hierarchy() = default;
std::size_t size() const;
bool empty() const{return this->size() == 0;}
std::vector<unsigned int> get_root_node_ids() const;

// returns id of the new node.
unsigned int add_node(tz::trs local_transform = {}, T data = {}, std::optional<unsigned int> 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<T>& tree);
unsigned int add_hierarchy_onto(const transform_hierarchy<T>& tree, unsigned int node_id);
const transform_node<T>& 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<transform_node<T>> nodes = {};
};
}

#include "tz/core/data/transform_hierarchy.inl"
#endif // TZ_CORE_DATA_TRANSFORM_HIERARCHY_HPP
151 changes: 151 additions & 0 deletions src/tz/core/data/transform_hierarchy.inl
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include "tz/dbgui/dbgui.hpp"
#include "tz/core/matrix_transform.hpp"
#include <type_traits>

namespace tz
{
template<typename T>
concept has_dbgui_method = requires(T t)
{
{t.dbgui()} -> std::same_as<void>;
};

template<typename T>
std::size_t transform_hierarchy<T>::size() const
{
return this->nodes.size();
}

template<typename T>
std::vector<unsigned int> transform_hierarchy<T>::get_root_node_ids() const
{
std::vector<unsigned int> 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<typename T>
unsigned int transform_hierarchy<T>::add_node(tz::trs local_transform, T data, std::optional<unsigned int> 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<typename T>
unsigned int transform_hierarchy<T>::add_hierarchy(const transform_hierarchy<T>& 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<typename T>
unsigned int transform_hierarchy<T>::add_hierarchy_onto(const transform_hierarchy<T>& 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<typename T>
const transform_node<T>& transform_hierarchy<T>::get_node(unsigned int id) const
{
return this->nodes[id];
}

template<typename T>
tz::trs transform_hierarchy<T>::get_global_transform(unsigned int id) const
{
const auto& n = this->get_node(id);
tz::trs global = n.local_transform;
std::optional<unsigned int> 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<typename T>
void transform_hierarchy<T>::dbgui()
{
for(unsigned int root : this->get_root_node_ids())
{
this->dbgui_node(root);
}
}

template<typename T>
void transform_hierarchy<T>::dbgui_node(unsigned int node_id)
{
const transform_node<T>& 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<T>)
{
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();
}
}
}
4 changes: 4 additions & 0 deletions test/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
80 changes: 80 additions & 0 deletions test/core/transform_hierarchy_test.cpp
Original file line number Diff line number Diff line change
@@ -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<unsigned int>{0u, 1u} || t.get_root_node_ids() == std::vector<unsigned int>{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<std::string> lhs;
unsigned int lhsroot = lhs.add_node({}, "LHS ROOT");

tz::transform_hierarchy<std::string> 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<std::string> lhs;
unsigned int lhsroot = lhs.add_node({}, "LHS ROOT");
unsigned int lhschild = lhs.add_node({}, "LHS CHILD", lhsroot);

tz::transform_hierarchy<std::string> 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();
}

0 comments on commit dd614ba

Please sign in to comment.