8.3. Creating a Manipulator

A manipulator is derived from another class of node, such as an SoTransform( C++ | Java | .NET ), SoLight( C++ | Java | .NET ), or SoCamera( C++ | Java | .NET ). It employs a dragger to edit the fields of that node and adds the dragger to the node as a hidden child. For example, the SoPointLightManip( C++ | Java | .NET ) is a subclass of SoPointLight( C++ | Java | .NET ). It adds an SoPointLightDragger( C++ | Java | .NET ) as a hidden child of the point light. When this manipulator is rendered, it draws a point-light dragger and, because it is a subclass of SoPointLight( C++ | Java | .NET ), it also executes the light commands. When an SoPointLightManip( C++ | Java | .NET ) is used, moving the dragger causes the light in the scene to move because the manipulator is responsible for maintaining the consistency between its own fields and the fields of its dragger.

Creating a new manipulator involves two kinds of work:

  1. First, you need to create a base class for the new kind of manipulator, if it does not already exist. The Inventor library provides four base classes for manipulators: SoTransformManip( C++ | Java | .NET ), SoPointLightManip( C++ | Java | .NET ), SoSpotLightManip( C++ | Java | .NET ), and SoDirectionalLightManip( C++ | Java | .NET ). This step requires more work than the following step.

  2. Next, you need to derive a new manipulator from this base class to employ the new dragger. This step is easy.

Examples 8-7 and 8-8 show the code to create RotTransManip, a subclass of SoTransformManip( C++ | Java | .NET ), so you do not need to create the base manipulator class (that is, you can skip step 1). You may be asking why anyone would want to derive a new manipulator from one of the existing base classes. The reason is that your new manipulator can use a different dragger, which creates a different look and feel for the user interface. For example, the handle box and trackball manipulators, which you're already familiar with, both edit the fields of an SoTransform( C++ | Java | .NET ). But their user interfaces look and operate differently from each other.

All you need to do in such cases is to create a new class of node and set the dragger in the constructor (step 2). If you are creating a manipulator derived from SoTransformManip( C++ | Java | .NET ), you also need to create the surround-scale part, since transform manipulators typically surround the things they affect (see Section 8.2, “Creating a Compound Dragger”).

Examples 8-7 and 8-8 show the RotTransManip class, which employs a RotTransDragger to edit an SoTransform( C++ | Java | .NET ) node.



Examples 8-9 and 8-10 create the Coord3Manip class, which requires more work than the RotTransManip. Use the SO_NODE_HEADER() and SO_NODE_SOURCE() macros found in SoSubNode.h. Follow the same basic steps required for any node class, plus some additional ones, as follows:

  1. Select a name for the new manipulator class and determine what class it is derived from. The new manipulator will be a subclass of the kind of node you want to provide a user interface for. For example, Coord3Manip is a subclass of SoCoordinate3( C++ | Java | .NET ) because it provides a user interface for editing the SoCoordinate3( C++ | Java | .NET ) node.

  2. Define an initClass() method to initialize type information for the manipulator. Use the SO_NODE_INIT_CLASS() macro.

  3. Define a constructor for the manipulator. The constructor defines and names any new fields required by the manipulator using the SO_NODE_ADD_FIELD() macro. It also creates the field sensor (see step 7) and uses the setDragger() method to add the correct dragger as a child of this manipulator.

  4. Define a destructor for the manipulator. If the manipulator created a field sensor in the constructor, it will need to delete it in the destructor.

  5. Write the routines for replacing the “regular” node in the scene graph with the editable manipulator node and for putting it back ( replaceNode() and replaceManip()).

  6. Write the value-changed callback on the dragger to update the manipulator's field if the dragger's location changes.

  7. Write the field sensor callback on the manipulator to update the dragger if the field in the manipulator changes.

  8. Implement a setDragger() method to allow the dragger to be replaced by another dragger.

  9. Implement a copy() method. The copy() method first copies the manipulator node and its field data. Then it copies the manipulator's children.

  10. Implement the actions supported by the manipulator. See the section called “Implementing Actions for the Manipulator”.

Example 8.10, “ Coord3Manip.c++ provides a typical model for how most manipulators implement actions. The doAction() method for this new manipulator is similar to that for a group: it simply traverses the manipulator's children.

For most other actions, the manipulator first traverses its children (including the dragger) using doAction() and then calls the method on the base class. See the callback(), GLRender(), handleEvent(), and pick() methods in Example 8.10, “ Coord3Manip.c++.

