entttree 0.1.0
Hierarchical entity management for EnTT
Loading...
Searching...
No Matches
bounding_hierarchy.h
Go to the documentation of this file.
1
6#pragma once
7
8#include <type_traits>
9
10#include <geomc/shape/Transformed.h>
11
13
14namespace entttree {
15
16
39template <
40 typename HTag,
41 typename T=double,
42 size_t N=2,
43 typename BTag=HTag,
44 typename XTag=HTag>
46
47 using xfn = AffineTransform<T,N>;
48 using vecn = Vec<T,N>;
49 using rangen = Rect<T,N>;
50 using rayn = Ray<T,N>;
52
53 /****************************
54 * Typed signals
55 ****************************/
56
58 entt::sigh<void(entt::entity, rangen)> on_bounds_set;
60 entt::sigh<void(entt::entity, rangen)> on_bounds_removed;
62 entt::sigh<void(entt::entity, rangen, rangen)> on_bounds_changed;
63
64 /****************************
65 * Construction
66 ****************************/
67
69 entt::registry& reg,
71 _reg(reg),
72 _transforms(transforms)
73 {
74 // listen for hierarchy changes to dirty bounds
75 _conn_added = entt::sink{_transforms.hierarchy().on_added}
76 .template connect<&BoundsSystem::_on_child_added>(*this);
77 _conn_removed = entt::sink{_transforms.hierarchy().on_removed}
78 .template connect<&BoundsSystem::_on_child_removed>(*this);
79 _conn_changed = entt::sink{_transforms.hierarchy().on_changed}
80 .template connect<&BoundsSystem::_on_reparent>(*this);
81
82 // listen for transform edits to dirty bounds
83 _conn_xf_set = entt::sink{_transforms.on_transform_set}
84 .template connect<&BoundsSystem::_on_transform_set>(*this);
85 _conn_xf_removed = entt::sink{_transforms.on_transform_removed}
86 .template connect<&BoundsSystem::_on_transform_removed>(*this);
87 _conn_xf_changed = entt::sink{_transforms.on_transform_changed}
88 .template connect<&BoundsSystem::_on_transform_changed>(*this);
89 }
90
91 BoundsSystem(const BoundsSystem&) = delete;
92 BoundsSystem& operator=(const BoundsSystem&) = delete;
93
94 TransformSystem<HTag,T,N,XTag>& transform_system() { return _transforms; }
95 HierarchySystem<HTag>& hierarchy() { return _transforms.hierarchy(); }
96
97 /****************************
98 * Mutation API
99 ****************************/
100
105 std::optional<rangen> set_intrinsic_bounds(entt::entity eid, rangen bounds) {
106 auto* old = _reg.try_get<IB>(eid);
107 std::optional<rangen> old_val;
108 if (old) {
109 old_val = old->bounds;
110 if (old->bounds != bounds) {
111 old->bounds = bounds;
112 _dirty_ancestors(eid);
113 on_bounds_changed.publish(eid, *old_val, bounds);
114 }
115 } else {
116 _reg.emplace<IB>(eid, bounds);
117 _dirty_ancestors(eid);
118 on_bounds_set.publish(eid, bounds);
119 }
120 return old_val;
121 }
122
123
125 std::optional<rangen> get_intrinsic_bounds(entt::entity eid) const {
126 auto* ib = _reg.try_get<IB>(eid);
127 if (ib) return ib->bounds;
128 return std::nullopt;
129 }
130
131
133 std::optional<rangen> remove_intrinsic_bounds(entt::entity eid) {
134 auto* ib = _reg.try_get<IB>(eid);
135 if (not ib) return std::nullopt;
136 rangen old = ib->bounds;
137 _reg.erase<IB>(eid);
138
139 if (_transforms.hierarchy().parent_of(eid) == entt::null
140 and _transforms.hierarchy().child_count(eid) == 0)
141 {
142 _dirty.erase(eid);
143 _computed.erase(eid);
144 } else {
145 _dirty_ancestors(eid);
146 }
147 on_bounds_removed.publish(eid, old);
148 return old;
149 }
150
151
162 std::optional<rangen> get_computed_bounds(entt::entity eid) {
163 if (_dirty.contains(eid)) {
164 return _recompute(eid);
165 }
166 auto it = _computed.find(eid);
167 if (it != _computed.end()) return it->second;
168 return std::nullopt;
169 }
170
171
172 /****************************
173 * Traversal
174 ****************************/
175
177 auto traverse(entt::entity root, SiblingOrder order) {
178 return augment_with_bounds(
179 _transforms.traverse(root, order)
180 );
181 }
182
183
191 template <TransformedTraversal<T,N> Traversal>
193 using Node = typename TraversalValue<Traversal>::Node::InnerNode;
194 return entttree::walk::map_nodes(
195 std::forward<Traversal>(t),
197 entt::entity eid = n.node.node_id;
198 std::optional<rangen> computed;
199 if (_dirty.contains(eid)) {
200 computed = _recompute(eid);
201 } else {
202 auto it = _computed.find(eid);
203 if (it != _computed.end()) computed = it->second;
204 }
205 return {
206 n,
207 get_intrinsic_bounds(eid),
208 computed
209 };
210 }
211 );
212 }
213
214
224 template <BoundedTraversal<T,N> Traversal>
226 using InnerNode = typename TraversalValue<Traversal>::Node::InnerNode;
227 return entttree::walk::exclude_if(
228 entttree::walk::map_nodes(
229 std::forward<Traversal>(t),
231 return {n, p / n.node_to_root};
232 }
233 ),
235 if (not n.computed_bounds) return true;
236 return n.computed_bounds->contains(n.local_point);
237 }
238 );
239 }
240
241
255 entt::entity root,
258 vecn p)
259 {
260 auto g = entttree::walk::dfs(
263 );
264 for (; g; ++g) {
265 auto&& n = *g;
266 if (n.intrinsic_bounds and n.intrinsic_bounds->contains(n.local_point)) {
267 co_yield n;
268 }
269 }
270 }
271
272
282 template <BoundedTraversal<T,N> Traversal>
284 using InnerNode = typename TraversalValue<Traversal>::Node::InnerNode;
285 return entttree::walk::exclude_if(
286 entttree::walk::map_nodes(
287 std::forward<Traversal>(t),
289 rayn local_ray = ray / n.node_to_root;
290 if (not n.computed_bounds) {
291 return {n, local_ray, Rect<T,1>::full};
292 }
293 rangen box = *n.computed_bounds;
294 auto interval = box.intersect(local_ray);
295 return {n, local_ray, interval};
296 }
297 ),
299 return not n.interval.is_empty();
300 }
301 );
302 }
303
304
317 entt::entity root,
320 rayn ray)
321 {
322 auto g = entttree::walk::dfs(
323 traverse_along_ray(traverse(root, sibling_order), ray),
325 );
326 for (; g; ++g) {
327 if (g->interval.is_empty()) continue;
328 co_yield *g;
329 }
330 }
331
332
333private:
334
335 entt::registry& _reg;
337
340
341 // scoped signal connections (auto-disconnect on destruction)
342 entt::scoped_connection _conn_added;
343 entt::scoped_connection _conn_removed;
344 entt::scoped_connection _conn_changed;
345 entt::scoped_connection _conn_xf_set;
346 entt::scoped_connection _conn_xf_removed;
347 entt::scoped_connection _conn_xf_changed;
348
349
350 void _dirty_ancestors(entt::entity eid) {
351 // dirty this node and all ancestors up to the root.
352 // ancestors() requires a ParentConnection, but root nodes don't have one,
353 // so we walk manually via parent_of().
354 entt::entity cur = eid;
355 while (cur != entt::null) {
356 auto [_, was_inserted] = _dirty.insert(cur);
357 if (not was_inserted) break; // already dirty above here
358 cur = _transforms.hierarchy().parent_of(cur);
359 }
360 }
361
362
363 std::optional<rangen> _recompute(entt::entity eid) {
364 rangen b = rangen::empty;
365
366 auto* ib = _reg.try_get<IB>(eid);
367 if (ib) b = ib->bounds;
368
369 for (auto g = _transforms.hierarchy().children(eid, SiblingOrder::Forward); g; ++g) {
370 entt::entity child = g->node_id;
371 rangen child_bound = rangen::empty;
372
373 if (_dirty.contains(child)) {
374 auto cb = _recompute(child);
375 if (cb) child_bound = *cb;
376 _dirty.erase(child);
377 } else {
378 auto it = _computed.find(child);
379 if (it != _computed.end()) child_bound = it->second;
380 }
381
382 if (not child_bound.is_empty()) {
383 auto xf = _transforms.get_transform(child);
384 if (xf) {
385 child_bound = ((*xf) * child_bound).bounds();
386 }
387 b |= child_bound;
388 }
389 }
390
391 if (not b.is_empty()) {
392 _computed.insert_or_assign(eid, b);
393 } else {
394 _computed.erase(eid);
395 }
396 _dirty.erase(eid);
397 return b.is_empty() ? std::nullopt : std::optional{b};
398 }
399
400
401 /****************************
402 * Signal handlers
403 ****************************/
404
405 void _on_child_added(entt::entity child, ParentConnection<HTag> pc) {
406 _dirty_ancestors(pc.parent);
407 }
408
409 void _on_child_removed(entt::entity child, ParentConnection<HTag> old_pc) {
410 _dirty_ancestors(old_pc.parent);
411 if (_transforms.hierarchy().child_count(child) == 0
412 and not get_intrinsic_bounds(child))
413 {
414 _dirty.erase(child);
415 }
416 }
417
418 void _on_reparent(
419 entt::entity child,
420 ParentConnection<HTag> old_val,
421 ParentConnection<HTag> new_val)
422 {
423 if (old_val.parent != new_val.parent) {
424 _dirty_ancestors(old_val.parent);
425 _dirty_ancestors(child);
426 }
427 }
428
429 void _on_transform_set(entt::entity eid, xfn new_xf) {
430 _dirty_parent_after_transform_edit(eid);
431 }
432
433 void _on_transform_removed(entt::entity eid, xfn old_xf) {
434 _dirty_parent_after_transform_edit(eid);
435 }
436
437 void _on_transform_changed(entt::entity eid, xfn old_xf, xfn new_xf) {
438 _dirty_parent_after_transform_edit(eid);
439 }
440
441 void _dirty_parent_after_transform_edit(entt::entity eid) {
442 // child-to-parent transform edit => parent's computed bounds are stale
443 entt::entity parent = _transforms.hierarchy().parent_of(eid);
444 if (parent != entt::null) {
445 _dirty_ancestors(parent);
446 }
447 }
448
449};
450
451
467template <typename BTag=void, typename HTag, typename T, size_t N, typename XTag>
469 entt::registry& reg,
471{
472 using LayerTag = std::conditional_t<std::is_void_v<BTag>, HTag, BTag>;
474}
475
476
477template <typename HTag, typename BTag=HTag, typename XTag=HTag>
478using BoundsSystem2d = BoundsSystem<HTag, double, 2, BTag, XTag>;
479
480template <typename HTag, typename BTag=HTag, typename XTag=HTag>
481using BoundsSystem3d = BoundsSystem<HTag, double, 3, BTag, XTag>;
482
483
484} // namespace entttree
auto add_bounds(entt::registry &reg, TransformSystem< HTag, T, N, XTag > &transforms)
Construct a BoundsSystem with deduced types from a TransformSystem.
DfsOrder
Controls the visit order for depth-first traversal.
Definition defs.h:35
SiblingOrder
Controls the iteration order of siblings within a parent.
Definition defs.h:43
A system for maintaining hierarchical bounding boxes.
entt::sigh< void(entt::entity, rangen)> on_bounds_set
Emitted when intrinsic bounds are first set on an entity. Args: (entity, new_bounds).
std::optional< rangen > get_computed_bounds(entt::entity eid)
Get the computed bounds for an entity in local coordinates.
std::optional< rangen > get_intrinsic_bounds(entt::entity eid) const
Get the intrinsic bounds in local coordinates, or std::nullopt if not set.
std::optional< rangen > set_intrinsic_bounds(entt::entity eid, rangen bounds)
Set the intrinsic (local) bounding box for an entity.
Generator< PointSearchNode< NodeEntry, T, N > > search_under_point(entt::entity root, SiblingOrder sibling_order, DfsOrder recursion_order, vecn p)
Yield all nodes whose intrinsic bounds contain a point.
auto traverse_under_point(Traversal &&t, vecn p)
Filter a bounded traversal to only visit nodes whose computed bounds contain a point (or which have d...
Generator< RaySearchNode< NodeEntry, T, N > > search_along_ray(entt::entity root, SiblingOrder sibling_order, DfsOrder recursion_order, rayn ray)
Yield all nodes whose computed bounds intersect a ray.
auto augment_with_bounds(Traversal &&t)
Convert a traversal of TransformedNode<Node,T,N> to a traversal of BoundedNode<Node,...
entt::sigh< void(entt::entity, rangen)> on_bounds_removed
Emitted when intrinsic bounds are removed from an entity. Args: (entity, old_bounds).
entt::sigh< void(entt::entity, rangen, rangen)> on_bounds_changed
Emitted when an existing intrinsic bounds value changes. Args: (entity, old_bounds,...
auto traverse_along_ray(Traversal &&t, rayn ray)
Filter a bounded traversal to only visit nodes whose computed bounds intersect a ray (or which have d...
auto traverse(entt::entity root, SiblingOrder order)
Create a traversal which yields BoundedNode<NodeEntry,T,N>.
std::optional< rangen > remove_intrinsic_bounds(entt::entity eid)
Remove the intrinsic bounds for an entity. Returns the old bounds if they existed.
A system for maintaining parent-child relationships on entities.
Definition hierarchy.h:37
entt::entity parent_of(entt::entity node) const
Returns the parent of node, or entt::null if the node is a root or not in the hierarchy.
Definition hierarchy.h:300
auto traverse(entt::entity root, SiblingOrder order) const
Returns a Traversal of the hierarchy rooted at root.
Definition hierarchy.h:396
Generator< NodeEntry > children(entt::entity parent, SiblingOrder order) const
Visit each child of a given node.
Definition hierarchy.h:351
size_t child_count(entt::entity parent) const
Returns the number of children of a given node.
Definition hierarchy.h:324
ECS component storing an entity's own (intrinsic) bounding box.
A system for maintaining local affine transforms layered on a hierarchy.
A lazy description of a rooted graph traversal.
Definition traverse.h:44
Transform system layered on top of a HierarchySystem.
std::remove_cvref_t< T > TraversalValue
Strip references/cv to get the bare Traversal type.
Definition traverse.h:86
and
Transform a traversal inductively, threading parent context through successors.
Definition traverse.h:542