Chapter 14. Creating a User Interface

Real-time user interaction with complex databases is one of OpenGL Performer's strengths. This chapter describes how to create a user interface in the following sections:

Traveling through a Scene

Often you want to allow the user to travel through a scene using an input device, such as a mouse, as a guide for the motion. OpenGL Performer includes the transformer class, pfiTDFXformer, for manipulating the eyepoint.

OpenGL Performer provides three models for interpreting input device events:

  • Trackball—when the input device is a trackball and the input is translated into 3D motion.

  • Drive—where mouse events are translated into 2D motion.

  • Fly—where mouse events are translated into 3D motion.


    Note: pfiTDFXformer is a subclass of pfiXformer, which can be extended for custom motion models.


To use the transformer class to interpret mouse events as the means by which a user moves through a scene, use the following procedure:

The following sections explain this procedure.

Creating a Transformer

To create a transformer, you must:

  1. Initialize the utility library, libui, using:

    void pfiInit(void);
    

  2. Create the transformer in the shared memory arena, as follows:

    pfiTDFXformer *pfiNewTDFXformer(void *arena);
    

  3. Check that the shared memory arena is not NULL, as follows:

    if (pfGetSharedArena() = NULL) {
        // use memory allocated from the local heap
    }
    

The shared memory arena is that portion of memory that is available to all OpenGL Performer processes. Three common processes in OpenGL Performer are:

  • APP

  • CULL

  • DRAW

Each process handles only part of the rendering. When a process is finished operating on one frame, it returns the data to the shared memory arena so that another process can grab and process it, as shown in Figure 14-1.

Figure 14-1. Shared Memory Arena

Shared Memory Arena

Initializing the Transformer

To initialize the transformer, you must:

  1. Specify the motion model to use, using:

    void pfiSelectXformerModel(pfiXformer* XF, int model);
    

    where XF is the transformer created in the previous section, and model is one of three values:

    • PFITDF_TRACKBALL

    • PFITDF_DRIVE

    • PFITDF_FLY

    For more information on the motion models, see “Traveling through a Scene”.

  2. Specify the initial position of the viewpoint, using:

    void pfiXformerCoord(pfiXformer* XF, pfCoord* coord);
    

    where XF is the transformer created in the previous section, and coord is a structure containing a three-dimensional set of coordinates, and three rotation values around those three dimensions. For more information about pfCoord, see “Direction and Position of the View” in Chapter 5.

Setting Up Transformer Input and Output

To set up transformer input (using a mouse) and output, use the following steps:

  1. Make the transformer object created previously, XF, read from the mouse and events buffer, as follows:

    void pfiXformerAutoInput(pfiXformer *XF, pfChannel *chan, 
        pfuMouse *mouse, pfuEventStream *events);
    

    events points at the buffer containing the mouse events.

  2. Specify the channel, chan, to update with the mouse events, as follows:

    void pfiXformerAutoPosition(pfiXformer* XF, pfChannel *chan, 
        pfDCS *dcs);
    

    chan is updated for mouse events; dcs is updated for trackball events. If you do not want to interpret trackball input, set dcs to NULL.

  3. Collect mouse events, as follows:

    pfuInitInput(mainPipeWindow, PFUINPUT_X);
    pfuCollectInput(void);
    pfuGetMouse(mouse);
    

    mainPipeWindow is the pfPipeWindow where the channel, chan, is rendered.

     PFUINPUT_X specifies that mouse and keyboard events are read from a forked process using X device commands. In this case, mainPipeWindow, must be a GLX window.

    mouse is a pointer to the buffer for mouse events.

Updating the Channel

To read the events from the input buffers and update the view in the channel, use the following method:

void pfiUpdateXformer(pfiXformer *XF);

where XF is the transformer.

This method updates the channel automatically.

Scaling the Motion

It is necessary to scale the effect of the motion of the mouse according to the size of the scene. For example, if moving the mouse an inch moves the viewpoint in the scene 50 meters, a small shape might be hard to maneuver because the motion of the viewpoint is so fast. On the other hand, in a large scene, such motion might be scaled appropriately.

