Open Inventor Release 2024.2.0
 
Loading...
Searching...
No Matches
Creating a Manipulator

A manipulator is derived from another class of node, such as an SoTransform, SoLight, or SoCamera. 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 is a subclass of SoPointLight. It adds an SoPointLightDragger 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, it also executes the light commands. When an SoPointLightManip 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, SoPointLightManip, SoSpotLightManip, and SoDirectionalLightManip. 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, 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. 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, you also need to create the surround-scale part, since transform manipulators typically surround the things they affect (see Creating a Compound Dragger).

Deriving a Class from SoTransformManip

Examples 8-7 and 8-8 show the RotTransManip class, which employs a RotTransDragger to edit an SoTransform node.

Example : RotTransManip.h

#include <Inventor/manips/SoTransformManip.h>
class RotTransManip : public SoTransformManip
{
SO_NODE_HEADER( RotTransManip );
public:
// Constructor
RotTransManip();
// Initialize the class. This should be called once
// after SoInteraction::init(),
// TranslateRadialDragger::init().
// and RotTransDragger::init().
static void initClass();
static void exitClass();
private:
// Destructor
~RotTransManip();
};

Example : RotTransManip.c++

#include <Inventor/nodes/SoSurroundScale.h>
SO_NODE_SOURCE( RotTransManip );
// Initialize the type ID for this manipulator node. This
// should be called once after SoInteraction::init(),
// TranslateRadialDragger::initClass()
// and RotTransDragger::initClass()
void
RotTransManip::initClass()
{
SO_NODE_INIT_CLASS( RotTransManip, SoTransformManip, "TransformManip" );
}
RotTransManip::RotTransManip()
{
SO_NODE_CONSTRUCTOR( RotTransManip );
// Create a new dragger and call setDragger(),
// a method inherited from SoTransformManip.
RotTransDragger* myDrag = new RotTransDragger;
setDragger( myDrag );
// We want this manipulator to surround the objects it
// affects when we put it in a scene. So create the
// surroundScale node.
SoSurroundScale* mySS = ( SoSurroundScale* )myDrag->getPart( "surroundScale", TRUE );
mySS->numNodesUpToContainer = 4;
mySS->numNodesUpToReset = 3;
}
RotTransManip::~RotTransManip() {}

Overview

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 because it provides a user interface for editing the SoCoordinate3 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”.

Implementing Actions for the Manipulator

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 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 Creating a Node).

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

Deriving a Class from SoCoordinate3

Coord3Manip.h shows the header file for the Coord3Manip class. 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:

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

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 : Coord3Manip.h

#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;
private:
// 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 : Coord3Manip.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 );
}
}
}