8.2. Creating a Compound Dragger

In this section, you will learn how simple draggers can be combined to make a more complex compound dragger. Compound draggers can typically perform several different operations, such as scaling and translating. The SoCenterballDragger SoCenterballDragger SoCenterballDragger andSoTransformBoxDragger SoTransformBoxDragger SoTransformBoxDragger are examples of compound draggers.

Unlike simple draggers, which can perform only one operation, compound draggers can typically do many things. The operation the user performs is determined by the part of the compound dragger that is first clicked upon.

The steps involved in creating a compound dragger are best illustrated through example. This section shows how to create a RotTransDragger, which—as its name implies—allows both rotations and translations. The rotation parts of this compound dragger are made from a set of three SoRotateCylindricalDraggers , one for each axis we wish to allow the user to rotate about. The translation part of the dragger is an instance of the TranslateRadialDragger that we created in the previous section.

Two nodes are introduced in this section: the SoAntiSquish SoAntiSquish SoAntiSquish node and the SoSurroundScale SoSurroundScale SoSurroundScale node. These nodes are often useful in compound draggers and in manipulators. The compound dragger created in this section uses an SoAntiSquish SoAntiSquish SoAntiSquish node. Its catalog also contains an SoSurroundScale SoSurroundScale SoSurroundScale node, which is not used by default. The RotTransManip manipulator created at the end of this chapter uses a RotTransDragger and turns on the SoSurroundScale SoSurroundScale SoSurroundScale part. This enables the manipulator to have its dragger geometry surround the other objects that will move along with it.

SoAntiSquish Node

The SoAntiSquish SoAntiSquish SoAntiSquish node makes scaling uniform so that draggers and manipulators retain their shape even if the current transformation contains a nonuniform scale. When an action is applied to this node, it decomposes the current transformation matrix into a rotation, a translation, and a scale. If the scale is nonuniform, it replaces the current transformation matrix with a new one that uses the same rotation and translation, but a uniform scale.

This node has one field, sizing, which controls how to make the scale uniform. Possible values for this field are as follows:

AVERAGE_DIMENSION

averages the x, y, and z scale value (default).

BIGGEST_DIMENSION

replaces all three scale values with the largest scale value.

SMALLEST_DIMENSION

replaces all three scale values with the smallest scale value.

LONGEST_DIAGONAL

constructs a unit cube at the origin and transforms it by the current transformation matrix. To do this, it finds the longest diagonal of that transformed cube. Then, it sets the uniform scale to be half the length of this diagonal. (If the cube is being sheared, the LONGEST_DIAGONAL gives you the greatest distance from any point in the cube to the origin. With shearing, this distance could be greater than BIGGEST_DIMENSION.)

SoSurroundScale Node

The SoSurroundScale SoSurroundScale SoSurroundScale node is used to cause a dragger or manipulator to surround certain objects in the scene. This node is typically used when you create an SoTransformManip SoTransformManip SoTransformManip from a dragger. Although this part is included in the catalog for the RotTransDragger, it is not actually constructed by the dragger and is left as NULL.

This node examines what you want it to surround in the scene graph and determines how large the objects are. It then adds a scale and a translation to the current transformation matrix so that it surrounds those objects. For a manipulator, these are usually the objects affected by the movement of the manipulator.

Figure 8.4, “ Using an SoSurroundScale Node in a Transform Manipulator ” shows a scene graph containing a transform manipulator and a cube. The dragger within the manipulator includes a surround-scale node. The top separator node is the container node. In addition to the manipulator, it contains a cube, which the manipulator surrounds. The transform manipulator is the reset node. The manipulator surrounds everything below the container node and to the right of the reset node.

The SoSurroundScale SoSurroundScale SoSurroundScale node has two fields:

numNodesUpToContainer

number of nodes up the current path to the node that contains the objects to surround

numNodesUpToReset

number of nodes up the chain to the reset node (that is, the node where the bounding box is emptied)

In Figure 8.4, “ Using an SoSurroundScale Node in a Transform Manipulator ”, numNodesUpToContainer equals 4 and numNodesUpToReset equals 3. (Note that numNodesUpToReset must be smaller than numNodesUpToContainer, or there will be no reset node.) The result is that the geometry of the dragger now surrounds the cube.