To scale the motion of the viewpoint according to the size of the scene, use the following procedure:

  1. Calculate the bounding box of the scene, as follows:

    void pfuTravCalcBBox(pfNode *node, pfBox *box);
    

    node is generally the root node of the scene graph. box returns the size of the bounding box of the scene. pfBox is a structure defined as follows:

    typedef struct {
        pfVec3 min;
        pfVec3 max;
    } pfBox;
    

    The minimum and maximum 3D coordinates specify the lower-left, upper-right, and front and back corner of the box, respectively.

  2. Adjust the speed and acceleration of the viewpoint based on the size of the bounding box, as follows:

    void pfiXformerLimits(pfiXformer* XF, float maxSpeed, 
        float angularVel, float maxAccel, pfBox* dbLimits);
    

    This method, in pfiXformer, sets the maximum speed of XF to maxSpeed, the angular velocity of XF to angularVel, the maximum acceleration of XF to maxAccel, and the bounds within which XF can move to be the bounding box, dbLimits.

Example of Implementing User Interaction

Example 14-1 shows in bold the code used to implement transforming the view according to the motion of a mouse.

Example 14-1. Implementing User Interaction

#include <Performer/pf.h>
#include <Performer/pfdu.h>
#include <Performer/pfutil.h>
#include <Performer/pfui.h>
#include <stdio.h>
 
 
/* Function prototypes */
 
void windowSetup(char *title);
void sceneSetup(char *filename);
void channelSetup(void);
void xformerSetup(void);
void handleEvents(void);
void printHelp(char *progName);
 
 
/* Global variables */
 
pfScene               *scene;
pfChannel             *chan;
char                  *progName;
int                   exitFlag = 0;
pfuEventStream        events;
		pfuMouse              mouse;
		pfiTDFXformer         *xformer;
 
 
