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:
- Node3D. The node type all other nodes are subclassed from. It provides basic states such as visible and active, the message queuing mechanism and the interface for polymorphic call of all basic operations in the scene graph.
- World3D. This is the root node of the scene graph and s of course a subclass of Group3D. It contains a connection to the renderer which can be retrieved by calling the World3D::getRenderer() method. It also maintains a reference to the active camera in the scene, as many Camera3D nodes can be declared and switched from one to another during the life of the scene graph.
- User3D. This type of node implements an autonomous entity that can navigate through the virtual world as a result of a controller that provides input and therefore drives the node. Despite its name, this is not a unique node; many User3D instances may populate the virtual world, each one representing a different actual user (e.g. for multiplayer games) or computer-controlled characters, animals or avatars in general. The User3D nodes do not implement a viewing system, as this is part of the Camera3D node, which of course can be bound ( attached) to any scene graph node, including a User3D instance. Cameras and users are completely different entities in SceneGraph3D, allowing a large diversity of configurations, multiple angles, multiple users and any dynamic combination of them. User3D is a subclass of Group3D (actually it is derived from a TransformNode3D which is in turn subclassed from a Group3D) and can therefore contain other nodes. This makes the addition of a visual represntation of the avatar very easy, as you only add whatever node you want as the content of the user and it will move along with it.
- Camera3D. The eye to the world. It can be configured to follow a specific user or any other node, to be left standing alone or to be transformed as a normal geometric node in a kinematic chain. Multiple cameras can be defined but only one acts as the primary (rendered) output.
- Input3D. This is the base class for all types of controller nodes. The basic input node implements user input from the GUI using the mouse and keyboard or alternatively via the joystick (not yet implemented). This is an abstract device interface which provides normalized axes values and button states for navigation and event triggering. It is acompanied by a large number of predefined events, primarily associated with the changes in the state of its variables. Any subclass must implement this abstract model in order for a User3D node to be able to bind with it for control. upon attribute parsing (scene loading phase), the Input3D node determines the device that is requested (if available) and creates an internal object to poll the physical device through the DirectX SDK interface. If you are planning to port the code to Linux, you must provide and altrnative implementation for the MouseKeyboardDevice3D class that is Windows-specific at the moment or create a different InputDevice3D subclass for your specific device or driver.
- Group3D. The basic collection of nodes in the scene graph. Every node that needs to contain multiple connections to other nodes must be subclassed from this one or from one of its subclasses. It provides hierarchical traversal for rendering, culling, behavior simulation, initialization and reset as well as hierarchical queries and parsing.
- TransformNode3D. Implements a standard geometric transformation node. OpenGL-style rotation, change of scale and transformation is supported as well as methods to optain and set the matrix directly. The transformation can be reset using event messages. The order of transformations is fixed: Scale, then Rotation, then Translation.
- Geometry3D. This is the simplest node to represent geometric data. For the moment, it is used as a placeholder for managing and displaying static geometry loaded from a file.
- ProximityTrigger3D. The scene graph usually contains many types of nodes that act as triggers for any kind of event. Thisis the simplest trigger, that is activated and deactivated whenever a predefined node enters or exits its area of effect, respectively. Multiple nodes can be declared as actuators to a proximity node. The events fired can be directed to any node and not necessarily only to the registered ones with the trigger. The area of effect of a ProximityTrigger3D can be a sphere, an object-oriented box or a plane (half-space). The area of effect is normally transformed along with the trigger when the later is part of a transformation chain.
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:
- Resolves all named references into actual object references
- Parses all event messages and equeues them in a message-ID-indexed table for fast triggering
- Checks the scene graph for severe errors. If a Camera3D node or a User3D node is missing, it fixes any problems associated with this situation
- Loads initial values to the various states of the nodes. These inital values can be reset at any time by performing a master reset on the world node (either manually, World3D::reset(), or by posting a reset message to the special %world node)
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:
- App stage. All nodes are first examined for any incoming messages. Each node contains an input queue and and output buffer queue for receiving and executing and for accumulating and dispatching messages respectively. All incoming messages are examined one by one and for each one recognized, the associated action is performed. This is done in the virtual void processMessage(char * msg) method that is implemented in each node class. The superclass Node3D::processMessages() is responsible for repeatedly calling processMessage() for all incoming messages. Then, all scene graph nodes hierarchically process their state, by traversing them and invoking their app() method. This method implements the behavior of each node. As part of the simulation of each node new events may be triggered (by calling the EVENT_OCCURED(eventname) macro), which causes an event flag to be activated in the node's internal state. Finally, all nodes perform a review of their event flags, compare the results with the designated event message associations and dispatch the appropriate messages to the associated recipient nodes. This process is performed within the dispatchMessages() method.
- Cull stage. Although, in a deferred renderer, the role of this operation is not that important, as demanding operations are performed on screen-space fragments with a depth complexity of zero or one, culling can save a lot of rendering time for large environements. The culling mechanism is different for each type of node and some special rendering passes require different culling strategies. In the current implementation no node-specific culling is implemented, but the procedure is in place for future extension of the core implementation.
- Draw stage. The actual rendering of the scene is the last operation in a frame duty cycle. All prepared data and node states are ready to be displayed. The world provides to the renderer its draw() method and through its primary camera member, the acces to the camera setup method, which performs the exact counterpart of the camera callback function discussed in the use of EaZD as a standalone renderer (see the Deferred renderer API).
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 vectoreventmap; 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