Defining the Parts

Several operations must be completed when you are designing the parts of a compound dragger:

  • Define the desired operations of the dragger—for this example, the operations are a rotation about each of the three axes, as well as arbitrary translation.

  • Choose the simple dragger to suit each operation—for rotations about a fixed axis, you can use the SoRotateCylindricalDragger SoRotateCylindricalDragger SoRotateCylindricalDragger . (You could also use theSoRotateDiscDragger SoRotateDiscDragger SoRotateDiscDragger , which rotates about a fixed axis; however, its behavior is not well suited for this application, since it does not allow rotation when looking edge-on. It is better for dial-type draggers.) For the arbitrary translation part, you can use the TranslateRadialDragger created in the previous section.

  • Get the resource names and correlate them to part names—for example, the SoRotateCylindricalDragger SoRotateCylindricalDragger SoRotateCylindricalDragger is the XRotator part in the RotTransDragger. The compound dragger needs to create its own resource name for each part of each simple dragger. By convention, Inventor concatenates the part name as follows: compound dragger name/simple dragger part name/name of the part within the simple dragger. In the RotTransDragger, the three rotators share the same resources.

  • Determine the geometry of the parts—by default, the simple draggers use their default part geometry. Typically, what gives compound draggers their flair is the way they redefine this default geometry in an interesting way. This redefinition is done in the Inventor file for the dragger. In the RotTransDragger, the default cylinder of each SoRotateCylindricalDragger SoRotateCylindricalDragger SoRotateCylindricalDragger is replaced by a thin ring (actually a thin cylinder with its top and bottom removed). The RotTransDragger does not display any feedback for the SoRotateCylindricalDraggers because the richer geometry of the compound dragger provides sufficient visual information for the user. (The feedback parts are set to be an empty separator.)

    Using an SoSurroundScale Node in a Transform Manipulator

    Figure 8.4.  Using an SoSurroundScale Node in a Transform Manipulator



Once all of these things have been determined, you can create a default geometry file for the compound dragger. The following geometry file defines the geometry for all parts in the translator and rotators that make up the RotTransDragger. It uses the same naming conventions used in the previous section describing the simple dragger. At this point, you also need to create a default include file for the compiled-in geometry. As described in the previous section, run the ivToIncludeFile utility to translate the .iv file into an array of hexadecimal numbers.

Example 8.4, “ rotTransDragger.iv shows the default geometry file for RotTransDragger.

Example 8.4.  rotTransDragger.iv

#Inventor V2.0 ascii

# Geometry resource file for the RotTransDragger

# Geometry for the rotating parts.
DEF rotTransRotatorRotator Separator {
    Material { 
       diffuseColor  .05 .18 .125
       emissiveColor .05 .18 .125
    }
    DrawStyle { lineWidth 2 }
    Cylinder {
	       radius 1.85
       	height .15
	       parts SIDES
    }
}

DEF rotTransRotatorRotatorActive Separator {
    Material { 
       	diffuseColor  .05 .2025 .18
	       emissiveColor .05 .2025 .18
    }
    DrawStyle { lineWidth 2 }
    Cylinder {
	       radius 1.85
	       height .15
	       parts SIDES
    }
}

# Do not display the axis feedback used
# by the cylinder manips.
DEF rotTransRotatorFeedback Separator { }
DEF rotTransRotatorFeedbackActive Separator { }

DEF rotTransTranslatorTranslator Separator {
    Material { diffuseColor .6 .6 .6 }
    DrawStyle { style LINES }
    Sphere { radius 1.732 }
}

DEF rotTransTranslatorTranslatorActive Separator {
    Material { diffuseColor .6 .6 0 }
    DrawStyle { style LINES }
    Sphere { radius 1.732 }
}

# Don't show anything for feedback during inactive state.
DEF rotTransTranslatorFeedback Separator { }

