28.4. Using an SoCallback Node

A typical use of an SoCallback SoCallback SoCallback node is to make calls to OpenGL. At the beginning of the callback function, you need to check the action type and then proceed based on the type of action that has been applied to the node. Typically, you are interested in the render action:

if(action->isOfType(SoGLRenderAction::getClassTypeId()))
{
   
   ...execute rendering code ..

}
  
if(action is SoGLRenderAction)
{
   
   ...execute rendering code ..

}
  
if(action instanceof SoGLRenderAction)
{

  ...execute rendering code ..

}
  

The effects of a callback node may not be cacheable, depending on what it does. For example, if the callback node contains shapes whose geometry is changing, it should not be cached. In Example 28.1, “ Using a Callback Node, the callback node creates a checked background, which can be cached because it is not changing.

If a callback node relies on any information outside of Inventor that may change (such as a global variable), it should not be cached. To prevent Inventor from automatically creating a cache, use the SoCacheElement::- invalidate() method from within a callback. For example:

void
myCallback(void *myData, SoAction *action)
{
   if (action->isOfType(SoGLRenderAction::getClassTypeId()))
   {
      SoCacheElement::invalidate(action->getState());
      //makes sure this isn't cached
      //...make OpenGL calls that depend on a global variable...//
   }
}
    
void
MyCallback(SoAction action)
{
   if (action is SoGLRenderAction))
   {
      SoCacheElement.Invalidate(action.GetState());
      //makes sure this isn't cached
      //...make OpenGL calls that depend on a global variable...//
   }
}
   
callback.setCallback(new SoCallback.CB()
{
  public void invoke(SoAction action)
  {
    if ( action instanceof SoGLRenderAction )
    {
      SoCacheElement.invalidate(action.getState());
      //makes sure this isn't cached
      //...make OpenGL calls that depend on a global variable...//
    }
  }
}

Be careful when opening an OpenGL display list inside an SoCallback SoCallback SoCallback node. Recall from Chapter 8, Applying Actions that the Inventor render cache contains an OpenGL display list. Only one OpenGL display list can be open at a time, and a separator node above the callback node may have already opened a display list for caching. If your callback node opens a second display list, an error occurs. Use the SoCacheElement::anyOpen() method to check whether a cache is open.

Example 28.1, “ Using a Callback Node creates an Inventor render area. It uses Inventor to create a red cube and a blue sphere and then uses an SoCallback SoCallback SoCallback node containing GL calls to draw a checked “floor.” The floor is cached automatically by Inventor. Note that the SoXtRenderArea automatically redraws the scene when the window is resized. Example 28.2, “ Using a GLX Window, which uses a GLX window, does not redraw automatically.

Both Examples 17-2 and 17-3 produce the same image, shown in Figure 28.1, “ Combining Use of Inventor and OpenGL.


Example 28.1.  Using a Callback Node

#include <GL/gl.h>
#include <Inventor/SbLinear.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/SoXtRenderArea.h>
#include <Inventor/nodes/SoCallback.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoTransform.h>

float   floorObj[81][3];

// Build a scene with two objects and some light
void
buildScene(SoGroup *root)
{
   // Some light
   root->addChild(new SoLightModel);
   root->addChild(new SoDirectionalLight);

   // A red cube translated to the left and down
   SoTransform *myTrans = new SoTransform;    
   myTrans->translation.setValue(-2.0, -2.0, 0.0);
   root->addChild(myTrans);

   SoMaterial *myMtl = new SoMaterial;
   myMtl->diffuseColor.setValue(1.0, 0.0, 0.0);
   root->addChild(myMtl);
   
   root->addChild(new SoCube);

   // A blue sphere translated right
   myTrans = new SoTransform;    
   myTrans->translation.setValue(4.0, 0.0, 0.0);
   root->addChild(myTrans);

   myMtl = new SoMaterial;
   myMtl->diffuseColor.setValue(0.0, 0.0, 1.0);
   root->addChild(myMtl);
   
   root->addChild(new SoSphere);
}

// Build the floor that will be rendered using OpenGL.
void
buildFloor()
{
   int a = 0;

   for (float i = -5.0; i <= 5.0; i += 1.25) {
      for (float j = -5.0; j <= 5.0; j += 1.25, a++) {
         floorObj[a][0] = j;
         floorObj[a][1] = 0.0;
         floorObj[a][2] = i;
      }
   }
}

// Draw the lines that make up the floor, using OpenGL
void
drawFloor()
{
   int i;

   glBegin(GL_LINES);
   for (i=0; i<4; i++) {
      glVertex3fv(floorObj[i*18]);
      glVertex3fv(floorObj[(i*18)+8]);
      glVertex3fv(floorObj[(i*18)+17]);
      glVertex3fv(floorObj[(i*18)+9]);
   }

   glVertex3fv(floorObj[i*18]);
   glVertex3fv(floorObj[(i*18)+8]);
   glEnd();

   glBegin(GL_LINES);
   for (i=0; i<4; i++) {
      glVertex3fv(floorObj[i*2]);
      glVertex3fv(floorObj[(i*2)+72]);
      glVertex3fv(floorObj[(i*2)+73]);
      glVertex3fv(floorObj[(i*2)+1]);
   }
   glVertex3fv(floorObj[i*2]);
   glVertex3fv(floorObj[(i*2)+72]);
   glEnd();
}

// Callback routine to render the floor using OpenGL
void
myCallbackRoutine(void *, SoAction *)
{
   glPushMatrix();
   glTranslatef(0.0, -3.0, 0.0);
   glColor3f(0.0, 0.7, 0.0);
   glLineWidth(2);
   glDisable(GL_LIGHTING);  // so we don't have to set normals
   drawFloor();
   glEnable(GL_LIGHTING);   
   glLineWidth(1);
   glPopMatrix();
}


main(int, char **)
{
   // Initialize Inventor utilities
   Widget myWindow = SoXt::init("Example 17.1");

   buildFloor();

   // Build a simple scene graph, including a camera and
   // a SoCallback node for performing some GL rendering.
   SoSeparator *root = new SoSeparator;
   root->ref();

   SoPerspectiveCamera *myCamera = new SoPerspectiveCamera;
   myCamera->position.setValue(0.0, 0.0, 5.0);
   myCamera->heightAngle  = M_PI/2.0;  // 90 degrees
   myCamera->nearDistance = 2.0;
   myCamera->farDistance  = 12.0;
   root->addChild(myCamera);

   SoCallback *myCallback = new SoCallback;
   myCallback->setCallback(myCallbackRoutine);
   root->addChild(myCallback);

   buildScene(root);
   
   // Initialize an Inventor Xt RenderArea and draw the scene.
   SoXtRenderArea *myRenderArea = new SoXtRenderArea(myWindow);
   myRenderArea->setSceneGraph(root);
   myRenderArea->setTitle("OpenGL Callback");
   myRenderArea->setBackgroundColor(SbColor(.8, .8, .8));
   myRenderArea->show();
   
   SoXt::show(myWindow);
   SoXt::mainLoop();
}