Open Scene Graph Applications

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.

Figure 5.5. vrj::OsgApp application class

vrj::OsgApp application class

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.

Scene Graph Initialization: vrj::OsgApp::initScene()

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.

Important

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().

Scene Graph Access: vrj::OsgApp::getScene()

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.

Possible Misuses

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().

Tutorial: Loading a Model with Open Scene Graph

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

Class Declaration

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 initScene() Member Function

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();                                 1
   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);                    2
   std::cout << "done." << std::endl;

   // Transform node for the model
   mModelTrans  = new osg::MatrixTransform();                    3
   //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)                                            4
   {
      std::cout << "ERROR: Could not load file: "
                << mFileToLoad << std::endl;
   }
   else
   {
      // Add model to the transform
      mModelTrans->addChild(mModel);                             5
   }
   
   // Add the transform to the tree
   mNavTrans->addChild(mModelTrans);                             6

   // run optimization over the scene graph
   osgUtil::Optimizer optimizer;
   optimizer.optimize(mRootNode);                                7
}

1

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.

2

Next, we attempt to load the model provided through an earlier call to setModelFileName(). In this application, the user must identify the model to load on the command line when running the application. The code for handling this can be found in the main() function.

3

The model will be attached to the scene graph under a transform node, so we must create that node next.

4 5

If the model was loaded successfully (mModel is not NULL), then we attach it to the scene graph under the freshly created model transform node.

6

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.

7

Finally, we use osgUtil::Optimizer to optimize the scene graph that we have built up.

The getScene() Member Function

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().