DEF rotTransTranslatorFeedbackActive Separator {
    Material { diffuseColor .5  .9 .9 }
    # An arrow aligned with the x-axis.
    RotationXYZ {
	       axis Z
	       angle 1.57079
    }
    Separator {

       	#stick
       	Cylinder { height 4.0 radius 0.05 }

       	#left arrowhead
	       Translation { translation 0 2.2 0 }
	       Cone { height 0.4 bottomRadius 0.2 }

       	#right arrowhead
       	Translation { translation 0 -4.4 0 }
       	RotationXYZ { axis Z angle 3.14159 }
	       Cone { height 0.4 bottomRadius 0.2 }
   }
}


Figure 8.5, “ Structure of the RotTransDragger shows the scene graph for the RotTransDragger class. Example 8.5, “ RotTransDragger.h ” shows the header file for this class.

Example 8.5.  RotTransDragger.h

//  Geometry resources and part names for this dragger:

//  Resource Names:                     Part Names:
// rotTransTranslatorTranslator      translator.translator
// rotTransTranslatorTranslatorActive
//                                   translator.translatorActive
// rotTransTranslatorFeedback        translator.feedback
// rotTransTranslatorFeedbackActive  translator.feedbackActive

// rotTransRotatorRotator             XRotator.rotator
// rotTransRotatorRotatorActive       XRotator.rotatorActive
// rotTransRotatorFeedback            XRotator.feedback
// rotTransRotatorFeedbackActive      XRotator.feedbackActive
// (and similarly for parts of the YRotator and ZRotator)

#include <Inventor/sensors/SoFieldSensor.h>
#include <Inventor/draggers/SoDragger.h>
#include <Inventor/fields/SoSFVec3f.h>
#include <Inventor/fields/SoSFRotation.h>

class TranslateRadialDragger;
class SoRotateCylindricalDragger;

class RotTransDragger : public SoDragger
{
   SO_KIT_HEADER(RotTransDragger);

   // Makes the dragger surround other objects
   SO_KIT_CATALOG_ENTRY_HEADER(surroundScale);



Structure of the RotTransDragger

Figure 8.5.  Structure of the RotTransDragger



// Keeps the dragger evenly sized in all 3 dimensions
   SO_KIT_CATALOG_ENTRY_HEADER(antiSquish);

   // The translating dragger...
   SO_KIT_CATALOG_ENTRY_HEADER(translator);

   // The X and Z rotators need to be turned so as to orient 
   // correctly. So create a separator part and put an 
   // SoRotation node and the dragger underneath.
   SO_KIT_CATALOG_ENTRY_HEADER(XRotatorSep);
   SO_KIT_CATALOG_ENTRY_HEADER(XRotatorRot);
   SO_KIT_CATALOG_ENTRY_HEADER(XRotator);

   SO_KIT_CATALOG_ENTRY_HEADER(YRotator);

   SO_KIT_CATALOG_ENTRY_HEADER(ZRotatorSep);
   SO_KIT_CATALOG_ENTRY_HEADER(ZRotatorRot);
   SO_KIT_CATALOG_ENTRY_HEADER(ZRotator);

  public:
  
   // Constructor
   RotTransDragger();

   // These fields reflect state of the dragger at all times.
   SoSFRotation rotation;
   SoSFVec3f   translation;

   // This should be called once after SoInteraction::init().
   static void initClass();

  protected:

   // These sensors ensure that the motionMatrix is updated 
   // when the fields are changed from outside.
   SoFieldSensor *rotFieldSensor;
   SoFieldSensor *translFieldSensor;
   static void fieldSensorCB(void *, SoSensor *);

   // This function is invoked by the child draggers when they 
   // change their value.
   static void valueChangedCB(void *, SoDragger *);

   // Called at the beginning and end of each dragging motion.
   // Tells the "surroundScale" part to recalculate.
   static void invalidateSurroundScaleCB(void *, SoDragger *);

   // This will detach/attach the fieldSensor.
   // It is called at the end of the constructor (to attach).
   // and at the start/end of SoBaseKit::readInstance()
   // and on the new copy at the start/end of SoBaseKit::copy()
   // Returns the state of the node when this was called.
   virtual SbBool setUpConnections( SbBool onOff, 
                        SbBool doItAlways = FALSE);

