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( C++ | Java | .NET ) andSoTransformBoxDragger( C++ | Java | .NET ) 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( C++ | Java | .NET ) node and the SoSurroundScale( C++ | Java | .NET ) node. These nodes are often useful in compound draggers and in manipulators. The compound dragger created in this section uses an SoAntiSquish( C++ | Java | .NET ) node. Its catalog also contains an SoSurroundScale( C++ | Java | .NET ) 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( C++ | Java | .NET ) part. This enables the manipulator to have its dragger geometry surround the other objects that will move along with it.

The SoAntiSquish( C++ | Java | .NET ) 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:

The SoSurroundScale( C++ | Java | .NET ) 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( C++ | Java | .NET ) 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( C++ | Java | .NET ) node has two fields:

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.

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

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.


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.




C++
// 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();
   static void exitClass();

  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();
};   

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

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.

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( C++ | Java | .NET ) 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.

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


C++
addValueChangedCallback(&RotTransDragger::valueChangedCB);

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( C++ | Java | .NET ) 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).

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.

The RotTransDragger uses three callback functions, described above:

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++


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");    
}

void
RotTransDragger::exitClass()
{
   SO__KIT_EXIT_CLASS(RotTransDragger);
}

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();
}