The JCarnac3D-OGL rendering mechanism is based on a scene graph.
A scene graph can be seen as a tree structure except that a node can be inserted more than once (i.e., have more than one parent).
The root node can be associated with a 3D view in order to trigger a scene graph traversal when the 3D view is painted.
Each node in the scene graph can perform OpenGL operations such as state modification (ex : bindTexture) or geometry rendering (ex : drawElements).
In general, a node should perform these steps in order :
Save OpenGL states (the one the node will modify)
Perfrom own rendering
traverse children is any
Restore modified OpenGL states : this will ensure that the other part of the scenegraph are not affected by this node rendering.
Note than in JCarnac3D-OGL, state modifications are passed to children but not to sibling. For example, if you are using a lighting node, only the children of this node will be illuminated. (This is not the case for all the 3D libraries existing on the market.)
A scene graph node is defined by the interface cgJOGLNode. This interface contains three methods for the scene graph traversal :
public void traverse(cgJOGLScenegraphVisitor visitor);
This method is the entry point in a node during the scene graph traversal.
public void render(cgJOGLRenderContext renderContext);
This method is called by the cgJOGLScenegraphVisitor during rendering.
public void select(cgJOGLSelectContext selectContext);
This method is called by the cgJOGLScenegraphVisitor during selection.
Scene Graph Traversal During Rendering :
The rendering is triggered on the 3D view. This leads to the creation of a dedicated cgJOGLScenegraphVisitor which will be responsible to correctly traverse the scene graph and call the render method on the traversed nodes.
Traversal sequence of the following scene graph during rendering (visitor is the variable name of the cgJOGLScenegraphVisitor used):
1 : node1.traverse(visitor)
2 : visitor.visit(node1)
3 : node1.render() //usually does nothing for composite node
4 : visitor.traverse(node2)
5 : node2.traverse(visitor)
6 : visitor.visit(node2)
7 : node2.render()
8 : visitor.traverse(node4)
9 : node4.traverse(visitor)
10 : visitor.visit(node4)
11 : node4.render()
12 : visitor.traverse(node5)
13 : node5.traverse(visitor)
14 : visitor.visit(node5)
15 : node5.render()
16 : visitor.traverse(node3)
17 : node3.traverse(visitor)
18 : visitor.visit(node3)
19 : node3.render()
Note :
You can see that the rendering of node3 occurs after the rendering of node 4 and 5.
Moreover, node 4 and 5 rendering is completely included in the traversal process of node 2. This allows node 2 to modify states before the rendering of node 4 and 5 and then to restore the states after.
Here is a simple description of the behavior of different nodes and visitors :
Composite node pseudo code :
traverse(cgJOGLScenegraphVisitor visitor) :
visitor.visit(this); // first visit the current node
for each child in children : //then traverse all the children
visitor.traverse(child)
Leaf node pseudo code :
traverse(cgJOGLScenegraphVisitor visitor) :
visitor.visit(this); //only visit the current node.
Rendering visitor pseudo code :
traverse(cgJOGLNode node) :
if (node is visible)
node.traverse(this)
visit(cgJOGLNode node):
node.render();
Selection visitor pseudo code :
traverse(cgJOGLNode node) :
if (node is visible)
node.traverse(this)
visit(cgJOGLNode node):
node.select();