   // This allows us to specify that certain parts do not
   // write out. We'll use this on the antiSquish and
   // surroundScale parts.
   virtual void setDefaultOnNonWritingFields();

  private:

   static const char geomBuffer[];

   // Destructor.
   ~RotTransDragger();
};   

Initializing the Compound Dragger

The complete source file for the RotTransDragger is shown in Example 8.6, “ RotTransDragger.c++. The process of initializing the compound dragger is identical to that of the simple dragger (see the section called “Initializing the Dragger Class”).

Constructor

Many of the steps for constructing the compound dragger are the same as those for a simple dragger. This section describes the similarities and differences. The basic steps for constructing a dragger, described first in the section called “Constructor”, are as follows:

a. Use SO_KIT_CONSTRUCTOR() to set up the internal variables for the class.

b. Define the catalog entries for the new dragger.

c. Put the default parts into the global dictionary.

d. Create the parts list and the parts that are created by default in this dragger using SO_KIT_INIT_INSTANCE().

e. Create the special-interest field or fields for the dragger.

f. Create the parts for the dragger.

g. Set the switches to inactive (if your dragger uses active/inactive pairs of parts).

h. Create the projector.

i. Add the dragger callback functions.

j. Add the value-changed callback function.

k. Put a sensor on the special-interest field (or fields).

l. Call the setUpConnections() method to attach the field sensors.

Steps a through e

Steps a through e are similar for simple and compound draggers.

Step a, using SO_KIT_CONSTRUCTOR(), is the same for simple and compound draggers.

Step b is to define the catalog entries for the dragger, which is actually a node kit. Use the SO_KIT_ADD_CATALOG_ENTRY() macro to define the catalog entries. Arrange the simple draggers relative to each other, as shown earlier in Figure 8.5, “ Structure of the RotTransDragger . Each simple dragger has its own separator, used for caching. The simple draggers can use the cache at the topSeparator node if none of the draggers is changing. In compound draggers such as this one, the geomSeparator, provided by the base SoDragger SoDragger SoDragger class, is not used.

Step c is to put the default parts into the global dictionary:

if (SO_KIT_IS_FIRST_INSTANCE())
   readDefaultParts("rotTransDragger.iv", geomBuffer, 
                     sizeof(geomBuffer));

Step d is to create the parts list and the default parts using the macro SO_KIT_INIT_INSTANCE().

Step e is to create the special-interest fields for this compound dragger: a rotation field and a translation field:

