The SceneGraph3D API

Home About EaZD Deferred renderer API SceneGraph3D API SceneGraph3D scene description language

Introduction

The SceneGraph3D API is a scene graph management toolkit for the EaZD renderer. Although in principle it can be easily modified to work as a standalone lobrary, it is designed to operate in par with the EaZD deferred renderer. The toolkit provides for the moment only the elementary nodes for building simple scene graphs, its design makes it easy to expand and provide more elaborate nodes and behaviors.

As explained in the general description of EaZD, the scene graph essentially consists of a number of collection (branching) and leaf nodes, each one instanciating a functional part of a three-dimentional world. The root node of this directed graph is the World3D node. Every node in the hierarchy is derived from the generic Node3D class, which implements all message passing and event processing mechanisms. A major category of subclasses of the Node3D class is the grouping nodes or collections (Group3D). Apart from the essential operation of managing a group of children nodes and passing the operations performed on the scene graph down to them, Group3D-derived node types also provide key functions in the scene graph such as queries by node name and by node type. World3D is a special Group3D node that essentially provides the entry point to the whole of the scene graph and a connection to the underlying rendering system. Upon creation, all nodes maintain reference to the unique World3D instance in the scene graph (protected member World3D *world) and their parent node (protected member Node3D * parent).

The scene graph itself can be either built manually, by creating new instances of various nodes and adding them to existing Group3D-derived nodes, or loaded from special XML files, the scene files. The tag format of the scene files is described in detail in the SceneGraph3D scene description language.

The minimum set of node types in the scene graph are:

Including SceneGraph3D in your Project

To include the SceneGraph3D toolkit in your code you need to bind the executable with the SceneGraph3D.lib static library and include the SceneGraph.h header file wherever you make any calls to any node. The SceneGraph3D.h contains an inclusion statement for each one of the separate header files, which provide the declaration of all types of nodes. The include directory built provided with the library source code contains all individual files, but you only have to include the master header file SceneGraph.h in your application.

Initialization and Scene Loading

for instructions on how to initialize the deferred renderer and the SceneGraph3D objects in your code, please refer to the SceneGraph3D API documentation. After EaZD initialization and registration of the renderer to the scene graph instance, you can proceed to loading the scene file and prepare the scene graph for rendering:

world->load(argv[1]);
world->init();

The loading procedure consists of parsing the XML scene file, identifying the relative paths for assets required in the declared nodes, and recursively building new nodes and parsing their attributes as the document tree is traversed from the World3D instance. After loading, all nodes are in place but they are not actually ready to run.

In order to prepare the nodes for rendering, you must call the init function which performs the following duties:

Scene Graph Duty Cycle

After initialization, the application enters an infinite loop, until an exit signal is triggered. In every cycle of the application, the scene graph must perform three essential operations: the application behavioral simulation (app stage), the culling of geometry not present in the current view frustum (subject to special rendering conditions) and the drawing of the results. Each one of the three stages is performed for the entire scene graph hierarchy before proceeding to the next stage, to keep the nodes in sync. Let's have a look at the duties that each one of these steps entail:

In a typical application, these three stages are repeated throughout the main loop, as demonstrated in the following GLUT fragment of code:

void Idle()   // idle time processing
{
    extern boolean exiting;
    if (exiting)
    {
        delete world;
        delete dr;
        exit (0);
    }
    world->app();
    world->cull();
    glutPostRedisplay();
}
void RenderMainWindow()
{
    extern DeferredRenderer *dr;
    dr->updateShadows();
    dr->draw();
    glutSwapBuffers();
}

Extending the Scene Graph Class Hierarchy

When adding new scene graph nodes, there is a number of steps that need to be taken care of in order for the new node to function seamlessly with the existing nodes.

Inherritance and New Node Types

After spending some time choosing the right node to subclass your new node from, create a new header file following the naming convention of the library and put its declaration in there. All scene graph node classes in SceneGraph3D end with the "3D" suffix and the header file (and corresponding .cpp implementation file) are named using the rule: SceneGraph_[classname].h/cpp. Please keep all new declaration and implementation files in the corresponding directories already provided. The new header file must be included in the master SceneGraph.h header file.

It is important to carefully choose the parent class of your node as this will save you a lot of code writing and will help others extend your code more efficiently. For example, if you are planning to implement a ping-pong-like motion effect, it would be best if you made a subclass of the TransformNode3D node, as all geometric transformation functionality is already in place for you.

In order to make the new class known to the parsing mechanism, you must provide an association between an XML tag name and your new class. This is done in the SceneGraph_MessageMap.cpp file, in the implementation of the Node3D::buildNode() method located there for this purpose. You only have to add a DECLARE_NODE(name,class) entry in the already existing list:

...
// node naming map -----------------------------
DECLARE_NODE("group",Group3D)
DECLARE_NODE("world",World3D)
DECLARE_NODE("transformation",TransformNode3D)
DECLARE_NODE("object",Geometry3D)
DECLARE_NODE("camera",Camera3D)
DECLARE_NODE("user",User3D)
DECLARE_NODE("input",Input3D)
DECLARE_NODE("light",Light3D)
DECLARE_NODE("proximitytrigger",ProximityTrigger3D)
DECLARE_NODE("mynodetype",MyNodeType3D) <-
...

Event Declaration and Triggering

To fire events in a node, you need to do four things. First, you must declare all new events for your node in the SceneGraphRegisterEvents() function of the SceneGraph_MessageMap.cpp file:

...
REGISTER_EVENT(User3D,"tiltdown");
REGISTER_EVENT(User3D,"turnleft");
REGISTER_EVENT(User3D,"turnright");

REGISTER_EVENT(Camera3D,"follow");
	
REGISTER_EVENT(ProximityTrigger3D,"enter");
REGISTER_EVENT(ProximityTrigger3D,"exit");
REGISTER_EVENT(ProximityTrigger3D,"inside");

REGISTER_EVENT(MyNewNode3D,"eventname1"); <-
REGISTER_EVENT(MyNewNode3D,"eventname2"); <-
...

Second you must initialize the message map for your new node by entering an INIT_MESSAGE_MAP(classtype) line in the same file:

...
INIT_MESSAGE_MAP(Input3D)
INIT_MESSAGE_MAP(ProximityTrigger3D)
INIT_MESSAGE_MAP(MyNewNode3D) <-
...

Third, you must delcare two specific protected members in your class:

protected:
static vector eventmap;
virtual int getEventID (char * eventname) GET_EVENT_ID(eventname)

Finally, whenever you want to trigger a specific event, call EVENT_OCCURED(eventname), where eventname is the string of the event name as declared in the SceneGraph_MessageMap.cpp file. If there are any event messages defined in the XML scene file, these will be automatically registered with the node and dispatched to their recipients whenever the event is fired.

Responding to Messages

To respond to messages received by your new node type, you must re-implement the processMessage() method. This method takes as input a single string that usually contains a message name and a set of parameters, e.g. "color 1.0, 1.1, 1.15". The method should compare the input with a set of acceptable message names and parse the arguments (if any) that are provided. If a message is not intercepted by the node, it should be under normal circumstances forwarded to the processMessage() method of its superclass by explicitely invoking the method. See the source code for an example of how to implement this behavior.



Last update: 6 Feb. 2009