Exceptions are the getMatrix() and getBoundingBox() methods. The getMatrix() method does not use doAction() because it doesn't need to traverse all the children. It performs the same tests as SoGroup::getMatrix() (see Chapter 2).

The getBoundingBox() method first traverses the children, but it does some extra work to determine the center of the group. Then it traverses the base class (in the example, “this” is SoCoordinate3( C++ | Java | .NET )).

Example 8.9, “ Coord3Manip.h shows the header file for the Coord3Manip class. Example 8.10, “ Coord3Manip.c++ shows the source code for this new class. Although these methods are quite lengthy, they are almost identical for all manipulator base classes. The only difference is the class name used. Also, in the replaceNode() and replaceManip() methods, the appropriate fields must be transferred from the old node to the new one. In this case, the field, point, is the only one copied.

The replaceNode() method must take care of the case where the child is owned by a node kit as well as the case where the child is simply a member of the group. To replace a node that is a part within a node kit, you must set the part by name:


C++
pointFieldSensor->detach();
point = oldPart->point;
Coordinate3Manip::fieldSensorCB(this, NULL);
pointFieldSensor->attach(&point);

lastKit->setPart(partName, this);

To replace a node that is not contained in a node kit, you simply call replaceChild():


C++
pointFieldSensor->detach();
point = ((SoCoordinate3 *)myFullPTail)->point;
Coordinate3Manip::fieldSensorCB(this, NULL);
pointFieldSensor->attach(&point);

((SoGroup *)parent)->replaceChild(myFullPTail, this);

Similarly, replaceManip() must deal separately with nodes that are owned by node kits and nodes that are not.

Example 8.9.  Coord3Manip.h


C++
#include <Inventor/draggers/SoDragger.h>
#include <Inventor/fields/SoSFLong.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/sensors/SoFieldSensor.h>

class Coordinate3Manip : public SoCoordinate3
{
   SO_NODE_HEADER(Coordinate3Manip);

  public:
   // Constructor
   Coordinate3Manip();

   // The index of the 'point' field that will be edited 
   // by our child-dragger.
   SoSFLong editIndex;

   // Returns the dragger node being employed by this manip.
   SoDragger *getDragger();

   virtual SoNode *copy(SbBool copyConnections = FALSE) const;

   // For replacing a regular SoCoordinate3 node with this
   // manipulator.
   SbBool replaceNode(SoPath *p);

   // For replacing this manipulator with a regular 
   // SoCoordinate3 node.
   SbBool replaceManip(SoPath *p, SoCoordinate3 *newOne) const;

   // These functions implement all actions for this manip.
   // They first traverse the children, then use the 
   // SoCoordinate3 version of the actions. They traverse first 
   // so that the SoCoordinate3 will affect objects which 
   // follow it in the tree, but not the dragger-child.
   virtual void doAction(SoAction *action);
   virtual void callback(SoCallbackAction *action);
   virtual void GLRender(SoGLRenderAction *action);
   virtual void getBoundingBox(SoGetBoundingBoxAction *action);
   virtual void getMatrix(SoGetMatrixAction *action);
   virtual void handleEvent(SoHandleEventAction *action);
   virtual void pick(SoPickAction *action);
   virtual void search(SoSearchAction *action);

   // call this after SoInteraction::init();
   static void initClass();
   static void exitClass();

   virtual SoChildList *getChildren() const;

  protected:

   // When the dragger moves, this interprets the translation 
   // field of the dragger and sets the point field of this 
   // node accordingly.
   static void valueChangedCB(void *,SoDragger *);

   // When the point field of this node changes, moves the
   // child-dragger to a new location, if necessary.
   SoFieldSensor *pointFieldSensor;
   static void fieldSensorCB(void *, SoSensor *);

   // Establishes the given dragger as the new child-dragger
   void setDragger(SoDragger *newDragger);

   // The hidden children.
   SoChildList *children;

  private:

   // Destructor
   ~Coordinate3Manip();

   int getNumChildren() const 
{ return (children->getLength()); }
};    


Example 8.10.  Coord3Manip.c++


C++
#include <Inventor/actions/SoCallbackAction.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/actions/SoGetMatrixAction.h>
#include <Inventor/actions/SoHandleEventAction.h>
#include <Inventor/actions/SoPickAction.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/draggers/SoDragPointDragger.h>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/fields/SoSFLong.h>
#include <Inventor/nodes/SoGroup.h>

