I have a tree structure like this: a Model has a root Node and each Node has any number of child Nodes and also any number of Meshes.
A lot of the time in my application is spent traversing this tree and doing computations like view frustrum culling and doing matrix multiplications. Currently, it is naively implemented, where each Node has vectors of child Nodes and Meshes, and the tree is recursively traversed. This is very slow.
I've been looking at the data-oriented design and I like the idea of it being very cache friendly. I've been thinking of something like this:
struct Mesh
{
// misc data
MeshID mMeshID;
}
// probably needs more information?
struct Node
{
// begin and end index into Models 'mNodes'
uint32_t mChildrenBegin;
uint32_t mChildrenEnd;
// as above but for meshes
uint32_t mMeshesBegin;
uint32_t mMeshesEnd;
}
struct Model
{
std::vector<Node> mNodes;
std::vector<Mesh> mMeshes;
}
Now I need to traverse the tree to get a list of visible meshes. At each node, I must check if the node is visible. The following branches:
The tree is static. Once a model is loaded in the application, the tree never changes. So somehow surely I must be able to use this information to get an efficient structure.
I'm puzzled how to approach this though.
A couple of questions;
Solution: Morris Traversal Morris Traversal is a method based on the idea of a threaded binary tree and can be used to traverse a tree without using recursion or stack.
1) Create an empty stack S. 2) Initialize current node as root 3) Push the current node to S and set current = current->left until current is NULL 4) If current is NULL and stack is not empty then a) Pop the top item from stack. b) Print the popped item, set current = popped_item->right c) Go to step 3.
Recursive functions are simpler to implement since you only have to care about a node, they use the stack to store the state for each call. Non-recursive functions have a lot less stack usage but require you to store a list of all nodes for each level and can be far more complex than recursive functions.
You could represent the tree as a single array in memory in depth-first traversal order
struct Node {
... node data ...
int next;
};
std::vector<Node> nodes;
next
field keeps the index of the next node at same or higher level; the first children of a node is not explicitly stated as it's the node following the current node in the sequence (unless next is also pointing to it; in that case the current node has no children).
For example in the tree:
the red arrows represent where next
is pointing to.
The traversing for example for rendering becomes:
void check(int ix) {
switch(nodes[ix].visibility()) {
case VISIBLE:
// Draw whole subtree, no more checking needed
for (int i=ix,e=nodes[ix].next; i!=e; i++) {
nodes[i].draw();
}
break;
case PARTIALLY_VISIBLE:
nodes[ix].draw();
for (int c=ix+1, e=nodes[ix].next; c!=e; c=nodes[c].next) {
check(c);
}
break;
}
}
The same code can also be converted to non-recursive by keeping an explicit stack (not sure why that would be a good idea unless the node operation and checking is extremely fast or the tree is insanely deep).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With