SO_KIT_ADD_FIELD(rotation, (0.0, 0.0, 0.0, 1.0));
SO_KIT_ADD_FIELD(translation, (0.0, 0.0. 0.0);

Step f: Creating the Parts

Step f is to create the parts for the node kit. This step is more involved for a compound dragger than for a simple dragger. At this point, you need to do the following things:

  1. Construct the antisquish node. For the RotTransDragger, the sizing field of the SoAntiSquish SoAntiSquish SoAntiSquish node is set to BIGGEST_DIMENSION. As a result, the largest of the three scale values is used as the uniform scale value.

  2. Create the simple draggers.

  3. Create the rotation nodes in the XRotatorRot and ZRotatorRot parts. The rotation node in the XRotatorRot part aligns the cylindrical rotate dragger with the x-axis (in the default position, it rotates about the y-axis). The rotation node in the ZRotatorRot part aligns the cylindrical rotate dragger with the z-axis.

Steps g through i

Steps g through i are all performed by the child draggers. The parent dragger class does not define these functions.

Step j: Value-Changed Callback Function

Step j is to update the rotation and translation fields in the dragger when the motion matrix changes (see the section called “Value-Changed Callback”, where this step was performed for the simple dragger).

The code for the RotTransDragger is

addValueChangedCallback(&RotTransDragger::valueChangedCB);

Step k: Field Sensors

Conversely, you need to update the motion matrix when the translation or rotation field changes. This dragger places a sensor on the translation field and another sensor on the rotation field. Both sensors use the same callback function, fieldSensorCB, defined later in the source file.

// Updates motionMatrix when either field changes.
rotFieldSensor = new SoFieldSensor(
                     &RotTransDragger::fieldSensorCB, this);
rotFieldSensor->setPriority(0);
translFieldSensor = new SoFieldSensor(
                        &RotTransDragger::fieldSensorCB,this);
translFieldSensor->setPriority(0);

setUpConnections(TRUE, TRUE);

Step l: Setting Up Connections

The setUpConnections() method is used to connect and disconnect the dragger's field connections, callback functions, and sensors. This method performs the following operations:

  1. Calls the base class setUpConnections() method.

  2. Sets up the geometry of its child draggers.

    For each child dragger in the compound dragger, the setUpConnections() method calls getAnyPart() to build and return the dragger.

    Then it calls setPartAsDefault() after looking up the replacement parts in the global dictionary.

  3. Adds the start and finish callback functions.

    The SoSurroundScale SoSurroundScale SoSurroundScale node does its bounding-box calculations when it is built and when its invalidate() method is called. For efficiency, the recalculation is performed only at the beginning and end of a drag. In beween, the dragger continues to draw at the same size. Register the invalidateSurroundScaleCB() callback function for each simple dragger.

  4. Registers the child draggers.

    It is worth describing in more detail what happens when you call the registerChildDragger() method for each simple dragger in the compound dragger. This method binds the child and parent draggers together to function as a unit in two main ways. First, it causes the parent dragger's callback functions to be called after any of the child dragger's callback functions are called. Second, it causes all child draggers to move as a unit: whenever the user clicks and drags on one child dragger, the other draggers move in unison with the first dragger.

    When you call registerChildDragger(), the following things happen automatically. A value-changed callback function is added to monitor the motion in the child dragger. When the child dragger moves, the callback transforms that motion into the compound dragger's space, applying it to the compound dragger as a whole. It then zeros out the child dragger's motion so that the child is not moved relative to the whole.

    If you create a new dragger and for some reason you don't want the pieces of the compound dragger to move as a whole, you can use the registerChildDraggerCallbacksOnly() method, which doesn't transfer the child dragger's motion to the parent dragger. (The spotlight dragger is an example of a dragger that uses this method. When the cone widens, the rest of the dragger remains unchanged.)

  5. Attaches the field sensors (as for simple draggers).

Destructor

Since the child draggers are nodes, they are destroyed automatically when the parent node is destroyed. There are no projectors to destroy as with the simple dragger, since no projectors were created here. The destructor merely needs to delete the sensor.

Callback Functions

The RotTransDragger uses three callback functions, described above:

invalidateSurroundScaleCB()

called when dragging starts and finishes

valueChangedCB()

called when the motion matrix changes

fieldSensorCB()

called when the special-interest field or fields change

The source file for the compound dragger must define each of these callback functions.

Example 8.6, “ RotTransDragger.c++ shows the source code for RotTransDragger.c++:

Example 8.6.  RotTransDragger.c++

#include <Inventor/nodes/SoAntiSquish.h>
#include <Inventor/nodes/SoRotation.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoSurroundScale.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/sensors/SoFieldSensor.h>

// Include files for child dragger classes.
#include <Inventor/draggers/SoRotateCylindricalDragger.h>
#include "TranslateRadialDragger.h"

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

// This file contains RotTransDragger::geomBuffer, which 
// describes the default geometry resources for this class.
#include "RotTransDraggerGeom.h"

SO_KIT_SOURCE(RotTransDragger);


//  Initializes the type ID for this dragger node. This
//  should be called once after SoInteraction::init().
void
RotTransDragger::initClass()
{
   SO_KIT_INIT_CLASS(RotTransDragger, SoDragger, "Dragger");    
}

RotTransDragger::RotTransDragger()
{
   SO_KIT_CONSTRUCTOR(RotTransDragger);

   // Don't create "surroundScale" by default. It's only put 
   // to use if this dragger is used within a manipulator.
   SO_KIT_ADD_CATALOG_ENTRY(surroundScale, SoSurroundScale, TRUE,
                            topSeparator, geomSeparator, TRUE);
   // Create an anti-squish node by default.
   SO_KIT_ADD_CATALOG_ENTRY(antiSquish, SoAntiSquish, FALSE,
                            topSeparator, geomSeparator, TRUE);
   SO_KIT_ADD_CATALOG_ENTRY(translator, TranslateRadialDragger,
                            TRUE, topSeparator, geomSeparator,
                            TRUE);
   SO_KIT_ADD_CATALOG_ENTRY(XRotatorSep, SoSeparator, FALSE,
                            topSeparator, geomSeparator, FALSE);
   SO_KIT_ADD_CATALOG_ENTRY(XRotatorRot, SoRotation, TRUE,
                            XRotatorSep, , FALSE);
   SO_KIT_ADD_CATALOG_ENTRY(XRotator,SoRotateCylindricalDragger,
                            TRUE, XRotatorSep, ,TRUE);

   SO_KIT_ADD_CATALOG_ENTRY(YRotator, SoRotateCylindricalDragger,
                            TRUE, topSeparator, geomSeparator, TRUE);

   SO_KIT_ADD_CATALOG_ENTRY(ZRotatorSep, SoSeparator, FALSE,
                            topSeparator, geomSeparator, FALSE);
   SO_KIT_ADD_CATALOG_ENTRY(ZRotatorRot, SoRotation, TRUE,
                            ZRotatorSep, ,FALSE);
   SO_KIT_ADD_CATALOG_ENTRY(ZRotator, SoRotateCylindricalDragger,
                            TRUE, ZRotatorSep, ,TRUE);

   // Read geometry resources. Only do this the first time we
   // construct one. 'geomBuffer' contains our compiled in
   // defaults. The user can override these by specifying new
   // scene graphs in the file:
   // $(SO_DRAGGER_DIR)/rotTransDragger.iv"
   if (SO_KIT_IS_FIRST_INSTANCE())
     readDefaultParts("rotTransDragger.iv", geomBuffer,
                       sizeof(geomBuffer));

   // Fields that always show current state of the dragger.
   SO_KIT_ADD_FIELD(rotation, (0.0, 0.0, 0.0, 1.0));
   SO_KIT_ADD_FIELD(translation, (0.0, 0.0, 0.0));

   // Creates parts list and default parts for this nodekit.
   SO_KIT_INIT_INSTANCE();

   // Make the anti-squish node surround the biggest dimension
   SoAntiSquish *myAntiSquish =
            SO_GET_ANY_PART(this, "antiSquish", SoAntiSquish);
   myAntiSquish->sizing = SoAntiSquish::BIGGEST_DIMENSION;

   // Create the simple draggers that comprise this dragger.
   // This dragger has four simple pieces:  
   //    1 TranslateRadialDragger
   //    3 RotateCylindricalDraggers
   // In the constructor, we just call SO_GET_ANY_PART to
   // build each dragger.
   // Within the setUpConnections() method, we will
   // take care of giving these draggers new geometry and 
   // establishing their callbacks.

   // Create the translator dragger.    
   SoDragger *tDragger = SO_GET_ANY_PART(this, "translator", 
                         TranslateRadialDragger);

   // Create the XRotator dragger.    
   SoDragger *XDragger = SO_GET_ANY_PART(this, "XRotator", 
                         SoRotateCylindricalDragger);

   // Create the YRotator dragger.    
   SoDragger *YDragger = SO_GET_ANY_PART(this, "YRotator", 
                         SoRotateCylindricalDragger);

   // Create the ZRotator dragger.    
   SoDragger *ZDragger = SO_GET_ANY_PART(this, "ZRotator", 
                         SoRotateCylindricalDragger);

   // Set rotations in "XRotatorRot" and "ZRotatorRot" parts.
   // These parts will orient the draggers from their default 
   // (rotating about Y) to the desired configurations.
   // By calling 'setAnyPartAsDefault' instead of 'setAnyPart'
   // we ensure that they will not be written out, unless
   // they are changed later on.
   SoRotation *XRot = new SoRotation;
   XRot->rotation.setValue(
     SbRotation(SbVec3f(0,1,0), SbVec3f(1,0,0)));
   setAnyPartAsDefault("XRotatorRot", XRot);

   SoRotation *ZRot = new SoRotation;
   ZRot->rotation.setValue(
     SbRotation(SbVec3f(0,1,0), SbVec3f(0,0,1)));
   setAnyPartAsDefault("ZRotatorRot", ZRot);

   // Updates the fields when motionMatrix changes 
   addValueChangedCallback(&RotTransDragger::valueChangedCB);

   // Updates motionMatrix when either field changes.
   rotFieldSensor = new SoFieldSensor(
                        &RotTransDragger::fieldSensorCB, this);
   rotFieldSensor->setPriority(0);
   translFieldSensor = new SoFieldSensor(
                           &RotTransDragger::fieldSensorCB,this);
   translFieldSensor->setPriority(0);

   setUpConnections(TRUE, TRUE);
}

RotTransDragger::~RotTransDragger()
{
   if (rotFieldSensor!=NULL)
     delete rotFieldSensor;
   if (translFieldSensor!=NULL)
     delete translFieldSensor;
}

SbBool
RotTransDragger::setUpConnections(SbBool onOff, SbBool doItAlways)
{
   if (!doItAlways && connectionsSetUp == onOff)
     return onOff;

   if (onOff) {
     // We connect AFTER base class.
     SoDragger::setUpConnections(onOff, doItAlways);

     // For each of the simple draggers that compries this:
     // [a]Call setPart after looking up our replacement parts 
     //    in the global dictionary.
     // [b]Add the invalidateSurroundScaleCB as a start and end
     //    callback. When using a surroundScale node, these 
     //    trigger it to recalculate a bounding box at the 
     //    beginning and end of dragging.
     // [c]Register the dragger as a 'childDragger' of this 
     //    one. This has the following effects: 
     //    [1] This dragger's callbacks will be invoked 
     //        following the child manip's callbacks.  
     //    [2] When the child is dragged, the child's motion 
     //        will be transferred into motion of the entire 
     //        dragger.
      SoDragger *tD =
               (SoDragger *) getAnyPart("translator", FALSE);
      // [a] Set up the parts in the child dragger...
      tD->setPartAsDefault("translator",
                           "rotTransTranslatorTranslator");
      tD->setPartAsDefault("translatorActive",
                           "rotTransTranslatorTranslatorActive");
      tD->setPartAsDefault("feedback",
                           "rotTransTranslatorFeedback");
      tD->setPartAsDefault("feedbackActive",
                           "rotTransTranslatorFeedbackActive");
      // [b] and [c] Add the callbacks and register the child
      tD->addStartCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      tD->addFinishCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      registerChildDragger(tD);

      SoDragger *XD =
               (SoDragger *) getAnyPart("XRotator", FALSE);
      // [a] Set up the parts in the child dragger...
      XD->setPartAsDefault("rotator",
                           "rotTransRotatorRotator");
      XD->setPartAsDefault("rotatorActive",
                           "rotTransRotatorRotatorActive");
      XD->setPartAsDefault("feedback",
                           "rotTransRotatorFeedback");
      XD->setPartAsDefault("feedbackActive",
                           "rotTransRotatorFeedbackActive");
      // [b] and [c] Add the callbacks and register the child
      XD->addStartCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      XD->addFinishCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      registerChildDragger(XD);

      SoDragger *YD =
               (SoDragger *) getAnyPart("YRotator", FALSE);
      // [a] Set up the parts in the child dragger...
      YD->setPartAsDefault("rotator",
                           "rotTransRotatorRotator");
      YD->setPartAsDefault("rotatorActive",
                           "rotTransRotatorRotatorActive");
      YD->setPartAsDefault("feedback",
                           "rotTransRotatorFeedback");
      YD->setPartAsDefault("feedbackActive",
                           "rotTransRotatorFeedbackActive");
      // [b] and [c] Add the callbacks and register the child
      YD->addStartCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      YD->addFinishCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      registerChildDragger(YD);

      SoDragger *ZD =
               (SoDragger *) getAnyPart("ZRotator", FALSE);
      // [a] Set up the parts in the child dragger...
      ZD->setPartAsDefault("rotator",
                           "rotTransRotatorRotator");
      ZD->setPartAsDefault("rotatorActive",
                           "rotTransRotatorRotatorActive");
      ZD->setPartAsDefault("feedback",
                           "rotTransRotatorFeedback");
      ZD->setPartAsDefault("feedbackActive",
                           "rotTransRotatorFeedbackActive");
      // [b] and [c] Add the callbacks and register the child
      ZD->addStartCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      ZD->addFinishCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      registerChildDragger(ZD);


     // Call the sensor CB to make things up-to-date.
     fieldSensorCB(this, NULL);

     // Connect the field sensors
     if (translFieldSensor->getAttachedField() != &translation)
        translFieldSensor->attach(&translation);
     if (rotFieldSensor->getAttachedField() != &rotation)
        rotFieldSensor->attach(&rotation);
   }
   else {
     // We disconnect BEFORE base class.

     // Remove the callbacks from the child draggers,
     // and unregister them as children.
      SoDragger *tD =
               (SoDragger *) getAnyPart("translator", FALSE);
      tD->removeStartCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      tD->removeFinishCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      unregisterChildDragger(tD);

      SoDragger *XD =
               (SoDragger *) getAnyPart("XRotator", FALSE);
      XD->removeStartCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      XD->removeFinishCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      unregisterChildDragger(XD);

      SoDragger *YD =
               (SoDragger *) getAnyPart("YRotator", FALSE);
      YD->removeStartCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      YD->removeFinishCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      unregisterChildDragger(YD);

      SoDragger *ZD =
               (SoDragger *) getAnyPart("ZRotator", FALSE);
      ZD->removeStartCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      ZD->removeFinishCallback(
               &RotTransDragger::invalidateSurroundScaleCB, this);
      unregisterChildDragger(ZD);

     // Disconnect the field sensors.
     if (translFieldSensor->getAttachedField()!=NULL)
        translFieldSensor->detach();
     if (rotFieldSensor->getAttachedField()!=NULL)
        rotFieldSensor->detach();

     SoDragger::setUpConnections(onOff, doItAlways);
   }

   return !(connectionsSetUp = onOff);
}

// Called when the motionMatrix changes. Sets the "translation"
// and "rotation" fields based on the new motionMatrix
void
RotTransDragger::valueChangedCB(void *, SoDragger *inDragger)
{
   RotTransDragger *myself = (RotTransDragger *) inDragger;

   // Factor the motionMatrix into its parts
   SbMatrix motMat = myself->getMotionMatrix();
   SbVec3f   trans, scale;
   SbRotation rot, scaleOrient;
   motMat.getTransform(trans, rot, scale, scaleOrient);

   // Set the fields. Disconnect the sensors while doing so.
   myself->rotFieldSensor->detach();
   myself->translFieldSensor->detach();
   if (myself->rotation.getValue() != rot)
     myself->rotation = rot;
   if (myself->translation.getValue() != trans)
     myself->translation = trans;
   myself->rotFieldSensor->attach(&myself->rotation);
   myself->translFieldSensor->attach(&myself->translation);
}

// If the "translation" or "rotation" field changes, changes
// the motionMatrix accordingly.
void
RotTransDragger::fieldSensorCB(void *inDragger, SoSensor *)
{
   RotTransDragger *myself = (RotTransDragger *) inDragger;

   SbMatrix motMat = myself->getMotionMatrix();
   myself->workFieldsIntoTransform(motMat);

   myself->setMotionMatrix(motMat);
}

// When any child dragger starts or ends a drag, tell the
// "surroundScale" part (if it exists) to invalidate its 
// current bounding box value.
void 
RotTransDragger::invalidateSurroundScaleCB(void *parent, SoDragger *)
{
   RotTransDragger *myParentDragger = (RotTransDragger *) parent;

   // Invalidate the surroundScale, if it exists.
   SoSurroundScale *mySS = SO_CHECK_PART(
            myParentDragger, "surroundScale", SoSurroundScale);
   if (mySS != NULL)
      mySS->invalidate();
}

void
RotTransDragger::setDefaultOnNonWritingFields()
{
   // The nodes pointed to by these part-fields may 
   // change after construction, but we
   // don't want to write them out.
   surroundScale.setDefault(TRUE);
   antiSquish.setDefault(TRUE);

   SoDragger::setDefaultOnNonWritingFields();
}