int main(int argc, char *argv[])
{
    extern char            *progName;
    extern int             exitFlag;
    extern pfuMouse        mouse;
    extern pfiTDFXformer       *xformer;
    char             *filename = “esprit.flt”;
 
 
    /* Initialize Performer and create the pipe */
 
    pfInit();
    pfuInitUtil();
    pfiInit();
    pfConfig();
 
 
    /* Set up a window, scene graph, and channel */
 
    progName = argv[0];
    windowSetup(progName);
 
    if (argc >= 2) filename = argv[1];
    sceneSetup(filename);
 
    channelSetup();
    xformerSetup();
 
 
    /* Simulate */
 
    printHelp(progName);
    while ( !exitFlag) {
        pfuGetMouse(&mouse);
        pfiUpdateXformer(xformer);
        pfFrame();
        handleEvents();
    }
 
 
    /* Clean up */
 
    pfuExitInput();
    pfuExitUtil();
    pfExit(); 
    return 0;
}
 
 
void windowSetup(char *title)
{
    pfPipe                *pipe;
    pfPipeWindow          *win;
 
    pipe = pfGetPipe(0);
    win = pfNewPWin(pipe);
    pfPWinName(win, title);
    pfPWinSize(win, 500, 500);
 
    pfPWinType(win, PFPWIN_TYPE_X);
    pfuInitInput(win, PFUINPUT_X);
 
    pfOpenPWin(win);
}
 
 
void sceneSetup(char *filename)
{
    extern pfScene            *scene;
    pfNode                    *model;
    pfLightSource             *light;
 
    scene = pfNewScene();
 
    light = pfNewLSource();
    pfAddChild(scene, light);
 
    pfFilePath(“/usr/people/perf/pf_data”);
    model = pfdLoadFile(filename);
    pfAddChild(scene, model);
}
 
 
void channelSetup(void)
{
    extern pfScene             *scene;
    extern pfChannel           *chan;
    pfPipe                     *pipe;
    pfSphere                   bsphere;
pfEarthSky                     *esky;
    
    pipe = pfGetPipe(0);
    chan = pfNewChan(pipe);
    pfChanScene(chan, scene);
 
    pfGetNodeBSphere(scene, &bsphere);
    pfChanNearFar(chan, 1.0f, 10.0f * bsphere.radius);
    pfChanFOV(chan, 60.0f, -1.0f);
 
    esky = pfNewESky();
    pfESkyAttr(esky, PFES_GRND_HT, -0.1f);
    pfESkyColor(esky, PFES_GRND_NEAR, 0.0f, 0.4f, 0.0f, 1.0f);
    pfESkyColor(esky, PFES_GRND_FAR, 0.0f, 0.4f, 0.0f, 1.0f);
    pfESkyMode(esky, PFES_BUFFER_CLEAR, PFES_SKY_GRND);
 
    pfChanESky(chan, esky);
 
}
 
 
void xformerSetup(void)
{
    extern pfScene                *scene;
    extern pfChannel              *chan;
    extern pfuEventStream         events;
    extern pfiTDFXformer          *xformer;
    extern pfuMouse               mouse;
    pfCoord                       view;
    pfSphere                      bsphere;
    pfBox                         bbox;
    float                         speed;
 
    xformer = pfiNewTDFXformer(pfGetSharedArena());
    pfiXformerAutoInput(xformer, chan, &mouse, &events);
    pfiXformerAutoPosition(xformer, chan, NULL);
    pfiSelectXformerModel(xformer, PFITDF_FLY);
 
    pfGetNodeBSphere(scene, &bsphere);
    pfSetVec3(view.xyz, 0.0f, -2.0f * bsphere.radius, 1.0f);
    pfSetVec3(view.hpr, 0.0f, 0.0f, 0.0f);
    pfiXformerCoord(xformer, &view);
    pfiXformerResetCoord(xformer, &view);
 
    pfuTravCalcBBox(scene, &bbox);
    speed = bsphere.radius / 3.0f;
    pfiXformerLimits(xformer, speed, 90.0f, speed/2.0f, &bbox);
}
 
 
void handleEvents(void)
{
   extern pfuEventStream              events;
   extern char                       *progName;
   extern int                         exitFlag;
   extern pfiTDFXformer              *xformer;
   int                                i, j;
   int                                key, dev, val, numDevs;
   pfuEventStream                    *pEvents = &events;
 
   pfuGetEvents(&events);
   numDevs = pEvents->numDevs;
 
   for ( j=0; j < numDevs; ++j) {
      dev = pEvents->devQ[j];
      val = pEvents->devVal[j];
 
      if ( pEvents->devCount[dev] > 0 ) {
         switch ( dev ) {
 
          case PFUDEV_REDRAW:
            pEvents->devCount[dev] = 0;
            break;
 
          case PFUDEV_WINQUIT:
            exitFlag = 1;
            pEvents->devCount[dev] = 0;
            break;
 
          case PFUDEV_KEYBD:
            for ( i=0; i < pEvents->numKeys; ++i ) {
 
               key = pEvents->keyQ[i];
               if ( pEvents->keyCount[key] ) {
 
                  switch ( key ) {
                   case 27:                   /* ESC key. Exits prog */
                     exitFlag = 1;
                     break;
 
                   case `h':
                     printHelp(progName);
                     break;
 
                   case `r':
                     pfiStopXformer(xformer);
                     pfiResetXformerPosition(xformer);
                     break;
 
                   default:
                     break;
                  }
               }
            }
            pEvents->devCount[dev] = 0;
            break;
 
          default:
            break;
         }
      }
   }
   pEvents->numKeys = 0;
   pEvents->numDevs = 0;
}
 
 
void printHelp(char *progName)
{
    printf(“\n%s - using a transforme\n\n”
        “<h> key\t\t\t- print help\n”
                  “<r> key\t\t\t- reset transformer position\n”
        “ESCAPE key\t\t- exit the program\n\n”, 
        progName);
 
}