// Include file for our new class
#include "Coordinate3Manip.h"

SO_NODE_SOURCE(Coordinate3Manip);

//  Initializes the type ID for this manipulator node. This
//  should be called once after SoInteraction::init().
void
Coordinate3Manip::initClass()
{
   SO_NODE_INIT_CLASS(Coordinate3Manip, SoCoordinate3,
                      "Coordinate3");
}

void
Coordinate3Manip::exitClass()
{
   SO__NODE_EXIT_CLASS(Coordinate3Manip);
}

Coordinate3Manip::Coordinate3Manip()
{
   children = new SoChildList(this);

   SO_NODE_CONSTRUCTOR(Coordinate3Manip);

   // Create the new 'editIndex' field
   SO_NODE_ADD_FIELD(editIndex, (0));

   // Create the field sensor
   pointFieldSensor = new SoFieldSensor(
            &Coordinate3Manip::fieldSensorCB, this);
   pointFieldSensor->setPriority(0);
   pointFieldSensor->attach(&point);

   // Create a new SoDragPointDragger and use
   // it for our child-dragger.
   setDragger(new SoDragPointDragger);
}

Coordinate3Manip::~Coordinate3Manip()
{
   // Important to do this because dragger may have callbacks
   // to this node.
   setDragger(NULL);

   if (pointFieldSensor!=NULL)
     delete pointFieldSensor;
   delete children;
}

// Sets the dragger to be the given node...
// Adds it as a child and adds a valueChangedCallback  
// on the child to tell this node when the dragger moves.
void
Coordinate3Manip::setDragger(SoDragger *newDragger)
{
   SoDragger *oldDragger = getDragger();
   if (oldDragger) {
     oldDragger->removeValueChangedCallback(
              &Coordinate3Manip::valueChangedCB,this);
     children->remove(0);
   }
      
   if (newDragger!=NULL) {
     if (children->getLength() > 0)
       children->set(0, newDragger);
     else
       children->append(newDragger);
     // Call the fieldSensorCB to transfer our values 
     // into the new dragger.
     Coordinate3Manip::fieldSensorCB(this, NULL);
     newDragger->addValueChangedCallback(
              &Coordinate3Manip::valueChangedCB,this);
   }
}

// Returns value of the current dragger.
SoDragger *
Coordinate3Manip::getDragger()
{
   if (children->getLength() > 0) {
     SoNode *n = (*children)[0];
     if (n->isOfType(SoDragger::getClassTypeId()))
       return ((SoDragger *) n);
     else {
#ifdef DEBUG
       SoDebugError::post("Coordinate3Manip::getDragger",
                          "Child is not a dragger!");
#endif
     }
   }
   return NULL;
}

// Description:
//    Replaces the tail of the path with this manipulator.
//
//    [1] Tail of fullpath must be correct type, or we return.
//    [2] If path has a nodekit, we try to use setPart() to 
//        insert manip. otherwise:
//    [3] Path must be long enough, or we return without 
//    [4] replacing.Second to last node must be a group, or we //        return without replacing.
//    [5] Copy values from node we are replacing into this manip
//    [6] Replace this manip as the child.
//    [7] Do not ref or unref anything. Assume that the user 
//        knows what he's doing.
//    [8] Do not fiddle with either node's field connections. 
//        Assume that the user knows what he's doing.
//
SbBool
Coordinate3Manip::replaceNode(SoPath *inPath)
{
   SoFullPath *myFullPath = (SoFullPath *) inPath;

   SoNode     *myFullPTail = myFullPath->getTail();
   if (!myFullPTail->isOfType (Coordinate3Manip::getClassTypeId())) {
#ifdef DEBUG
     SoDebugError::post("Coordinate3Manip::replaceNode", 
                        "End of path is not a Coordinate3Manip");
#endif
     return FALSE;
   }

   SoNode *pTail = inPath->getTail();
   if (pTail->isOfType(SoBaseKit::getClassTypeId())) {

     // Okay, we've got a nodekit here! Let's do this right...
     // If myFullPTail is a part in the kit, then we've got to 
     // follow protocol and let the kit set the part itself.
     SoBaseKit *lastKit = 
                (SoBaseKit*)((SoNodeKitPath*)inPath)->getTail();
     SbString partName = lastKit->getPartString(inPath);
     if (partName != "") {
       SoCoordinate3 *oldPart =
                (SoCoordinate3 *) lastKit->getPart(partName, TRUE); 
       if (oldPart != NULL) {

         // Detach the sensor while copying the values.
         pointFieldSensor->detach();
         point = oldPart->point;
         Coordinate3Manip::fieldSensorCB(this, NULL);
         pointFieldSensor->attach(&point);

         lastKit->setPart(partName, this);
         return TRUE;
       }
       else {
         // Although the part's there, we couldn't get at it.
         // Some kind of problem going on
         return FALSE;
       }
     }
     // If it's not a part, that means it's contained within a 
     // subgraph underneath a part. For example, it's within 
     // the 'contents' separator of an SoWrapperKit. In that 
     // case, the nodekit doesn't care and we just continue on
     // through...
   }

   if (myFullPath->getLength() < 2) {
#ifdef DEBUG
     SoDebugError::post("Coordinate3Manip::replaceNode",
                       "Path is too short!");
#endif
     return FALSE;
   }

   SoNode      *parent = myFullPath->getNodeFromTail(1);
   if (!parent->isOfType( SoGroup::getClassTypeId() )) {
#ifdef DEBUG
     SoDebugError::post("Coordinate3Manip::replaceNode",
                        "Parent node is not a group.!");
#endif
     return FALSE;
   }

   ref();

   // Detach the sensor while copying the values.
   pointFieldSensor->detach();
   point = ((SoCoordinate3 *) myFullPTail)->point;
   Coordinate3Manip::fieldSensorCB(this, NULL);
   pointFieldSensor->attach(&point);

   ((SoGroup *) parent)->replaceChild(myFullPTail, this);

   unrefNoDelete();
   return TRUE;
}

