Part 2: Setting up GLUT to work with Open Inventor

This part of the tutorial will teach you how to setup GLUT in order to work with Open Inventor. This information is partially based on sample code from the Coin3D project.

If you do not know how to use GLUT, I would recommend to have a look at the OpenGL tutorials on this site, as all of them use glut.

If you are new to programming with some of the following may look a bit complicated, but you must not worry. You can build fairly complicated Inventor applications before you need to change the design of the following!

Before starting with the actual code, four more Inventor header files as well as GLUT's header must be included (highlighted in yellow) :

#include SoDB.h>
#include SoSceneManager.h>

#include SoDirectionalLight.h>
#include SoPerspectiveCamera.h>
#include 
#include 
#include 
#include 

#include 
#include 

Then we can do the usual GLUT staff; initialize GLUT, open a window, and assign callbacks for the display, reshape, and idle functions before entering GLUT's mail loop glutMainLoop().

I chose to explain first the design of the main function and then of the others. Lets first create our functions prototypes. These are the function createScene discussed in part 1 of the tutorial plus the usual GLUT functions. 

SoSeparator* createScene(void) ;
void idle(void) ;
void reshape(int w, int h) ;
void display(void) ;
void redraw(void * user, SoSceneManager * manager) ;
 
SoSceneManager *scenemanager;

Please note the declaration of the global variable scenemanager. This is declared globally for reasons that will be explained shortly.

When working with GLUT based, OpenGL applications one of the first things to do is to initialise GLUT by calling glutInit. Inventor programs need to initialise the Open Inventor database also. This can be done by calling DB::init() . Then SoDB::getVersion() can be used to print on the console the version of Open Inventor we are using.

int main(int argc, char ** argv)
{
  //Initialise the Open Inventor Database
  SoDB::init();	
  
  //print Inventor's version on the console
  printf("Open Inventor version: %s\n",SoDB::getVersion()) ;

  //initialise GLUT
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); 

After initialising the two libraries a scene manager (SoSceneManager) must be created and several properties of it must be set. Its responsibility is to perform rendering and event handling on the scene graph, creating the resulting OpenGL rendered image. The scene manager must be global to the whole program as it needs to be accessed from several functions. Remember that the scenemanager was declared globally at the top of the file.

  scenemanager = new SoSceneManager ;

Now that the scene manager is created we must set some of its properties. Before doing so we should first prepare the actual Inventor graph that we want rendered to the screen.

If you remember our function called createScene returned a SoSeparator node containing some shape hints, materials, draw styles and sphere nodes. This is a perfectly valid Invetor graph but before rendering it to the screen we must add a light and a camera in the hierarchy. To do so we will firstly create a new separator node. This node must be referenced by calling the ref method.

This is need because of the way Inventor deletes its nodes. Referencing nodes is a field that we chose not to discuss here as we try to keep the tutorial fairly simple. Just remember that the root (top most) node of the graph should always be referenced as in the opposite case it will be deleted and the application will crash. This node should be dereferenced when the application is terminated.

  SoSeparator *root ;

  root = new SoSeparator;
  root->ref();

Now we can add a perspective camera to the graph (a SoPerspectiveCamera node), a light (in this case a SoDirectionalLight), and finally the whole graph we created previously:

  //add a camera to the graph
  SoPerspectiveCamera *perspCamera = new SoPerspectiveCamera;
  root->addChild(perspCamera);

  //add a light
  root->addChild(new SoDirectionalLight);
  //finally add our scene by calling createScene()
  root->addChild(createScene()) ;

Now that our whole graph is ready for rendering we can continuw with setting up the scene manager. First, we must set its render callback. This function is called whenever something in the scene changes, and it should contain a call to the render function:

scenemanager->setRenderCallback(redraw, NULL);

The redraw function can be seen here:

void redraw(void * user, SoSceneManager * manager)
{
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  scenemanager->render();

  glutSwapBuffers();
}

This function makes sure that OpenGL's depth testing and lights are turned on, calls the render method of the scene graph to render the Inventor graph (in the back buffer) and finally updates GLUT's double buffer by calling glutSwapBuffers.

Back to the main function we can now set the background color by calling the setBackgroundColor method of the scenemanager, and finally activate the scene manager by calling activate. A scene manager must always be activated to render a scene graph to the screen.

  scenemanager->setBackgroundColor(SbColor(0.0, 0.0, 0.0)); //BLACK COLOR
  scenemanager->activate();

Finally we tell the scene manager which scene graph we want to render by calling the function setSceneGraph. The parameter to this function is the previously referenced root node:

  scenemanager->setSceneGraph(root);

The scene manager is now set and ready to render the graph to the screen. Before doing so we can call a very useful camera method called viewAll which makes sure that the camera is positioned and oriented in such way that views the whole scene.

This function needs two parameters. The first is the root node of the graph we want to view and the second is the viewport of the GLUT window. The viewport of the window can be found by calling the getViewportRegion method of the scene manager.

  //view the whole scene
  perspCamera->viewAll(root, scenemanager->getViewportRegion());

Now we can create the GLUT based window and set its properties. Some callback functions are also registered here, before finally calling glutMainLoop which will only exit when the application terminates.

  glutInitWindowSize(400, 400);
  SbString title("Open Inventor Tutorial 1 : www.dev-gallery.com");
  glutCreateWindow(title.getString());
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutIdleFunc(idle);

  glutMainLoop();

When this point of the program will be reached we can be sure that the user (or the system ;-)) terminated our application; thus, we can safely dereference the root node (similar to manually deleting it) and finally delete the scene manager instance:

  root->unref();
  delete scenemanager;

  return 0;
}

Our application and tutorial is now nearly finished, the only remaining topic being the three callback functions display, reshape, and idle.

The display function is the usual GLUT display function but much simpler, as the only thing that it does is to turn on lights and depth testing before calling the render method of the scene manager and swapping GLUT's buffers.

// Redraw on expose events.
void display(void)
{
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  scenemanager->render();

  glutSwapBuffers();
}

The reshape function is also similar to the normal one but this timeit contains some Inventor calls. This function is called whenever the window changes size, so its task is to update the scene manager window and viewport settings.

// Reconfigure on changes to window dimensions.
void reshape(int w, int h)
{
  scenemanager->setWindowSize(SbVec2s(w, h));
  scenemanager->setSize(SbVec2s(w, h));
  scenemanager->setViewportRegion(SbViewportRegion(w, h));
  scenemanager->scheduleRedraw();
}

As you can see, first the new window size is set by calling setWindowSize with the parameters of the reshape function, which are the new width and height of the window. Then the size and origin of the viewport are set by calling setSize. setOrigin is not needed as we will use the default (0,0).

Then the viewport region is set and finally a call to scheduleRedraw is done. This function makes sure that a redraw will be performed as soon as possible. This is similar to GLUT's glutPostRedisplay.

Finally, the idle function:

void idle(void)
{
  SoDB::getSensorManager()->processTimerQueue();
  SoDB::getSensorManager()->processDelayQueue(TRUE);
}

which makes sure that all Inventor queues are processed in order for any animation and interaction to work. This tutorial does not contain any of these, so we could safely not use it, but is generally good practice to do so.

This is the end of our first Open Inventor tutorial. I hope that I made it easy enough for everybody to understand without problems. If you found some bits difficult to understand please use the site's forum.

 Here you can download the source of this tutorial as well as a VC++6 project file and windows executable. Remember that you must first install the Inventor SDK. You can do so by following the links in the main page.

Index
Part 1: Building a simple Inventor Graph
Part 2: Setting up GLUT to work with Open Inventor