Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data-oriented tree traversal without recursion

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 node is visible. All child nodes and meshes below it are visible too. Don't go deeper into this branch. Check other nodes at the same depth.
  • The node is not visible. No child nodes or meshes at this node or below it will be visible. Don't go deeper into this branch. Check other nodes at the same depth.
  • The node is partially visible. Some nodes and/or some meshes are visible. Must go deeper into hierarchy.

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;

  1. How do I layout the nodes in memory? Is the root node of the first index? Are the other nodes added based on depth?
  2. How do I iterate the tree without using recursion? I don't want to visit each node unless I really have to. For example, if a node at depth=2 is not visible, all its meshes and children (and their meshes) should not be tested, but skipped completely.
like image 678
KaiserJohaan Avatar asked Feb 16 '15 15:02

KaiserJohaan


People also ask

Can you traverse a tree without recursion?

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.

How do you find a tree without recursion?

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.

What is recursive and non-recursive tree traversing?

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.


1 Answers

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:

enter image description here

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).

like image 79
6502 Avatar answered Oct 12 '22 16:10

6502