// Replaces tail of path (which should be this manipulator)
// with the given SoCoordinate3 node.
//
//    [1] Tail of fullpath must be this node, or we return.
//    [2] If path has a nodekit, we try to use setPart() to 
//        insert new node. otherwise:
//    [3] Path must be long enough, or we return without 
//    [4] replacing. Second to last node must be a group, or we
//        return without replacing.
//    [5] Copy values from node we are replacing into this manip
//    [6] Replace this manip as the child.
//    [7] Do not ref or unref anything. Assume that the user 
//        knows what he's doing.
//    [8] Do not fiddle with either node's field connections. 
//        Assume that the user knows what he's doing.
//
SbBool
Coordinate3Manip::replaceManip(SoPath *path, SoCoordinate3 
                               *newOne) const
{
   SoFullPath *myFullPath = (SoFullPath *) path;

   SoNode     *myFullPTail = myFullPath->getTail();
   if (myFullPTail != this) {
#ifdef DEBUG
     SoDebugError::post("Coordinate3Manip::replaceManip",
                        "Child to replace is not this manip!");
#endif
     return FALSE;
   }

   SoNode *pTail = path->getTail();
   if (pTail->isOfType(SoBaseKit::getClassTypeId())) {

     // Okay, we've got a nodekit here! Let's do this right...
     // If myFullPTail is a part in the kit, then we've got to 
     // follow protocol and let the kit set the part itself.
     SoBaseKit *lastKit = (SoBaseKit *) 
                         ((SoNodeKitPath*) path)->getTail();
     SbString partName = lastKit->getPartString(path);
     if (partName != "") {
   
       if (newOne != NULL)
         newOne->point = point;
     
         lastKit->setPart(partName, newOne);
         return TRUE;
     }
     // If it's not a part, that means it's contained within a
     // subgraph underneath a part. For example, it's within the 
     // 'contents' separator of an SoWrapperKit. In that case, 
     // the node kit doesn't care and we just continue on
     // through...
   }

   if (myFullPath->getLength() < 2) {
#ifdef DEBUG
     SoDebugError::post("Coordinate3Manip::replaceManip", 
                        "Path is too short!");
#endif
     return FALSE;
   }
   SoNode      *parent = myFullPath->getNodeFromTail(1);
   if (! parent->isOfType(SoGroup::getClassTypeId())) {
#ifdef DEBUG
     SoDebugError::post("Coordinate3Manip::replaceManip",
                        "Parent node is not a group.!");
#endif
     return FALSE;
   }

   if (newOne == NULL)
     newOne = new SoCoordinate3;
   newOne->ref();
   newOne->point = point;
   ((SoGroup *) parent)->replaceChild((Coordinate3Manip *) this,
                                      newOne);
   newOne->unrefNoDelete();

   return TRUE;
}
//    Creates and returns an exact copy...
SoNode *
Coordinate3Manip::copy(SbBool copyConnections) const
{
   // Create a copy of the node and fieldData
   Coordinate3Manip *newManip = (Coordinate3Manip *) 
     SoCoordinate3::copy(copyConnections);

   // Copy the children
   for (int i = 0; i < children->getLength(); i++)
     newManip->children->append(
       (*children)[i]->copy(copyConnections));

   return newManip;
}

