A C++20 library for hierarchical entity management built on EnTT. Provides type-tagged parent-child hierarchies, affine transform propagation, and hierarchical bounding volumes with lazy bound recomputation.
The hierarchy structure is defined by a single per-entity component ParentConnection associated with each node. The component names its parent entity and encodes the child's position among its siblings using a fractional index. All other information is derived efficiently from the entity : ParentConnection mapping, and all changes to the graph structure can be written as updates to that one component.
Multiple independent hierarchies can coexist on the same entities using compile-time tag types:
struct RenderH {};
struct CollisionH {};
entt::registry reg;
A system for maintaining parent-child relationships on entities.
Transform and bounds layers are also independently taggable per hierarchy (with defaults), so one hierarchy can host multiple overlays such as render/collision/hitbox bounds.
Documentation
API Reference
Systems
HierarchySystem
Manages parent-child relationships with fractional-index ordering and typed signal notifications.
auto root = reg.create();
auto child = reg.create();
h.set_parent(child, root);
h.set_parent(child, root, position);
h.order_child_before(child_a, child_b);
for (auto g = h.traverse_dfs(root, SiblingOrder::Forward, DfsOrder::ShallowFirst); g; ++g) {
}
entt::sink{h.on_added}.connect<[] (entt::entity child, ParentConnection<RenderH> connection) { ... }>();
entt::sink{h.on_removed}.connect<[] (entt::entity child, ParentConnection<RenderH> old_connection) { ... }>();
entt::sink{h.on_changed}.connect<[] (entt::entity child, ParentConnection<RenderH> old_val, ParentConnection<RenderH> new_val) { ... }>();
TransformSystem
Layers affine transforms on a hierarchy. Nodes without an explicit transform use the identity.
auto xf = entttree::add_transforms(reg, h);
xf.set_transform(child, AffineTransform<double,2>::translation({5, 0}));
auto world = xf.object_to_world(child);
auto rel = xf.xf_between(node_a, node_b);
To maintain multiple transform overlays on one hierarchy:
struct RenderXf {};
struct PhysicsXf {};
BoundsSystem
Maintains hierarchical bounding boxes. Computed bounds are the union of a node's intrinsic bounds and its children's bounds (in parent space). Listens to hierarchy and transform signals for automatic dirty propagation.
auto bs = entttree::add_bounds(reg, xf);
bs.set_intrinsic_bounds(entity, Rect<double,2>{{0,0}, {10,10}});
auto bounds = bs.get_computed_bounds(entity);
for (auto g = bs.search_under_point(root, fwd, shallow_first, point); g; ++g) {
}
Multiple bounds overlays on one hierarchy can share the same transform system:
struct RenderBounds {};
struct CollisionBounds {};
struct HitboxBounds {};
To mix multiple bounds layers with multiple transform layers on one hierarchy:
Traversal framework
Traversals are lazy, coroutine-based generators that can be composed:
auto t = h.traverse(root, SiblingOrder::Forward);
auto visible = entttree::walk::exclude_if(t, [](const auto& node) {
return is_visible(node);
});
auto in_frustum = entttree::walk::prune_if(visible, [](const auto& node) {
return should_explore(node);
});
for (auto g = entttree::walk::dfs(
in_frustum,
DfsOrder::ShallowFirst); g; ++g) {
}
for (auto g = entttree::walk::dfs<
DfsOrder::DeepFirst>(in_frustum); g; ++g) {
}
auto backward = h.
traverse(root, SiblingOrder::Backward);
for (auto g = entttree::walk::dfs(backward); g; ++g) {
}
for (auto g = entttree::walk::bfs(reversed); g; ++g) {
}
auto traverse(entt::entity root, SiblingOrder order) const
Returns a Traversal of the hierarchy rooted at root.
auto reverse_successors(T &&t)
Reverse the order of each node's successors.
Traversal node semantics
Traversal nodes are intentionally stored and queued by value. To avoid expensive copies, Node should normally be a lightweight handle (entity ID, pointer, index, or std::reference_wrapper<T>), not a heavyweight component payload.
auto t = entttree::make_traversal(
root_entity,
[&h] (entt::entity& e) { return h.children(e, SiblingOrder::Forward); }
);
struct NodeView {
entt::entity eid;
const Renderable* render;
const LocalTransform<RenderH,double,2>* xf;
};
return {
eid,
reg.try_get<Renderable>(eid),
reg.try_get<LocalTransform<RenderH,double,2>>(eid)
};
});
auto map_nodes(Traversal &&t, Transform &&xform)
Transform the node type of a traversal.
If you need stable reference semantics during traversal, do not mutate data structures in ways that invalidate those references while the generator is active.
walk::reverse_successors(...) buffers each expanded node's child list in a temporary vector. If your source can already enumerate children in reverse cheaply (like HierarchySystem::children(..., SiblingOrder::Backward)), prefer that.
Dependencies
Building
Requires CMake 3.20+ and a C++20 compiler.
cmake -B build
cmake --build build
ctest --test-dir build --output-on-failure
Install
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --install build --prefix /usr/local
Downstream projects can then use:
find_package(entttree)
target_link_libraries(myapp PRIVATE entttree::entttree)
Options
| Option | Default | Description |
ENTTTREE_BUILD_TESTS | ON | Build the test suite |