This section explains how to use the Open Scene Graph (OSG) in a VR Juggler application. OSG is an open source scene graph that is available at www.openscenegraph.org.
An OSG-based VR Juggler application must derive from
vrj::OsgApp. The vrj::OsgApp class is
derived from the vrj::GlApp presented
previously, which in turn derives from
vrj::App.
vrj::OsgApp extends
vrj::GlApp by adding methods that deal with
scene graph initialization and access. Figure 5.5, “vrj::OsgApp application
class” shows how
vrj::OsgApp fits into the class hierarchy
of an OSG-based VR Juggler application.
The two main application methods for
vrj::OsgApp VR Juggler applications are
initScene() and
getScene(). These are called by the OSG application class
wrapper 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::OsgApp). The rest of this section
gives a more detailed description of these methods and some sample
code to illustrate the concepts presented.
In an application using OSG, the scene graph must be
initialized before it can be used. The method
vrj::OsgApp::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 application initialization,
vrj::OsgApp::initScene() is
invoked. This method is invoked in
vrj::OsgApp::init(). Therefore,
user application objects that derive from
vrj::OsgApp should be sure to invoke
vrj::OsgApp::init() in their
overriding init() method, or the
contents of the overriding init()
method should be moved into the implementation of
initScene().
In order for OSG to render the application scene graph,
it must get access to the scene graph root. The method
vrj::OsgApp::getScene() will be called
by the OSG application class wrapper so that it can get access
to the currently active scene graph whenever the wrapper needs
to use it (for example when rendering or updating). 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 current scene graph root
node.
Do not do any CPU-heavy processing in this method. Because it is called frequently, it should only do the minimum amount of processing necessary to return the root scene graph node. In most cases, this method should only be one line of code. See the following code for an example.
virtual osg::Group* getScene()
{
return mSceneRoot; // Return the root of the graph
}To update the scene graph, use either
preFrame(),
intraFrame(), or
postFrame().
In this section, we present a tutorial that demonstrates model loading and scene navigation using Open Scene Graph. The tutorial overview is as follows:
Description: Simple OSG application that loads a model and allows navigation.
Objectives: Understand how to load a model, add it to a scene graph, and return the root to VR Juggler.
Member functions:
vrj::OsgApp::initScene(),
vrj::OsgApp::getScene()
Directory:
$VJ_BASE_DIR/share/vrjuggler/samples/OSG/simple/osgNav
Files: OsgNav.h,
OsgNav.cpp
The following application class is called
osgNav. It is derived from
vrj::OsgApp and has custom
initScene(),
getScene(),
configSceneView(),
preFrame(), and
latePreFrame() methods declared.
Refer to OsgNav.h for the
implementation of
setModelFileName(). Note that we
will ignore the code for remote navigation via a Tweek-based
GUI in this description.
1 class OsgNav : public vrj::OpenSGApp
{
public:
OsgNav(vrj::Kernel* kern, int& argc, char** argv);
5 virtual ~OsgNav();
virtual void configSceneView();
virtual void initScene();
10 void myInit();
virtual osg::Group* getScene();
virtual void preFrame();
virtual void latePreFrame();
15
void setModelFileName(std::string filename);
private:
osg::Group* mRootNode;
20 osg::Group* mNoNav;
osg::MatrixTransform* mNavTrans;
osg::MatrixTransform* mModelTrans;
osg::Node* mModel;
25 OsgNavigator mNavigator;
std::string mFileToLoad;
vpr::Interval mLastPreFrameTime;
30
public:
gadget::PositionInterface mWand;
gadget::PositionInterface mHead;
gadget::DigitalInterface mButton0;
35 gadget::DigitalInterface mButton1;
gadget::DigitalInterface mButton2;
gadget::DigitalInterface mButton3;
gadget::DigitalInterface mButton4;
gadget::DigitalInterface mButton5;
40 };
The implementation of
initScene() is in
OsgNav.cpp. This method looks very
similar to the usual implementation of init() in other
application object examples. The important thing to note is
the last line of the method body where myInit() is
invoked.
1 void OsgNav::initScene()
{
mWand.init("VJWand");
mHead.init("VJHead");
5 mButton0.init("VJButton0");
mButton1.init("VJButton1");
mButton2.init("VJButton2");
mButton3.init("VJButton3");
mButton4.init("VJButton4");
10 mButton5.init("VJButton5");
myInit();
}Within the myInit() method,
we see the real work for initializing the scene. In this
method, we create the scene graph root node, the lighting
node, and load a user-specified model. The implementation,
found in OsgNav.cpp, follows:
void OsgNav::myInit()
{
//
// /-- mNoNav
// mRootNode
// \-- mNavTrans -- mModelTrans -- mModel
//The top level nodes of the tree
mRootNode = new osg::Group();
mNoNav = new osg::Group();
mNavTrans = new osg::MatrixTransform();
mNavigator.init();
mRootNode->addChild(mNoNav);
mRootNode->addChild(mNavTrans);
//Load the model
std::cout << "Attempting to load file: "
<< mFileToLoad << "... " << std::flush;
mModel = osgDB::readNodeFile(mFileToLoad);
std::cout << "done." << std::endl;
// Transform node for the model
mModelTrans = new osg::MatrixTransform();
//This can be used if the model orientation needs to change
mModelTrans->preMult(
osg::Matrix::rotate(gmtl::Math::deg2Rad(-90.0f),
1.0f, 0.0f, 0.0f)
);
if(NULL == mModel)
{
std::cout << "ERROR: Could not load file: "
<< mFileToLoad << std::endl;
}
else
{
// Add model to the transform
mModelTrans->addChild(mModel);
}
// Add the transform to the tree
mNavTrans->addChild(mModelTrans);
// run optimization over the scene graph
osgUtil::Optimizer optimizer;
optimizer.optimize(mRootNode);
}We begin by creating the nodes that will make up the application scene graph. Note that this scene graph will contain two branches: one for nodes that will be affected by user navigation and one for nodes that will not. | |
Next, we attempt to load the model provided
through an earlier call to
| |
The model will be attached to the scene graph under a transform node, so we must create that node next. | |
If the model was loaded successfully
( | |
Whether the named model was loaded successfully or not, we attach the model transform to the scene graph under the navigation-enabled branch. When the model is loaded successfully, this allows the user to fly around the model. | |
Finally, we use
|
The method
vrj::OsgApp::draw() will call the
application's getScene() method to
get the root of the scene graph. The implementation of this
method can be found in OsgNav.h. The
code is as follows:
osg::Group* OsgNav::getScene()
{
return mRootNode;
}The simplicity of this method implementation is not
limited to the simple tutorial from which it is taken. All
OSG-based VR Juggler applications can take advantage of this
idiom where the root node is a member variable returned in
getScene().