Programmers familiar with the use of scene graphs may prefer to use that data structure rather than writing OpenGL manually. While VR Juggler does not provide a scene graph of its own, its design allows the use of existing scene graph software. In VR Juggler 1.1 and beyond, the supported scene graphs are OpenGL Performer from SGI, OpenSG, and Open Scene Graph. This section explains how to use OpenGL Performer to write VR Juggler applications.
A Performer-based VR Juggler application must derive from
vrj::PfApp. Similar to vrj::GlApp
presented in the previous section,
vrj::PfApp derives from
vrj::App. vrj::PfApp
extends vrj::App by adding methods that
deal with scene graph initialization and access. Figure 5.3, “vrj::PfApp application
class” shows how
vrj::PfApp fits into the class hierarchy of
a Performer-based VR Juggler application.
Two of the methods added to the application interface by
vrj::PfApp are
initScene() and
getScene(). These are called by the Performer Draw Manager to
initialize the application scene graph and to get the root of the
scene graph respectively. They must be implemented by the
application (they are pure virtual methods within
vrj::PfApp). Additional methods will be
discussed in this section, but in many cases the default
implementations of these other methods may be used. A simple
tutorial application will be provided to illustrate the concepts
presented.
In an application using OpenGL Performer, the scene graph
must be initialized before it can be used. The method
vrj::PfApp::initScene() is provided
for that purpose. Within this method, the root of the
application scene graph should be created, and any required
models should be loaded and attached to the root in some way.
The exact mechanisms for accomplishing this will vary depending
on what the application will do.
During the initialization of OpenGL Performer by VR
Juggler, vrj::PfApp::initScene() is
invoked after the Performer functions
pfInit() and
pfConfig() but before
vrj::App::apiInit().
In order for Performer to render the application scene
graph, it must get access to the scene graph root. The method
vrj::PfApp::getScene() will be called
by the Performer Draw Manager so that it can give the scene
graph root node to Performer. Since the job of
getScene() is straightforward, its
implementation can be very simple. A typical implementation
will have a single statement that returns a member variable
that holds a pointer to the application scene graph root
node.
Make sure that the node returned is
not a pfScene
object. If it is, then lighting will not work.
In this section, we present a tutorial that demonstrates model loading with OpenGL Performer. The tutorial overview is as follows:
Description: Simple OpenGL Performer application that loads a model.
Objective: Understand how to load a model, add it to a scene graph, and return the root to VR Juggler.
Member functions:
vrj::PfApp::initScene(),
vrj::PfApp::getScene()
Directory:
$VJ_BASE_DIR/share/samples/Pf/simple/simplePf
Files: simplePfApp.h,
simplePfApp.cpp
The following application class is called
simplePfApp. It is derived from
vrj::PfApp and has custom
initScene() and
getScene() methods declared. Note
that this application uses
preForkInit() which will be
discussed later. Refer to simplePfApp.h
for the implementations of
preForkInit() and
setModel().
1 class simplePfApp : public vrj::PfApp
{
public:
simplePfApp();
5 virtual ~simplePfApp();
virtual void preForkInit();
virtual void initScene();
virtual pfGroup* getScene();
10 void setModel(std::string modelFile);
public:
std::string mModelFileName;
15 pfGroup* mLightGroup;
pfLightSource* mSun;
pfGroup* mRootNode;
pfNode* mModelRoot;
};The implementation of
initScene() is in
simplePfApp.cpp. Within this method, we
create the scene graph root node, the lighting node, and
load a user-specified model. The implementation
follows:
1 void simplePfApp::initScene ()
{
// Allocate all the nodes needed
mRootNode = new pfGroup;
5
// Create the SUN light source
mLightGroup = new pfGroup;
mSun = new pfLightSource;
mLightGroup->addChild(mSun);
10 mSun->setPos(0.3f, 0.0f, 0.3f, 0.0f);
mSun->setColor(PFLT_DIFFUSE, 1.0f, 1.0f, 1.0f);
mSun->setColor(PFLT_AMBIENT, 0.3f, 0.3f, 0.3f);
mSun->setColor(PFLT_SPECULAR, 1.0f, 1.0f, 1.0f);
mSun->on();
15
// --- LOAD THE MODEL -- //
mModelRoot = pfdLoadFile(mModelFileName.c_str());
// -- CONSTRUCT STATIC STRUCTURE OF SCENE GRAPH -- //
20 mRootNode->addChild(mModelRoot);
mRootNode->addChild(mLightGroup);
}
| First, the root node is constructed as a
|
| Next, some steps are taken to create a light source for the application. |
| Finally, the model is loaded using
|
| Finally, the model and the light source nodes are added as children of the root. |
The Performer Draw Manager will call the application's
getScene() method to get the root
of the scene graph. The implementation of this method can be
found in simplePfApp.h. The code is as
follows:
pfGroup* simplePfApp::getScene ()
{
return mRootNode;
}The simplicity of this method implementation is not
limited to the simple tutorial from which it is taken. All
Performer-based VR Juggler applications can take advantage
of this idiom where the root node is a member variable
returned in getScene().
Besides the two methods discussed so far, there are
several other methods in vrj::PfApp that
extend the basic vrj::App interface.
Each is discussed in this section.
Prototype: public void preForkInit();
This member function allows the user application to do
any processing that needs to happen before Performer forks
its processes but after pfInit() is
called. In other words, it is invoked after
pfInit() but before
pfConfig().
Prototype: public void appChanFunc(pfChannel* chan);
This method is called every frame in the application
process for each active channel. It is called immediately
before rendering (pfFrame()).
Prototype: public void configPWin(pfPipeWindow* pWin);
This method is used to initialize a pipe window. It is called as soon as the pipe window is opened.
Prototype: public std::vector<int> getFrameBufferAttrs();
This method returns the needed parameters for the Performer frame buffer. Stereo, double buffering, depth buffering, and RGBA are all requested by default.
Prototype: public void drawChan(pfChannel* chan,
void* chandata);
This is the method called in the channel draw function to do the actual rendering. For most programs, the default behavior of this function is correct. It makes the following calls:
chan->clear(); pfDraw();
Advanced users may want to override this behavior for
complex rendering effects such as overlays or multi-pass
rendering. (See the OpenGL Performer manual pages about
overriding the draw traversal function.) This function is
the draw traversal function but with the projections set
correctly for the given displays and eye. Prior to the
invocation of this method, chan is ready
to draw.
The Performer function pfExit()
poses a problem for VR Juggler applications, and some
background information will help ensure that readers understand
the consequences of using pfExit() (or
not). The main issue with pfExit() as it
relates to VR Juggler is that calling
pfExit() has the side effect of calling
the system function exit(), which means
that it should be (or has to be) the very last function call of
a program. Prior to VR Juggler 2.0.1, the VR Juggler Performer
Draw Manager was written to call pfExit()
from within the method
vrj::PfDrawManager::closeAPI(). Before
VR Juggler 2.0 Beta 3, however, this method of the
vrj::PfDrawManager interface had never
been called—the result of the kernel shutdown process being
incomplete. With the more complete kernel shutdown process in
VR Juggler 2.0 Beta 3 and 2.0.0, authors of Performer-based VR
Juggler applications saw their applications exiting prematurely
after invoking the kernel shutdown. More specifically, any code
that was intended to be executed after
vrj::PfDrawManager::closeAPI() would
not be executed. Such code includes
vrj::App::exit() or an override
thereof; an application object destructor; and anything else to
be done after
vrj::Kernel::waitForKernelStop()
returned.
To remedy this problem,
vrj::PfDrawManager::closeAPI() in VR
Juggler 2.0.1 and newer does not call
pfExit(). Rather, it is the responsibility
of the application programmer to call
pfExit() if s/he so desires. Failing to
call pfExit() could result in resource
leaks from Performer, but calling pfExit()
has been known to cause application crashes (irrespective of
whether VR Juggler is used). In general, users should call
pfExit() at the end of their
main() function, but if doing so causes
the application to crash on exit, then not calling
pfExit() is probably the better
option.
The important thing to remember is that the application
object destructor needs to be called
before pfExit() is
called. Refer to Example 5.2, “Using pfExit() with a
Heap-Allocated Application Object”
for an example of how pfExit() would be
used with a VR Juggler application object allocated on the
heap. For a stack-allocated application object, see Example 5.3, “Using pfExit() with a
Stack-Allocated Application Object”.
Example 5.2. Using pfExit() with a
Heap-Allocated Application Object
1 int main(int argc, char* argv[])
{
vrj::Kernel* kernel = vrj::Kernel::instance();
5 // Allocate the application object on the heap. Its
// destructor will be called manually before calling
// pfExit().
simplePfApp* application = new simplePfApp();
10 // Load config files.
for ( int i = 2; i < argc; ++i )
{
kernel->loadConfigFile(argv[i]);
}
15
kernel->start();
// Configure the application.
application->setModel(argv[1]);
20 kernel->setApplication(application);
// Wait for the kernel to shut down.
kernel->waitForKernelStop();
25 // Final application clean-up.
delete application;
// Clean up Performer.
// Calls system exit() function and therefore never returns.
30 pfExit();
return 0;
}Example 5.3. Using pfExit() with a
Stack-Allocated Application Object
1 int main(int argc, char* argv[])
{
// Nested scope for stack-allocated data.
{
5 vrj::Kernel* kernel = vrj::Kernel::instance();
// Load config files.
for ( int i = 2; i < argc; ++i )
{
10 kernel->loadConfigFile(argv[i]);
}
kernel->start();
15 // Allocate the application object on the stack. Its
// destructor will be called automatically at the end
// of this nested scope.
simplePfApp application;
20 // Configure the application and give it to the kernel.
application.setModel(argv[1]);
kernel->setApplication(&application);
// Wait for the kernel to shut down.
25 kernel->waitForKernelStop();
}
// Clean up Performer.
// Calls system exit() function and therefore never returns.
30 pfExit();
return 0;
}