//    Returns the child list...
SoChildList *
Coordinate3Manip::getChildren() const
{
   return children;
}

void 
Coordinate3Manip::doAction(SoAction *action)
{
   int         numIndices;
   const int   *indices;

   if (action->getPathCode(numIndices, indices) 
     == SoAction::IN_PATH)
     children->traverse(action, 0, indices[numIndices - 1]);
   else
     children->traverse(action);
}

// These functions implement all actions for Coordinate3Manip.
void
Coordinate3Manip::getMatrix(SoGetMatrixAction *action)
{
   int         numIndices;
   const int   *indices;

   switch (action->getPathCode(numIndices, indices)) {
     case SoAction::NO_PATH:
       break;
     case SoAction::IN_PATH:
       children->traverse(action, 0,indices[numIndices - 1]);
       break;
     case SoAction::BELOW_PATH:
       break;
     case SoAction::OFF_PATH:
       children->traverse(action);
       break;
   }
}

void 
Coordinate3Manip::callback(SoCallbackAction *action)
{ 
   Coordinate3Manip::doAction(action);
   SoCoordinate3::callback(action);
}

void 
Coordinate3Manip::getBoundingBox(
   SoGetBoundingBoxAction *action)
{ 
   SbVec3f     totalCenter(0,0,0);
   int         numCenters = 0;
   int         numIndices;
   const int   *indices;
   int         lastChild;

   if (action->getPathCode(numIndices, indices) 
     == SoAction::IN_PATH)
     lastChild = indices[numIndices - 1];
   else
     lastChild = getNumChildren() - 1;

   // Traverse the children
   for (int i = 0; i <= lastChild; i++) {
     children->traverse(action, i, i);
     if (action->isCenterSet()) {
       totalCenter += action->getCenter();
       numCenters++;
       action->resetCenter();
     }
   }

   // Traverse this as an SoCoordinate3
   SoCoordinate3::getBoundingBox(action);
   if (action->isCenterSet()) {
     totalCenter += action->getCenter();
     numCenters++;
     action->resetCenter();
   }

   // Now, set the center to be the average:
   if (numCenters != 0)
     action->setCenter(totalCenter / numCenters, FALSE);
}

void 
Coordinate3Manip::GLRender(SoGLRenderAction *action)
{
   Coordinate3Manip::doAction(action); 
   SoCoordinate3::GLRender(action);
}

void 
Coordinate3Manip::handleEvent(SoHandleEventAction *action)
{ 
   Coordinate3Manip::doAction(action); 
   SoCoordinate3::handleEvent(action);
}

void 
Coordinate3Manip::pick(SoPickAction *action)
{ 
  Coordinate3Manip::doAction(action); 
  SoCoordinate3::pick(action);
}

void
Coordinate3Manip::valueChangedCB(void *inManip, 
   SoDragger *inDragger)
{
   Coordinate3Manip *manip = (Coordinate3Manip *) inManip;

   SbMatrix motMat = inDragger->getMotionMatrix();
   SbVec3f location = motMat[3];

   // Disconnect the field sensor
   manip->pointFieldSensor->detach();

   int ind = (int) manip->editIndex.getValue();

   // Set value of the point if it's different.
   if (ind < manip->point.getNum()) {
     if (manip->point[ind] != location)
       manip->point.set1Value(ind,location);
   }

   // Reconnect the field sensors
   manip->pointFieldSensor->attach(&manip->point);
}

void
Coordinate3Manip::fieldSensorCB(void *inManip, SoSensor *)
{
   Coordinate3Manip *manip = (Coordinate3Manip *) inManip;

   int ind = manip->editIndex.getValue();

   // Set value of the point if it's different.
   if (ind < manip->point.getNum()) {

     SoDragger *dragger = manip->getDragger();

     if (dragger!=NULL) {
       SbVec3f location = manip->point[ind];
       SbMatrix newMat = dragger->getMotionMatrix();
       newMat[3][0] = location[0];
       newMat[3][1] = location[1];
       newMat[3][2] = location[2];

       dragger->setMotionMatrix(newMat);
     }
   }
}