Skip to content

SoTransformer2Dragger.cpp

Mark Millard edited this page Apr 11, 2018 · 1 revision
/*!
  \class SoTransformer2Dragger SoTransformer2Dragger.h Inventor/draggers/SoTransformer2Dragger.h
  \brief The SoTransformer2Dragger provides geometry for translation, scaling and rotations.
  \ingroup draggers

  \DRAGGER_DEFAULT_SCREENSHOT

  <center>
  <img src="http://doc.coin3d.org/images/Coin/draggers/transformer.png">
  </center>

  Translate the dragger by clicking and dragging any of the
  (invisible) sides. Translation will default be done in the plane of
  the side the end-user selected. The user can hold down a \c SHIFT
  key to lock translation to a single of the axes in the plane. By
  holding down a \c CTRL key instead, translation can be done along
  the plane's normal vector.

  Scaling is done by dragging the corner cubes. By default, uniform
  scaling will be done. Hold down \c SHIFT before selecting any of the
  corners to do non-uniform scaling. Uniform scaling towards a
  corner-point can be accomplished by holding down \c CTRL before
  clicking and dragging one of the cubes.

  Rotation is done by dragging any of the 6 end-markers of the axis
  cross. The initial drag direction decides which orientation the
  rotation will be done in. Hold down \c SHIFT to do free-form
  rotation around the sphere instead.


  This is a big and complex dragger which needs a fair amount of
  proper documentation when provided in end-user applications. If what
  you are trying to accomplish in your application does not really
  demand most of the features of this dragger, you are advised to
  investigate whether or not any of the less complex draggers can
  fulfill your requirements -- so you can provide an as simple as
  possible user interface to your end-users.


  For the application programmer's convenience, the Coin library also
  provides a manipulator class called SoTransformer2Manip, which wraps
  the SoTransformer2Dragger into the necessary mechanisms for making
  direct insertion of this dragger into a scenegraph possible with
  very little effort.

  \sa SoTransformer2Manip
*/

#include <string.h>

#include <Inventor/draggers/SoTransformer2Dragger.h>
//#include <Inventor/nodekits/SoSubKitP.h>
#include "nodekits/SoSubKitP.h"
#include <Inventor/nodes/SoAntiSquish.h>
#include <Inventor/nodes/SoLocateHighlight.h>
#include <Inventor/nodes/SoRotation.h>
#include <Inventor/nodes/SoSurroundScale.h>
#include <Inventor/nodes/SoSwitch.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoTranslation.h>
#include <Inventor/nodes/SoMatrixTransform.h>
#include <Inventor/sensors/SoFieldSensor.h>
#include <Inventor/SbVec3f.h>
#include <Inventor/SbMatrix.h>
#include <Inventor/projectors/SbPlaneProjector.h>
#include <Inventor/projectors/SbLineProjector.h>
#include <Inventor/projectors/SbSphereSectionProjector.h>
#include <Inventor/projectors/SbCylinderPlaneProjector.h>
#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/actions/SoGetMatrixAction.h>
#include <Inventor/lists/SoPathList.h>

#include <data/draggerDefaults/transformer2Dragger.h>

#include <coindefs.h> // COIN_STUB() & COIN_OBSOLETED()

// FIXME, bugs or missing features (pederb, 20000224):
// o some feedback is missing (mostly crosshair)
// o detect if disc or cylinder rotator should be used (disc-only right now)
//
// Also the translation feedback is a bit different from OIV. Coin
// always places the feedback axes at the center of the face being
// translated. OIV places them at the picked point. I think our
// strategy is better, since when switching between constrained
// translations and unconstrained translation, the OIV feedback axes
// can easily be positioned outside the face being dragged.
//
// MATRICES AND SPACES:
// There are many matrices and spaces that can take some time to get
// understand. The matrices calculated are:
//
// localToWorld = motionMatrix * draggerToWorld
// worldToLocal = worldToDragger * motionMatrix^-1
// draggerToWorld = worldToDragger^-1
//
// localToWorking = surroundScaleMatrix^-1
// workingToLocal = surroundScaleMatrix
//
// workingToWorld = surroundScaleMatrix * localToWorld
// worldToWorking = worldToLocal * surroundScaleMatrix
// 
// boxPointInWorldSpace = p * surroundScaleMatrix * localToWorld
// worldPointInBoxSpace = p * worldToLocal * surroundScaleMatrix

/*!
  \enum SoTransformer2Dragger::State

  The various possible states the dragger might be in at any given
  time. That is: either SoTransformer2Dragger::INACTIVE if there's no
  interaction, or any of the other values to indicate what operation
  the end-user is currently executing.
*/

/*!
  \var SoSFRotation SoTransformer2Dragger::rotation

  This field is continuously updated to contain the orientation of the
  dragger.
*/
/*!
  \var SoSFVec3f SoTransformer2Dragger::translation

  The dragger's offset position from the local origo.
*/
/*!
  \var SoSFVec3f SoTransformer2Dragger::scaleFactor

  Continuously updated to contain the current vector of scaling along
  the X, Y and Z axes.
*/
/*!
  \var SoSFVec3f SoTransformer2Dragger::center

  The dragger's center position from the local origo.
*/

// FIXME: can't see what this is for -- investigate. 20011208 mortene.
//  /*!
//    \var SoSFFloat SoTransformer2Dragger::minDiscRotDot
//  */

/*!
  \var SoFieldSensor * SoTransformer2Dragger::translFieldSensor
  \COININTERNAL
*/
/*!
  \var SoFieldSensor * SoTransformer2Dragger::scaleFieldSensor
  \COININTERNAL
*/
/*!
  \var SoFieldSensor * SoTransformer2Dragger::rotateFieldSensor
  \COININTERNAL
*/
/*!
  \var SoFieldSensor * SoTransformer2Dragger::centerFieldSensor
  \COININTERNAL
*/
/*!
  \var SoNodeList SoTransformer2Dragger::antiSquishList
  \COININTERNAL
*/

#define WHATKIND_NONE      0
#define WHATKIND_SCALE     1
#define WHATKIND_TRANSLATE 2
#define WHATKIND_ROTATE    3

#define CONSTRAINT_OFF  0
#define CONSTRAINT_WAIT 1
#define CONSTRAINT_X    2
#define CONSTRAINT_Y    3
#define CONSTRAINT_Z    4

#define KNOB_DISTANCE 1.25f   // distance from center to rotate-knobs


#ifndef DOXYGEN_SKIP_THIS

class SoTransformer2DraggerP {
public:
  SbMatrix prevMotionMatrix;
  SbVec3f prevWorldHitPt;
  SbVec3f ctrlOffset;
  SbBool ctrlDown;
  SbBool shiftDown;
  SbVec2f normalizedStartLocaterPosition;

  SbBool locateHighlighting;
  static int colinearThreshold;
  int constraintState;

  int whatkind;
  int whatnum;
  int dimension;
};

int SoTransformer2DraggerP::colinearThreshold = 3; // FIXME: find default value from somewhere

#endif // DOXYGEN_SKIP_THIS

SO_KIT_SOURCE(SoTransformer2Dragger);

// doc in superclass
void
SoTransformer2Dragger::initClass(void)
{
  SO_KIT_INTERNAL_INIT_CLASS(SoTransformer2Dragger, SO_FROM_INVENTOR_1);
}

void
SoTransformer2Dragger::build_catalog1(void)
{
  SO_KIT_ADD_CATALOG_ENTRY(surroundScale, SoSurroundScale, TRUE, topSeparator, overallStyle, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(overallStyle, SoGroup, TRUE, topSeparator, geomSeparator, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translatorSep, SoSeparator, TRUE, topSeparator, rotatorSep, FALSE);
}

void
SoTransformer2Dragger::build_catalog2(void)
{
  SO_KIT_ADD_CATALOG_ENTRY(translator1Switch, SoSwitch, TRUE, translatorSep, translator2Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator1LocateGroup, SoLocateHighlight, TRUE, translator1Switch, translator1Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator1, SoSeparator, TRUE, translator1LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator1Active, SoSeparator, TRUE, translator1Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator2Switch, SoSwitch, TRUE, translatorSep, translator3Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator2LocateGroup, SoLocateHighlight, TRUE, translator2Switch, translator2Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator2, SoSeparator, TRUE, translator2LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator2Active, SoSeparator, TRUE, translator2Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator3Switch, SoSwitch, TRUE, translatorSep, translator4Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator3LocateGroup, SoLocateHighlight, TRUE, translator3Switch, translator3Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator3, SoSeparator, TRUE, translator3LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator3Active, SoSeparator, TRUE, translator3Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator4Switch, SoSwitch, TRUE, translatorSep, translator5Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator4LocateGroup, SoLocateHighlight, TRUE, translator4Switch, translator4Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator4, SoSeparator, TRUE, translator4LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator4Active, SoSeparator, TRUE, translator4Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator5Switch, SoSwitch, TRUE, translatorSep, translator6Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator5LocateGroup, SoLocateHighlight, TRUE, translator5Switch, translator5Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator5, SoSeparator, TRUE, translator5LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator5Active, SoSeparator, TRUE, translator5Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator6Switch, SoSwitch, TRUE, translatorSep, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator6LocateGroup, SoLocateHighlight, TRUE, translator6Switch, translator6Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translator6, SoSeparator, TRUE, translator6LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translator6Active, SoSeparator, TRUE, translator6Switch, "", TRUE);
}

void
SoTransformer2Dragger::build_catalog3(void)
{
  SO_KIT_ADD_CATALOG_ENTRY(translateToCenter, SoMatrixTransform, TRUE, topSeparator, rotatorSep, TRUE);

  SO_KIT_ADD_CATALOG_ENTRY(rotatorSep, SoSeparator, TRUE, topSeparator, scaleSep, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator1Switch, SoSwitch, TRUE, rotatorSep, rotator2Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator1LocateGroup, SoLocateHighlight, TRUE, rotator1Switch, rotator1Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator1, SoSeparator, TRUE, rotator1LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator1Active, SoSeparator, TRUE, rotator1Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator2Switch, SoSwitch, TRUE, rotatorSep, rotator3Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator2LocateGroup, SoLocateHighlight, TRUE, rotator2Switch, rotator2Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator2, SoSeparator, TRUE, rotator2LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator2Active, SoSeparator, TRUE, rotator2Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator3Switch, SoSwitch, TRUE, rotatorSep, rotator4Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator3LocateGroup, SoLocateHighlight, TRUE, rotator3Switch, rotator3Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator3, SoSeparator, TRUE, rotator3LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator3Active, SoSeparator, TRUE, rotator3Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator4Switch, SoSwitch, TRUE, rotatorSep, rotator5Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator4LocateGroup, SoLocateHighlight, TRUE, rotator4Switch, rotator4Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator4, SoSeparator, TRUE, rotator4LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator4Active, SoSeparator, TRUE, rotator4Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator5Switch, SoSwitch, TRUE, rotatorSep, rotator6Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator5LocateGroup, SoLocateHighlight, TRUE, rotator5Switch, rotator5Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator5, SoSeparator, TRUE, rotator5LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator5Active, SoSeparator, TRUE, rotator5Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator6Switch, SoSwitch, TRUE, rotatorSep, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator6LocateGroup, SoLocateHighlight, TRUE, rotator6Switch, rotator6Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator6, SoSeparator, TRUE, rotator6LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(rotator6Active, SoSeparator, TRUE, rotator6Switch, "", TRUE);
}

void
SoTransformer2Dragger::build_catalog4(void)
{
  SO_KIT_ADD_CATALOG_ENTRY(scaleSep, SoSeparator, TRUE, topSeparator, circleFeedbackSep, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale1Switch, SoSwitch, TRUE, scaleSep, scale2Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale1LocateGroup, SoLocateHighlight, TRUE, scale1Switch, scale1Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale1, SoSeparator, TRUE, scale1LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale1Active, SoSeparator, TRUE, scale1Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale2Switch, SoSwitch, TRUE, scaleSep, scale3Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale2LocateGroup, SoLocateHighlight, TRUE, scale2Switch, scale2Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale2, SoSeparator, TRUE, scale2LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale2Active, SoSeparator, TRUE, scale2Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale3Switch, SoSwitch, TRUE, scaleSep, scale4Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale3LocateGroup, SoLocateHighlight, TRUE, scale3Switch, scale3Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale3, SoSeparator, TRUE, scale3LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale3Active, SoSeparator, TRUE, scale3Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale4Switch, SoSwitch, TRUE, scaleSep, scale5Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale4LocateGroup, SoLocateHighlight, TRUE, scale4Switch, scale4Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale4, SoSeparator, TRUE, scale4LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale4Active, SoSeparator, TRUE, scale4Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale5Switch, SoSwitch, TRUE, scaleSep, scale6Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale5LocateGroup, SoLocateHighlight, TRUE, scale5Switch, scale5Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale5, SoSeparator, TRUE, scale5LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale5Active, SoSeparator, TRUE, scale5Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale6Switch, SoSwitch, TRUE, scaleSep, scale7Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale6LocateGroup, SoLocateHighlight, TRUE, scale6Switch, scale6Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale6, SoSeparator, TRUE, scale6LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale6Active, SoSeparator, TRUE, scale6Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale7Switch, SoSwitch, TRUE, scaleSep, scale8Switch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale7LocateGroup, SoLocateHighlight, TRUE, scale7Switch, scale7Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale7, SoSeparator, TRUE, scale7LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale7Active, SoSeparator, TRUE, scale7Switch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale8Switch, SoSwitch, TRUE, scaleSep, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale8LocateGroup, SoLocateHighlight, TRUE, scale8Switch, scale8Active, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scale8, SoSeparator, TRUE, scale8LocateGroup, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scale8Active, SoSeparator, TRUE, scale8Switch, "", TRUE);
}

void
SoTransformer2Dragger::build_catalog5(void)
{
  SO_KIT_ADD_CATALOG_ENTRY(axisFeedbackSep, SoSeparator, TRUE, geomSeparator, translateBoxFeedbackSep, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(axisFeedbackLocation, SoTranslation, TRUE, axisFeedbackSep, xAxisFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(xAxisFeedbackSwitch, SoSwitch, TRUE, axisFeedbackSep, yAxisFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(xAxisFeedbackActive, SoSeparator, TRUE, xAxisFeedbackSwitch, xAxisFeedbackSelect, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(xAxisFeedbackSelect, SoSeparator, TRUE, xAxisFeedbackSwitch, xCrosshairFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(xCrosshairFeedback, SoSeparator, TRUE, xAxisFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(yAxisFeedbackSwitch, SoSwitch, TRUE, axisFeedbackSep, zAxisFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(yAxisFeedbackActive, SoSeparator, TRUE, yAxisFeedbackSwitch, yAxisFeedbackSelect, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(yAxisFeedbackSelect, SoSeparator, TRUE, yAxisFeedbackSwitch, yCrosshairFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(yCrosshairFeedback, SoSeparator, TRUE, yAxisFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(zAxisFeedbackSwitch, SoSwitch, TRUE, axisFeedbackSep, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(zAxisFeedbackActive, SoSeparator, TRUE, zAxisFeedbackSwitch, zAxisFeedbackSelect, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(zAxisFeedbackSelect, SoSeparator, TRUE, zAxisFeedbackSwitch, zCrosshairFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(zCrosshairFeedback, SoSeparator, TRUE, zAxisFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(translateBoxFeedbackSep, SoSeparator, TRUE, geomSeparator, scaleBoxFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translateBoxFeedbackSwitch, SoSwitch, TRUE, translateBoxFeedbackSep, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translateBoxFeedbackRotation, SoRotation, TRUE, translateBoxFeedbackSwitch, translateBoxFeedback, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(translateBoxFeedback, SoSeparator, TRUE, translateBoxFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(scaleBoxFeedbackSwitch, SoSwitch, TRUE, geomSeparator, posXWallFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(scaleBoxFeedback, SoSeparator, TRUE, scaleBoxFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(posXWallFeedbackSwitch, SoSwitch, TRUE, geomSeparator, posYWallFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(posXWallFeedback, SoSeparator, TRUE, posXWallFeedbackSwitch, posXRoundWallFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(posXRoundWallFeedback, SoSeparator, TRUE, posXWallFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(posYWallFeedbackSwitch, SoSwitch, TRUE, geomSeparator, posZWallFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(posYWallFeedback, SoSeparator, TRUE, posYWallFeedbackSwitch, posYRoundWallFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(posYRoundWallFeedback, SoSeparator, TRUE, posYWallFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(posZWallFeedbackSwitch, SoSwitch, TRUE, geomSeparator, negXWallFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(posZWallFeedback, SoSeparator, TRUE, posZWallFeedbackSwitch, posZRoundWallFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(posZRoundWallFeedback, SoSeparator, TRUE, posZWallFeedbackSwitch, "", TRUE);
}

void
SoTransformer2Dragger::build_catalog6(void)
{
  SO_KIT_ADD_CATALOG_ENTRY(negXWallFeedbackSwitch, SoSwitch, TRUE, geomSeparator, negYWallFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(negXWallFeedback, SoSeparator, TRUE, negXWallFeedbackSwitch, negXRoundWallFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(negXRoundWallFeedback, SoSeparator, TRUE, negXWallFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(negYWallFeedbackSwitch, SoSwitch, TRUE, geomSeparator, negZWallFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(negYWallFeedback, SoSeparator, TRUE, negYWallFeedbackSwitch, negYRoundWallFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(negYRoundWallFeedback, SoSeparator, TRUE, negYWallFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(negZWallFeedbackSwitch, SoSwitch, TRUE, geomSeparator, radialFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(negZWallFeedback, SoSeparator, TRUE, negZWallFeedbackSwitch, negZRoundWallFeedback, TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(negZRoundWallFeedback, SoSeparator, TRUE, negZWallFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(radialFeedbackSwitch, SoSwitch, TRUE, geomSeparator, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(radialFeedback, SoSeparator, TRUE, radialFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(circleFeedbackSep, SoSeparator, TRUE, topSeparator, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(circleFeedbackTransformSwitch, SoSwitch, TRUE, circleFeedbackSep, xCircleFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(circleFeedbackAntiSquish, SoAntiSquish, TRUE, circleFeedbackTransformSwitch, circleFeedbackTransform, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(circleFeedbackTransform, SoTransform, TRUE, circleFeedbackTransformSwitch, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(xCircleFeedbackSwitch, SoSwitch, TRUE, circleFeedbackSep, yCircleFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(xCircleFeedback, SoSeparator, TRUE, xCircleFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(yCircleFeedbackSwitch, SoSwitch, TRUE, circleFeedbackSep, zCircleFeedbackSwitch, FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(yCircleFeedback, SoSeparator, TRUE, yCircleFeedbackSwitch, "", TRUE);
  SO_KIT_ADD_CATALOG_ENTRY(zCircleFeedbackSwitch, SoSwitch, TRUE, circleFeedbackSep, "", FALSE);
  SO_KIT_ADD_CATALOG_ENTRY(zCircleFeedback, SoSeparator, TRUE, zCircleFeedbackSwitch, "", TRUE);
}

#undef THIS
#define THIS this->pimpl
#undef THISP
#define THISP thisp->pimpl

// FIXME: document which parts need to be present in the geometry
// scenegraph, and what role they play in the dragger. 20010913 mortene.
/*!
  \DRAGGER_CONSTRUCTOR

  \NODEKIT_PRE_DIAGRAM

  \verbatim
  CLASS SoTransformer2Dragger
  -->"this"
        "callbackList"
        "topSeparator"
           "motionMatrix"
  -->      "surroundScale"
  -->      "overallStyle"
           "geomSeparator"
  -->         "axisFeedbackSep"
  -->            "axisFeedbackLocation"
  -->            "xAxisFeedbackSwitch"
  -->               "xAxisFeedbackActive"
  -->               "xAxisFeedbackSelect"
  -->               "xCrosshairFeedback"
  -->            "yAxisFeedbackSwitch"
  -->               "yAxisFeedbackActive"
  -->               "yAxisFeedbackSelect"
  -->               "yCrosshairFeedback"
  -->            "zAxisFeedbackSwitch"
  -->               "zAxisFeedbackActive"
  -->               "zAxisFeedbackSelect"
  -->               "zCrosshairFeedback"
  -->         "translateBoxFeedbackSep"
  -->            "translateBoxFeedbackSwitch"
  -->               "translateBoxFeedbackRotation"
  -->               "translateBoxFeedback"
  -->         "scaleBoxFeedbackSwitch"
  -->            "scaleBoxFeedback"
  -->         "posXWallFeedbackSwitch"
  -->            "posXWallFeedback"
  -->            "posXRoundWallFeedback"
  -->         "posYWallFeedbackSwitch"
  -->            "posYWallFeedback"
  -->            "posYRoundWallFeedback"
  -->         "posZWallFeedbackSwitch"
  -->            "posZWallFeedback"
  -->            "posZRoundWallFeedback"
  -->         "negXWallFeedbackSwitch"
  -->            "negXWallFeedback"
  -->            "negXRoundWallFeedback"
  -->         "negYWallFeedbackSwitch"
  -->            "negYWallFeedback"
  -->            "negYRoundWallFeedback"
  -->         "negZWallFeedbackSwitch"
  -->            "negZWallFeedback"
  -->            "negZRoundWallFeedback"
  -->         "radialFeedbackSwitch"
  -->            "radialFeedback"
  -->      "translatorSep"
  -->         "translator1Switch"
  -->            "translator1LocateGroup"
  -->               "translator1"
  -->            "translator1Active"
  -->         "translator2Switch"
  -->            "translator2LocateGroup"
  -->               "translator2"
  -->            "translator2Active"
  -->         "translator3Switch"
  -->            "translator3LocateGroup"
  -->               "translator3"
  -->            "translator3Active"
  -->         "translator4Switch"
  -->            "translator4LocateGroup"
  -->               "translator4"
  -->            "translator4Active"
  -->         "translator5Switch"
  -->            "translator5LocateGroup"
  -->               "translator5"
  -->            "translator5Active"
  -->         "translator6Switch"
  -->            "translator6LocateGroup"
  -->               "translator6"
  -->            "translator6Active"
  -->      "translateToCenter"
  -->      "rotatorSep"
  -->         "rotator1Switch"
  -->            "rotator1LocateGroup"
  -->               "rotator1"
  -->            "rotator1Active"
  -->         "rotator2Switch"
  -->            "rotator2LocateGroup"
  -->               "rotator2"
  -->            "rotator2Active"
  -->         "rotator3Switch"
  -->            "rotator3LocateGroup"
  -->               "rotator3"
  -->            "rotator3Active"
  -->         "rotator4Switch"
  -->            "rotator4LocateGroup"
  -->               "rotator4"
  -->            "rotator4Active"
  -->         "rotator5Switch"
  -->            "rotator5LocateGroup"
  -->               "rotator5"
  -->            "rotator5Active"
  -->         "rotator6Switch"
  -->            "rotator6LocateGroup"
  -->               "rotator6"
  -->            "rotator6Active"
  -->      "scaleSep"
  -->         "scale1Switch"
  -->            "scale1LocateGroup"
  -->               "scale1"
  -->            "scale1Active"
  -->         "scale2Switch"
  -->            "scale2LocateGroup"
  -->               "scale2"
  -->            "scale2Active"
  -->         "scale3Switch"
  -->            "scale3LocateGroup"
  -->               "scale3"
  -->            "scale3Active"
  -->         "scale4Switch"
  -->            "scale4LocateGroup"
  -->               "scale4"
  -->            "scale4Active"
  -->         "scale5Switch"
  -->            "scale5LocateGroup"
  -->               "scale5"
  -->            "scale5Active"
  -->         "scale6Switch"
  -->            "scale6LocateGroup"
  -->               "scale6"
  -->            "scale6Active"
  -->         "scale7Switch"
  -->            "scale7LocateGroup"
  -->               "scale7"
  -->            "scale7Active"
  -->         "scale8Switch"
  -->            "scale8LocateGroup"
  -->               "scale8"
  -->            "scale8Active"
  -->      "circleFeedbackSep"
  -->         "circleFeedbackTransformSwitch"
  -->            "circleFeedbackAntiSquish"
  -->            "circleFeedbackTransform"
  -->         "xCircleFeedbackSwitch"
  -->            "xCircleFeedback"
  -->         "yCircleFeedbackSwitch"
  -->            "yCircleFeedback"
  -->         "zCircleFeedbackSwitch"
  -->            "zCircleFeedback"
  \endverbatim

  \NODEKIT_POST_DIAGRAM


  \NODEKIT_PRE_TABLE

  \verbatim
  CLASS SoTransformer2Dragger
  PVT   "this",  SoTransformer2Dragger  --- 
        "callbackList",  SoNodeKitListPart [ SoCallback, SoEventCallback ] 
  PVT   "topSeparator",  SoSeparator  --- 
  PVT   "motionMatrix",  SoMatrixTransform  --- 
        "surroundScale",  SoSurroundScale  --- 
  PVT   "overallStyle",  SoGroup  --- 
  PVT   "geomSeparator",  SoSeparator  --- 
  PVT   "translatorSep",  SoSeparator  --- 
  PVT   "translator1Switch",  SoSwitch  --- 
  PVT   "translator1LocateGroup",  SoLocateHighlight  --- 
        "translator1",  SoSeparator  --- 
        "translator1Active",  SoSeparator  --- 
  PVT   "translator2Switch",  SoSwitch  --- 
  PVT   "translator2LocateGroup",  SoLocateHighlight  --- 
        "translator2",  SoSeparator  --- 
        "translator2Active",  SoSeparator  --- 
  PVT   "translator3Switch",  SoSwitch  --- 
  PVT   "translator3LocateGroup",  SoLocateHighlight  --- 
        "translator3",  SoSeparator  --- 
        "translator3Active",  SoSeparator  --- 
  PVT   "translator4Switch",  SoSwitch  --- 
  PVT   "translator4LocateGroup",  SoLocateHighlight  --- 
        "translator4",  SoSeparator  --- 
        "translator4Active",  SoSeparator  --- 
  PVT   "translator5Switch",  SoSwitch  --- 
  PVT   "translator5LocateGroup",  SoLocateHighlight  --- 
        "translator5",  SoSeparator  --- 
        "translator5Active",  SoSeparator  --- 
  PVT   "translator6Switch",  SoSwitch  --- 
  PVT   "translator6LocateGroup",  SoLocateHighlight  --- 
        "translator6",  SoSeparator  --- 
        "translator6Active",  SoSeparator  --- 
  PVT   "translateToCenter", SoMatrixTransform  ---
  PVT   "rotatorSep",  SoSeparator  --- 
  PVT   "rotator1Switch",  SoSwitch  --- 
  PVT   "rotator1LocateGroup",  SoLocateHighlight  --- 
        "rotator1",  SoSeparator  --- 
        "rotator1Active",  SoSeparator  --- 
  PVT   "rotator2Switch",  SoSwitch  --- 
  PVT   "rotator2LocateGroup",  SoLocateHighlight  --- 
        "rotator2",  SoSeparator  --- 
        "rotator2Active",  SoSeparator  --- 
  PVT   "rotator3Switch",  SoSwitch  --- 
  PVT   "rotator3LocateGroup",  SoLocateHighlight  --- 
        "rotator3",  SoSeparator  --- 
        "rotator3Active",  SoSeparator  --- 
  PVT   "rotator4Switch",  SoSwitch  --- 
  PVT   "rotator4LocateGroup",  SoLocateHighlight  --- 
        "rotator4",  SoSeparator  --- 
        "rotator4Active",  SoSeparator  --- 
  PVT   "rotator5Switch",  SoSwitch  --- 
  PVT   "rotator5LocateGroup",  SoLocateHighlight  --- 
        "rotator5",  SoSeparator  --- 
        "rotator5Active",  SoSeparator  --- 
  PVT   "rotator6Switch",  SoSwitch  --- 
  PVT   "rotator6LocateGroup",  SoLocateHighlight  --- 
        "rotator6",  SoSeparator  --- 
        "rotator6Active",  SoSeparator  --- 
  PVT   "scaleSep",  SoSeparator  --- 
  PVT   "scale1Switch",  SoSwitch  --- 
  PVT   "scale1LocateGroup",  SoLocateHighlight  --- 
        "scale1",  SoSeparator  --- 
        "scale1Active",  SoSeparator  --- 
  PVT   "scale2Switch",  SoSwitch  --- 
  PVT   "scale2LocateGroup",  SoLocateHighlight  --- 
        "scale2",  SoSeparator  --- 
        "scale2Active",  SoSeparator  --- 
  PVT   "scale3Switch",  SoSwitch  --- 
  PVT   "scale3LocateGroup",  SoLocateHighlight  --- 
        "scale3",  SoSeparator  --- 
        "scale3Active",  SoSeparator  --- 
  PVT   "scale4Switch",  SoSwitch  --- 
  PVT   "scale4LocateGroup",  SoLocateHighlight  --- 
        "scale4",  SoSeparator  --- 
        "scale4Active",  SoSeparator  --- 
  PVT   "scale5Switch",  SoSwitch  --- 
  PVT   "scale5LocateGroup",  SoLocateHighlight  --- 
        "scale5",  SoSeparator  --- 
        "scale5Active",  SoSeparator  --- 
  PVT   "scale6Switch",  SoSwitch  --- 
  PVT   "scale6LocateGroup",  SoLocateHighlight  --- 
        "scale6",  SoSeparator  --- 
        "scale6Active",  SoSeparator  --- 
  PVT   "scale7Switch",  SoSwitch  --- 
  PVT   "scale7LocateGroup",  SoLocateHighlight  --- 
        "scale7",  SoSeparator  --- 
        "scale7Active",  SoSeparator  --- 
  PVT   "scale8Switch",  SoSwitch  --- 
  PVT   "scale8LocateGroup",  SoLocateHighlight  --- 
        "scale8",  SoSeparator  --- 
        "scale8Active",  SoSeparator  --- 
  PVT   "circleFeedbackSep",  SoSeparator  --- 
  PVT   "circleFeedbackTransformSwitch",  SoSwitch  --- 
  PVT   "circleFeedbackAntiSquish",  SoAntiSquish  --- 
  PVT   "circleFeedbackTransform",  SoTransform  --- 
  PVT   "xCircleFeedbackSwitch",  SoSwitch  --- 
        "xCircleFeedback",  SoSeparator  --- 
  PVT   "yCircleFeedbackSwitch",  SoSwitch  --- 
        "yCircleFeedback",  SoSeparator  --- 
  PVT   "zCircleFeedbackSwitch",  SoSwitch  --- 
        "zCircleFeedback",  SoSeparator  --- 
  PVT   "axisFeedbackSep",  SoSeparator  --- 
  PVT   "axisFeedbackLocation",  SoTranslation  --- 
  PVT   "xAxisFeedbackSwitch",  SoSwitch  --- 
        "xAxisFeedbackActive",  SoSeparator  --- 
        "xAxisFeedbackSelect",  SoSeparator  --- 
        "xCrosshairFeedback",  SoSeparator  --- 
  PVT   "yAxisFeedbackSwitch",  SoSwitch  --- 
        "yAxisFeedbackActive",  SoSeparator  --- 
        "yAxisFeedbackSelect",  SoSeparator  --- 
        "yCrosshairFeedback",  SoSeparator  --- 
  PVT   "zAxisFeedbackSwitch",  SoSwitch  --- 
        "zAxisFeedbackActive",  SoSeparator  --- 
        "zAxisFeedbackSelect",  SoSeparator  --- 
        "zCrosshairFeedback",  SoSeparator  --- 
  PVT   "translateBoxFeedbackSep",  SoSeparator  --- 
  PVT   "translateBoxFeedbackSwitch",  SoSwitch  --- 
  PVT   "translateBoxFeedbackRotation",  SoRotation  --- 
        "translateBoxFeedback",  SoSeparator  --- 
  PVT   "scaleBoxFeedbackSwitch",  SoSwitch  --- 
        "scaleBoxFeedback",  SoSeparator  --- 
  PVT   "posXWallFeedbackSwitch",  SoSwitch  --- 
        "posXWallFeedback",  SoSeparator  --- 
        "posXRoundWallFeedback",  SoSeparator  --- 
  PVT   "posYWallFeedbackSwitch",  SoSwitch  --- 
        "posYWallFeedback",  SoSeparator  --- 
        "posYRoundWallFeedback",  SoSeparator  --- 
  PVT   "posZWallFeedbackSwitch",  SoSwitch  --- 
        "posZWallFeedback",  SoSeparator  --- 
        "posZRoundWallFeedback",  SoSeparator  --- 
  PVT   "negXWallFeedbackSwitch",  SoSwitch  --- 
        "negXWallFeedback",  SoSeparator  --- 
        "negXRoundWallFeedback",  SoSeparator  --- 
  PVT   "negYWallFeedbackSwitch",  SoSwitch  --- 
        "negYWallFeedback",  SoSeparator  --- 
        "negYRoundWallFeedback",  SoSeparator  --- 
  PVT   "negZWallFeedbackSwitch",  SoSwitch  --- 
        "negZWallFeedback",  SoSeparator  --- 
        "negZRoundWallFeedback",  SoSeparator  --- 
  PVT   "radialFeedbackSwitch",  SoSwitch  --- 
        "radialFeedback",  SoSeparator  --- 
  \endverbatim

  \NODEKIT_POST_TABLE
*/
SoTransformer2Dragger::SoTransformer2Dragger(void)
{
  THIS = new SoTransformer2DraggerP;

  SO_KIT_INTERNAL_CONSTRUCTOR(SoTransformer2Dragger);

  // split-up to avoid one huge method
  this->build_catalog1();
  this->build_catalog2();
  this->build_catalog3();
  this->build_catalog4();
  this->build_catalog5();
  this->build_catalog6();

  if (SO_KIT_IS_FIRST_INSTANCE()) {
    SoInteractionKit::readDefaultParts("transformerDragger.iv",
                                       TRANSFORMER2DRAGGER_draggergeometry,
                                       (int)strlen(TRANSFORMER2DRAGGER_draggergeometry));
  }

  SO_KIT_ADD_FIELD(rotation, (SbRotation(SbVec3f(0.0f, 0.0f, 1.0f), 0.0f)));
  SO_KIT_ADD_FIELD(translation, (0.0f, 0.0f, 0.0f));
  SO_KIT_ADD_FIELD(scaleFactor, (1.0f, 1.0f, 1.0f));
  SO_KIT_ADD_FIELD(center, (0.0f, 0.0f, 0.0f));
  // FIXME: it doesn't look like this field is actually used or set
  // anywhere else but here. Investigate. 20011208 mortene.
  SO_KIT_ADD_FIELD(minDiscRotDot, (0.025f));

  SO_KIT_INIT_INSTANCE();

  this->setPartAsDefault("overallStyle", "transformerOverallStyle");
  this->setPartAsDefault("translator1", "transformerTranslator1");
  this->setPartAsDefault("translator2", "transformerTranslator2");
  this->setPartAsDefault("translator3", "transformerTranslator3");
  this->setPartAsDefault("translator4", "transformerTranslator4");
  this->setPartAsDefault("translator5", "transformerTranslator5");
  this->setPartAsDefault("translator6", "transformerTranslator6");
  this->setPartAsDefault("translator1Active", "transformerTranslator1Active");
  this->setPartAsDefault("translator2Active", "transformerTranslator2Active");
  this->setPartAsDefault("translator3Active", "transformerTranslator3Active");
  this->setPartAsDefault("translator4Active", "transformerTranslator4Active");
  this->setPartAsDefault("translator5Active", "transformerTranslator5Active");
  this->setPartAsDefault("translator6Active", "transformerTranslator6Active");
  this->setPartAsDefault("rotator1", "transformerRotator1");
  this->setPartAsDefault("rotator2", "transformerRotator2");
  this->setPartAsDefault("rotator3", "transformerRotator3");
  this->setPartAsDefault("rotator4", "transformerRotator4");
  this->setPartAsDefault("rotator5", "transformerRotator5");
  this->setPartAsDefault("rotator6", "transformerRotator6");
  this->setPartAsDefault("rotator1Active", "transformerRotator1Active");
  this->setPartAsDefault("rotator2Active", "transformerRotator2Active");
  this->setPartAsDefault("rotator3Active", "transformerRotator3Active");
  this->setPartAsDefault("rotator4Active", "transformerRotator4Active");
  this->setPartAsDefault("rotator5Active", "transformerRotator5Active");
  this->setPartAsDefault("rotator6Active", "transformerRotator6Active");
  this->setPartAsDefault("scale1", "transformerScale1");
  this->setPartAsDefault("scale2", "transformerScale2");
  this->setPartAsDefault("scale3", "transformerScale3");
  this->setPartAsDefault("scale4", "transformerScale4");
  this->setPartAsDefault("scale5", "transformerScale5");
  this->setPartAsDefault("scale6", "transformerScale6");
  this->setPartAsDefault("scale7", "transformerScale7");
  this->setPartAsDefault("scale8", "transformerScale8");
  this->setPartAsDefault("scale1Active", "transformerScale1Active");
  this->setPartAsDefault("scale2Active", "transformerScale2Active");
  this->setPartAsDefault("scale3Active", "transformerScale3Active");
  this->setPartAsDefault("scale4Active", "transformerScale4Active");
  this->setPartAsDefault("scale5Active", "transformerScale5Active");
  this->setPartAsDefault("scale6Active", "transformerScale6Active");
  this->setPartAsDefault("scale7Active", "transformerScale7Active");
  this->setPartAsDefault("scale8Active", "transformerScale8Active");
  this->setPartAsDefault("xAxisFeedbackActive", "transformerXAxisFeedbackActive");
  this->setPartAsDefault("xAxisFeedbackSelect", "transformerXAxisFeedbackSelect");
  this->setPartAsDefault("yAxisFeedbackActive", "transformerYAxisFeedbackActive");
  this->setPartAsDefault("yAxisFeedbackSelect", "transformerYAxisFeedbackSelect");
  this->setPartAsDefault("zAxisFeedbackActive", "transformerZAxisFeedbackActive");
  this->setPartAsDefault("zAxisFeedbackSelect", "transformerZAxisFeedbackSelect");
  this->setPartAsDefault("xCrosshairFeedback", "transformerXCrosshairFeedback");
  this->setPartAsDefault("yCrosshairFeedback", "transformerYCrosshairFeedback");
  this->setPartAsDefault("zCrosshairFeedback", "transformerZCrosshairFeedback");
  this->setPartAsDefault("xCircleFeedback", "transformerXCircleFeedback");
  this->setPartAsDefault("yCircleFeedback", "transformerYCircleFeedback");
  this->setPartAsDefault("zCircleFeedback", "transformerZCircleFeedback");
  this->setPartAsDefault("radialFeedback", "transformerRadialFeedback");
  this->setPartAsDefault("translateBoxFeedback", "transformerTranslateBoxFeedback");

  this->setPartAsDefault("scaleBoxFeedback", "transformerScaleBoxFeedback");
  this->setPartAsDefault("posXWallFeedback", "transformerPosXWallFeedback");
  this->setPartAsDefault("posYWallFeedback", "transformerPosYWallFeedback");
  this->setPartAsDefault("posZWallFeedback", "transformerPosZWallFeedback");
  this->setPartAsDefault("negXWallFeedback", "transformerNegXWallFeedback");
  this->setPartAsDefault("negYWallFeedback", "transformerNegYWallFeedback");
  this->setPartAsDefault("negZWallFeedback", "transformerNegZWallFeedback");
  this->setPartAsDefault("posXRoundWallFeedback", "transformerPosXRoundWallFeedback");
  this->setPartAsDefault("posYRoundWallFeedback", "transformerPosYRoundWallFeedback");
  this->setPartAsDefault("posZRoundWallFeedback", "transformerPosZRoundWallFeedback");
  this->setPartAsDefault("negXRoundWallFeedback", "transformerNegXRoundWallFeedback");
  this->setPartAsDefault("negYRoundWallFeedback", "transformerNegYRoundWallFeedback");
  this->setPartAsDefault("negZRoundWallFeedback", "transformerNegZRoundWallFeedback");

  this->state = INACTIVE;
  THIS->constraintState = CONSTRAINT_OFF;
  // FIXME: according to SGI classdoc, this flag is supposed to be
  // default TRUE?  Investigate. 20011208 mortene.
  THIS->locateHighlighting = FALSE;
  THIS->whatkind = WHATKIND_NONE;
  THIS->whatnum = -1;

  this->setAllPartSwitches(0, 0, 0);

  this->addStartCallback(SoTransformer2Dragger::startCB);
  this->addMotionCallback(SoTransformer2Dragger::motionCB);
  this->addFinishCallback(SoTransformer2Dragger::finishCB);
  this->addValueChangedCallback(SoTransformer2Dragger::valueChangedCB);
  this->addOtherEventCallback(SoTransformer2Dragger::metaKeyChangeCB);

  this->planeProj = new SbPlaneProjector;
  this->lineProj = new SbLineProjector;
  this->sphereProj = new SbSphereSectionProjector;
  this->cylProj = new SbCylinderPlaneProjector;

  this->translFieldSensor = new SoFieldSensor(SoTransformer2Dragger::fieldSensorCB, this);
  this->translFieldSensor->setPriority(0);
  this->scaleFieldSensor = new SoFieldSensor(SoTransformer2Dragger::fieldSensorCB, this);
  this->scaleFieldSensor->setPriority(0);
  this->rotateFieldSensor = new SoFieldSensor(SoTransformer2Dragger::fieldSensorCB, this);
  this->rotateFieldSensor->setPriority(0);
  this->centerFieldSensor = new SoFieldSensor(SoTransformer2Dragger::fieldSensorCB, this);
  this->centerFieldSensor->setPriority(0);

  this->setUpConnections(TRUE, TRUE);

  // make sure these are not written if they have the default value.
  // FIXME: investigate why this is needed. There must be a
  // notification that is sent somewhere that causes the fields to
  // become non-default. pederb, 2003-04-01
  this->translatorSep.setDefault(TRUE);
  this->rotatorSep.setDefault(TRUE);
  this->scaleSep.setDefault(TRUE);
  this->translateBoxFeedbackSep.setDefault(TRUE);
  this->axisFeedbackSep.setDefault(TRUE);
  this->scale8LocateGroup.setDefault(TRUE);
  this->scale7LocateGroup.setDefault(TRUE);
  this->circleFeedbackSep.setDefault(TRUE);
}

/*!
  Protected destructor.

  (Dragger classes are derived from SoBase, so they are reference
  counted and automatically destroyed when their reference count goes
  to 0.)
 */
SoTransformer2Dragger::~SoTransformer2Dragger()
{
  delete this->translFieldSensor;
  delete this->scaleFieldSensor;
  delete this->rotateFieldSensor;
  delete this->centerFieldSensor;

  delete THIS;
}

// Doc in super.
SbBool
SoTransformer2Dragger::setUpConnections(SbBool onoff, SbBool doitalways)
{
  if (!doitalways && this->connectionsSetUp == onoff) return onoff;

  if (onoff) {
    inherited::setUpConnections(onoff, doitalways);

    SoTransformer2Dragger::fieldSensorCB(this, NULL);

    if (this->translFieldSensor->getAttachedField() != &this->translation) {
      this->translFieldSensor->attach(&this->translation);
    }
    if (this->scaleFieldSensor->getAttachedField() != &this->scaleFactor) {
      this->scaleFieldSensor->attach(&this->scaleFactor);
    }
    if (this->rotateFieldSensor->getAttachedField() != &this->rotation) {
      this->rotateFieldSensor->attach(&this->rotation);
    }
	if (this->centerFieldSensor->getAttachedField() != &this->center) {
      this->centerFieldSensor->attach(&this->center);
    }
  }
  else {
    if (this->translFieldSensor->getAttachedField() != NULL) {
      this->translFieldSensor->detach();
    }
    if (this->scaleFieldSensor->getAttachedField() != NULL) {
      this->scaleFieldSensor->detach();
    }
    if (this->rotateFieldSensor->getAttachedField() != NULL) {
      this->rotateFieldSensor->detach();
    }
	if (this->centerFieldSensor->getAttachedField() != NULL) {
      this->centerFieldSensor->detach();
    }
    inherited::setUpConnections(onoff, doitalways);
  }
  return !(this->connectionsSetUp = onoff);
}

// Convenience method used to call setDefault on similar fields.
//
// Note: keep the function name prefix to avoid name clashes with
// other dragger .cpp files for "--enable-compact" builds.
//
// FIXME: should collect these methods in a common method visible to
// all draggers implementing the exact same functionality. 20010826 mortene.
static void
SoTransformer2Dragger_set_default(SoDragger * dragger, const char * fmt,
                                 int minval, int maxval)
{
  SbString str;
  for (int i = minval; i <= maxval; i++) {
    str.sprintf(fmt, i);
    SoField * f = dragger->getField(str.getString());
    assert(f);
    f->setDefault(TRUE);
  }
}

// Doc in superclass.
void
SoTransformer2Dragger::setDefaultOnNonWritingFields(void)
{
  this->surroundScale.setDefault(TRUE);
  this->circleFeedbackAntiSquish.setDefault(TRUE);
  this->circleFeedbackTransform.setDefault(TRUE);
  this->axisFeedbackLocation.setDefault(TRUE);
  this->translateBoxFeedbackRotation.setDefault(TRUE);

  SoTransformer2Dragger_set_default(this, "translator%dLocateGroup", 1, 6);
  SoTransformer2Dragger_set_default(this, "rotator%dLocateGroup", 1, 6);
  SoTransformer2Dragger_set_default(this, "scale%dLocateGroup", 1, 6);

  inherited::setDefaultOnNonWritingFields();
}

/*! \COININTERNAL */
void
SoTransformer2Dragger::fieldSensorCB(void *d, SoSensor *inSensor)
{
  SoTransformer2Dragger *thisp = (SoTransformer2Dragger*)d;

  SoField *trigF = NULL;
  if ( inSensor )
      trigF = ((SoDataSensor *)inSensor)->getTriggerField();

  // If inSensor is NULL or the trigger field is NULL, we will
  // do both the matrix and center stuff, since we don't know what changed.
  SbBool doMatrix = TRUE;
  SbBool doCenter = TRUE;

  // But if this is invoked by a sensor with a trigger field, 
  // we can do different things if it was the center 
  // or the rotation fields that triggered.
  if ( trigF )
  {
	if ( trigF == &thisp->rotation    ||
	     trigF == &thisp->translation ||
	     trigF == &thisp->scaleFactor )
	  doCenter = FALSE;

	if ( trigF == &thisp->center )
	  doMatrix = FALSE;
  }

  if ( doCenter )
  {
	// Build a matrix to translate the origin of the ball
	// to the transformed center...
	thisp->recalcTranslateToCenter();

	// We need to specifically tell the dragger to invoke the
	// value changed callbacks, since only calling setMotionMatrix
	// automatically does this.
	if ( doMatrix == FALSE )
	  thisp->valueChanged();
  }

  if ( doMatrix )
  {
    SbMatrix matrix = thisp->getMotionMatrix();
    thisp->workFieldsIntoTransform(matrix);
    thisp->setMotionMatrix(matrix);
  }
}

/*! \COININTERNAL */
void
SoTransformer2Dragger::valueChangedCB(void *, SoDragger * d)
{
  SoTransformer2Dragger *thisp = (SoTransformer2Dragger*)d;
  SbMatrix matrix = thisp->getMotionMatrix();
  SbVec3f trans, scale;
  SbRotation rot, scaleOrient;
  const SbVec3f &center = thisp->center.getValue();
  matrix.getTransform(trans, rot, scale, scaleOrient, center);

  thisp->translFieldSensor->detach();
  if (thisp->translation.getValue() != trans)
    thisp->translation = trans;
  thisp->translFieldSensor->attach(&thisp->translation);

  thisp->scaleFieldSensor->detach();
  if (thisp->scaleFactor.getValue() != scale)
    thisp->scaleFactor = scale;
  thisp->scaleFieldSensor->attach(&thisp->scaleFactor);

  thisp->rotateFieldSensor->detach();
  if (thisp->rotation.getValue() != rot) {
    thisp->rotation = rot;
  }
  thisp->rotateFieldSensor->attach(&thisp->rotation);

  // Do nothing with the centerFieldSensor.
}

/*!
  Returns an indicator for the current operation executed on the
  dragger by the end-user -- or SoTransformer2Dragger::INACTIVE if
  none.
*/
SoTransformer2Dragger::State
SoTransformer2Dragger::getCurrentState(void)
{
  return this->state;
}

void
SoTransformer2Dragger::unsquishKnobs(void)
{
  this->updateAntiSquishList();
}

SbBool
SoTransformer2Dragger::isLocateHighlighting(void)
{
  return THIS->locateHighlighting;
}

void
SoTransformer2Dragger::setLocateHighlighting(SbBool onoff)
{
  // FIXME: I can't see that this flag is actually used anywhere..?
  // 20011208 mortene.
  THIS->locateHighlighting = onoff;
}

void
SoTransformer2Dragger::setColinearThreshold(int newval)
{
  SoTransformer2DraggerP::colinearThreshold = newval;
}

int
SoTransformer2Dragger::getColinearThreshold(void)
{
  return SoTransformer2DraggerP::colinearThreshold;
}

SbVec3f
SoTransformer2Dragger::getBoxPointInWorldSpace(const SbVec3f & pointonunitbox)
{
  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  mat.multRight(this->getLocalToWorldMatrix());
  SbVec3f ret;
  mat.multVecMatrix(pointonunitbox, ret);
  return ret;
}

SbVec3f
SoTransformer2Dragger::getBoxDirInWorldSpace(const SbVec3f & dironunitbox)
{
  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  mat.multRight(this->getLocalToWorldMatrix());
  SbVec3f ret;
  mat.multDirMatrix(dironunitbox, ret);
  return ret;
}

SbVec3f
SoTransformer2Dragger::getWorldPointInBoxSpace(const SbVec3f & pointinworldspace)
{
  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  mat.multLeft(this->getWorldToLocalMatrix());
  SbVec3f ret;
  mat.multVecMatrix(pointinworldspace, ret);
  return ret;
}

SbVec2f
SoTransformer2Dragger::getWorldPointInPixelSpace(const SbVec3f & thepoint)
{
  SbVec3f screenpt;
  this->getViewVolume().projectToScreen(thepoint, screenpt);
  return SbVec2f(screenpt[0], screenpt[1]);
}

SbVec3f
SoTransformer2Dragger::getInteractiveCenterInBoxSpace(void)
{
  if (THIS->ctrlDown) return THIS->ctrlOffset;
  else return SbVec3f(0.0f, 0.0f, 0.0f);
}

/*! \COININTERNAL */
void
SoTransformer2Dragger::startCB(void *, SoDragger * d)
{
  SoTransformer2Dragger *thisp = (SoTransformer2Dragger*)d;
  thisp->dragStart();
}

/*! \COININTERNAL */
void
SoTransformer2Dragger::motionCB(void *, SoDragger * d)
{
  SoTransformer2Dragger *thisp = (SoTransformer2Dragger*)d;
  thisp->drag();
}

/*! \COININTERNAL */
void
SoTransformer2Dragger::finishCB(void *, SoDragger * d)
{
  SoTransformer2Dragger *thisp = (SoTransformer2Dragger*)d;
  thisp->dragFinish();
}

/*! \COININTERNAL */
void
SoTransformer2Dragger::metaKeyChangeCB(void *, SoDragger *d)
{
  SoTransformer2Dragger *thisp = (SoTransformer2Dragger*)d;
  if (!thisp->isActive.getValue()) return;

  const SoEvent *event = thisp->getEvent();
  if (THISP->shiftDown != event->wasShiftDown()) {
    thisp->drag();
  }
  if (THISP->ctrlDown != event->wasCtrlDown()) {
    thisp->drag();
  }
}

// invalidate surround scale node, if it exists
static void
invalidate_surroundscale(SoBaseKit * kit)
{
  SoSurroundScale * ss = (SoSurroundScale*)
    kit->getPart("surroundScale", FALSE);
  if (ss) ss->invalidate();
}

/*! \COININTERNAL
  Called when dragger is selected (picked) by the user.
*/
void
SoTransformer2Dragger::dragStart(void)
{
  invalidate_surroundscale(this);

  int i;
  const SoPath *pickpath = this->getPickPath();
  const SoEvent *event = this->getEvent();

  SbBool found = FALSE;
  this->state = INACTIVE;

  SbVec3f startpt = this->getLocalStartingPoint();
  startpt = this->localToWorking(startpt);

  SbString str;
  if (!found) {
    for (i = 1; i <= 6; i++) {
      str.sprintf("translator%d", i);
      if (pickpath->findNode(this->getNodeFieldNode(str.getString())) >= 0 ||
          this->getSurrogatePartPickedName() == str.getString()) break;
    }
    if (i <= 6) {
      found = TRUE;
      this->state = (State)(int(RIT_TRANSLATE) + (i-1));
      THIS->whatkind = WHATKIND_TRANSLATE;
      THIS->whatnum = i;
      if (i <= 2) THIS->dimension = 1;
      else if (i <= 4) THIS->dimension = 0;
      else THIS->dimension = 2;
    }
  }

  if (!found) {
    for (i = 1; i <= 6; i++) {
      str.sprintf("rotator%d", i);
      if (pickpath->findNode(this->getNodeFieldNode(str.getString()))>= 0 ||
          this->getSurrogatePartPickedName() == str.getString()) break;
    }
    if (i <= 6) {
      found = TRUE;
      this->state = (State)(int(RIT_X_ROTATE) + (i-1));
      THIS->whatkind = WHATKIND_ROTATE;
      THIS->whatnum = i;
      if (i <= 2) THIS->dimension = 1;
      else if (i <= 4) THIS->dimension = 0;
      else THIS->dimension = 2;
    }
  }
  if (!found) {
    for (i = 1; i <= 8; i++) {
      str.sprintf("scale%d", i);
      if (pickpath->findNode(this->getNodeFieldNode(str.getString()))>= 0 ||
          this->getSurrogatePartPickedName() == str.getString()) break;
    }
    if (i <= 8) {
      found = TRUE;
      this->state = (State) (int(PX_PY_PZ_3D_SCALE) + (i-1));
      THIS->whatkind = WHATKIND_SCALE;
      THIS->whatnum = i;
    }
  }
  assert(found);

  THIS->ctrlDown = event->wasCtrlDown();
  THIS->shiftDown = event->wasShiftDown();
  THIS->ctrlOffset = this->calcCtrlOffset(startpt);

  switch(THIS->whatkind) {
  case WHATKIND_TRANSLATE:
    {
      SbVec3f n(0.0f, 0.0f, 0.0f);
      n[THIS->dimension] = 1.0f;
      this->planeProj->setPlane(SbPlane(n, startpt));
      this->lineProj->setLine(SbLine(startpt, startpt + n));
      THIS->constraintState = CONSTRAINT_OFF;
      if (THIS->shiftDown) {
        THIS->constraintState = CONSTRAINT_WAIT;
      }
      SbLine myline(SbVec3f(0.0f, 0.0f, 0.0f), n);
      SoTranslation *t = SO_GET_ANY_PART(this, "axisFeedbackLocation", SoTranslation);
      t->translation = myline.getClosestPoint(startpt);

      this->setAllPartSwitches(SO_SWITCH_NONE, SO_SWITCH_NONE, SO_SWITCH_NONE);
      SbString str;
      str.sprintf("translator%dSwitch", THIS->whatnum);
      this->setSwitchValue(str.getString(), 1);
      this->setSwitchValue("translateBoxFeedbackSwitch", SO_SWITCH_ALL);
      SoRotation * feedbackrot = (SoRotation*) this->getAnyPart("translateBoxFeedbackRotation", TRUE);
      assert(feedbackrot);
      switch (THIS->whatnum) {
      default:
      case 1:
        feedbackrot->rotation = SbRotation::identity();
        break;
      case 2:
        feedbackrot->rotation = SbRotation(SbVec3f(1.0f, 0.0f, 0.0f), float(M_PI));
        break;
      case 3:
        feedbackrot->rotation = SbRotation(SbVec3f(0.0f, 0.0f, 1.0f), float(M_PI)*0.5f);
        break;
      case 4:
        feedbackrot->rotation = SbRotation(SbVec3f(0.0f, 0.0f, 1.0f), -float(M_PI)*0.5f);
        break;
      case 5:
        feedbackrot->rotation = SbRotation(SbVec3f(1.0f, 0.0f, 0.0f), float(M_PI)*0.5f);
        break;
      case 6:
        feedbackrot->rotation = SbRotation(SbVec3f(1.0f, 0.0f, 0.0f), -float(M_PI)*0.5f);
        break;
      }
      (void) this->setDynamicTranslatorSwitches(event);
    }
    break;
  case WHATKIND_SCALE:
    {
      SoTranslation *t = SO_GET_ANY_PART(this, "axisFeedbackLocation", SoTranslation);
      t->translation = startpt;
      this->lineProj->setLine(SbLine(SbVec3f(0.0f, 0.0f, 0.0f), startpt));
      THIS->constraintState = CONSTRAINT_OFF;
      if (THIS->shiftDown) {
        THIS->constraintState = CONSTRAINT_WAIT;
      }

      SbString str;
      str.sprintf("scale%dSwitch", THIS->whatnum);
      this->setAllPartSwitches(0, SO_SWITCH_NONE, SO_SWITCH_NONE);
      this->setSwitchValue(str.getString(), 1);
      (void) this->setDynamicScaleSwitches(event);
    }
    break;
  case WHATKIND_ROTATE:
    {
      SoTranslation *t = SO_GET_ANY_PART(this, "axisFeedbackLocation", SoTranslation);
      t->translation = startpt;
      this->sphereProj->setSphere(SbSphere(SbVec3f(0.0f, 0.0f, 0.0f), startpt.length()));
      this->sphereProj->setViewVolume(this->getViewVolume());
      this->sphereProj->setWorkingSpace(this->getWorkingToWorldMatrix());

      switch (this->getFrontOnProjector()) {
      case FRONT:
        this->sphereProj->setFront(TRUE);
        break;
      case BACK:
        this->sphereProj->setFront(TRUE);
        break;
      default: // avoid warnings
      case USE_PICK:
        this->sphereProj->setFront(this->sphereProj->isPointInFront(startpt));
        break;
      }

      SbVec3f projpt = this->sphereProj->project(this->getNormalizedLocaterPosition());
      this->getWorkingToWorldMatrix().multVecMatrix(projpt, THIS->prevWorldHitPt);
      THIS->prevMotionMatrix = this->getMotionMatrix();

      THIS->constraintState = CONSTRAINT_OFF;
      if (!THIS->shiftDown) {
        THIS->constraintState = CONSTRAINT_WAIT;
        // this plane is only used to find constraint direction
        this->planeProj->setPlane(SbPlane(startpt, startpt));
        THIS->normalizedStartLocaterPosition = this->getNormalizedLocaterPosition();
      }
      SoAntiSquish *squish = SO_GET_ANY_PART(this, "circleFeedbackAntiSquish", SoAntiSquish);
      SoAntiSquish::Sizing sizing;
      switch (THIS->dimension) {
      case 0: sizing = SoAntiSquish::X; break;
      case 1: sizing = SoAntiSquish::Y; break;
      case 2: sizing = SoAntiSquish::Z; break;
      default: assert(FALSE); sizing = SoAntiSquish::Z; // Dummy assignment to avoid compiler warning.
      }
      squish->sizing = sizing;
      squish->recalc();
      this->setAllPartSwitches(SO_SWITCH_NONE, 0, SO_SWITCH_NONE);
      (void) this->setDynamicRotatorSwitches(event);
    }
    break;
  default:
    assert(0 && "unknown whatkind");
    break;
  }
}

/*! \COININTERNAL
  Called when user drags the mouse after picking the dragger.
*/
void
SoTransformer2Dragger::drag(void)
{
  switch(THIS->whatkind) {
  case WHATKIND_SCALE:
    this->dragScale();
    break;
  case WHATKIND_TRANSLATE:
    this->dragTranslate();
    break;
  case WHATKIND_ROTATE:
    this->dragRotate();
    break;
  default:
    assert(0 && "illegal whatkind");
    break;
  }
}

void
SoTransformer2Dragger::dragTranslate()
{
  SbVec3f startpt = this->getLocalStartingPoint();
  startpt = this->localToWorking(startpt);

  this->planeProj->setViewVolume(this->getViewVolume());
  this->planeProj->setWorkingSpace(this->getWorkingToWorldMatrix());
  SbVec3f projpt = this->planeProj->project(this->getNormalizedLocaterPosition());

  const SoEvent *event = this->getEvent();
  if (event->wasShiftDown() && THIS->constraintState == CONSTRAINT_OFF) {
    THIS->constraintState = CONSTRAINT_WAIT;
    this->setStartLocaterPosition(event->getPosition());
  }
  else if (!event->wasShiftDown() && THIS->constraintState != CONSTRAINT_OFF) {
    THIS->constraintState = CONSTRAINT_OFF;
  }

  // Every time something changes (shift or ctrl is pressed), resave
  // the state so that the transformation to come starts with blank
  // sheets.
  if (this->setDynamicTranslatorSwitches(event)) {
    this->saveStartParameters();
    SbVec3f n(0.0f, 0.0f, 0.0f);
    n[THIS->dimension] = 1.0f;
    this->lineProj->setLine(SbLine(projpt, projpt+n));
    SbVec3f worldpt;
    this->getWorkingToWorldMatrix().multVecMatrix(projpt, worldpt);
    this->setStartingPoint(worldpt);
    startpt = projpt;
  }

  SbVec3f motion;
  if (THIS->ctrlDown) {
    this->lineProj->setViewVolume(this->getViewVolume());
    this->lineProj->setWorkingSpace(this->getWorkingToWorldMatrix());
    projpt = this->lineProj->project(this->getNormalizedLocaterPosition());
    motion = projpt - startpt;
  }
  else {
    motion = projpt - startpt;

    switch(THIS->constraintState) {
    case CONSTRAINT_OFF:
      break;
    case CONSTRAINT_WAIT:
      if (this->isAdequateConstraintMotion()) {
        int biggest = 0;
        double bigval = fabs(motion[0]);
        if (fabs(motion[1]) > bigval) {
          biggest = 1;
          bigval = fabs(motion[1]);
        }
        if (fabs(motion[2]) > bigval) {
          biggest = 2;
        }

        // Set all but the constraint axis to 0
        motion[(biggest + 1) % 3] = 0.0f;
        motion[(biggest + 2) % 3] = 0.0f;

        THIS->constraintState = CONSTRAINT_X + biggest;
      }
      else {
        return;
      }
      break;
    case CONSTRAINT_X:
      motion[1] = 0.0f;
      motion[2] = 0.0f;
      break;
    case CONSTRAINT_Y:
      motion[0] = 0.0f;
      motion[2] = 0.0f;
      break;
    case CONSTRAINT_Z:
      motion[0] = 0.0f;
      motion[1] = 0.0f;
      break;
    }
  }

  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  this->setMotionMatrix(this->appendTranslation(this->getStartMotionMatrix(), motion, &mat));
  this->unsquishKnobs();
}

void
SoTransformer2Dragger::dragScale()
{
  SbVec3f startpt = this->getLocalStartingPoint();
  startpt = this->localToWorking(startpt);

  this->lineProj->setViewVolume(this->getViewVolume());
  this->lineProj->setWorkingSpace(this->getWorkingToWorldMatrix());
  SbVec3f projpt = this->lineProj->project(this->getNormalizedLocaterPosition());
  const SoEvent *event = this->getEvent();

  if (event->wasShiftDown() && THIS->constraintState == CONSTRAINT_OFF) {
    THIS->constraintState = CONSTRAINT_WAIT;
    this->setStartLocaterPosition(event->getPosition());
  }
  else if (!event->wasShiftDown() && THIS->constraintState != CONSTRAINT_OFF) {
    this->saveStartParameters();
    THIS->constraintState = CONSTRAINT_OFF;
    this->lineProj->setLine(SbLine(SbVec3f(0.0f, 0.0f, 0.0f), projpt));
    THIS->ctrlOffset = this->calcCtrlOffset(projpt);
    startpt = projpt;
    SbVec3f worldpt;
    this->getWorkingToWorldMatrix().multVecMatrix(projpt, worldpt);
    this->setStartingPoint(worldpt);
  }

  if (THIS->constraintState == CONSTRAINT_WAIT && this->isAdequateConstraintMotion()) {
    // detect which dimension user has moved mouse the most. Done by projecting
    // mouse positions onto the near plane, finding that world vector, and
    // transforming that world vector into working space.
    const SbViewVolume &vv = this->getViewVolume();
    const SbViewportRegion &vp = this->getViewportRegion();
    SbVec2s move = this->getLocaterPosition() - this->getStartLocaterPosition();
    SbVec2f normmove((float)move[0]/(float)vp.getViewportSizePixels()[0],
                     (float)move[1]/(float)vp.getViewportSizePixels()[1]);
    SbVec3f tmp = vv.getPlanePoint(vv.getNearDist(), SbVec2f(0.5f, 0.5f));
    SbVec3f dir = vv.getPlanePoint(vv.getNearDist(), SbVec2f(0.5f, 0.5f) + normmove);
    dir -= tmp;
    dir.normalize();
    this->getWorldToWorkingMatrix().multDirMatrix(dir, dir);
    int biggest = 0;
    double bigval = fabs(dir[0]);
    if (fabs(dir[1]) > bigval) {
      biggest = 1;
      bigval = fabs(dir[1]);
    }
    if (fabs(dir[2]) > bigval) {
      biggest = 2;
    }
    SbVec3f n(0.0f, 0.0f, 0.0f);
    n[biggest] = 1.0f;

    THIS->constraintState = CONSTRAINT_X + biggest;

    this->saveStartParameters();
    this->lineProj->setLine(SbLine(projpt, projpt+n));
    startpt = projpt;
    projpt[(biggest+1)%3] = 0.0f;
    projpt[(biggest+2)%3] = 0.0f;
    THIS->ctrlOffset = this->calcCtrlOffset(projpt);
    projpt = startpt;
    SbVec3f worldpt;
    this->getWorkingToWorldMatrix().multVecMatrix(projpt, worldpt);
    this->setStartingPoint(worldpt);
  }

  // If the control key is pressed or released,
  // setDynamicScaleSwitches will return TRUE. When this happens, we
  // have to resave our start motionmatrix to be able to do correct
  // scaling. If we do not do this, the scaled object will jump
  // because the scale center is changed, but not the scale.
  if (this->setDynamicScaleSwitches(event)) {
    this->saveStartParameters();
    startpt = projpt;

    SbVec3f worldpt;
    this->getWorkingToWorldMatrix().multVecMatrix(projpt, worldpt);
    this->setStartingPoint(worldpt);
  }

  if (THIS->constraintState == CONSTRAINT_WAIT) return;

  if (THIS->constraintState >= CONSTRAINT_X) {
    int num = THIS->constraintState - CONSTRAINT_X;
    projpt[(num+1)%3] = 0.0f;
    projpt[(num+2)%3] = 0.0f;
    startpt[(num+1)%3] = 0.0f;
    startpt[(num+2)%3] = 0.0f;
  }

  SbVec3f center(0.0f, 0.0f, 0.0f);
  if (THIS->ctrlDown) {
    center -= THIS->ctrlOffset;
  }

  float orglen = (startpt-center).length();
  float currlen = (projpt-center).length();
  float scale = 0.0f;

  if (orglen > 0.0f) scale = currlen / orglen;
  if (scale > 0.0f && (startpt-center).dot(projpt-center) <= 0.0f) scale = 0.0f;

  SbVec3f scalevec(scale, scale, scale);
  if (THIS->constraintState >= CONSTRAINT_X) {
    int num = THIS->constraintState - CONSTRAINT_X;
    scalevec[(num+1)%3] = 1.0f;
    scalevec[(num+2)%3] = 1.0f;
  }

  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  this->setMotionMatrix(this->appendScale(this->getStartMotionMatrix(),
                                          scalevec,
                                          center, &mat));
  this->unsquishKnobs();
}

// The dragRotate method has been implemented somewhat differently
// than the other drag functions. This is because of the unfortunate
// effect that happens when a non-uniform scale has been applied to
// the motion-matrix, and rotation is attempted to be done before the
// scale. Both operations are perfectly valid, but the end result is
// not what you'd expect; the transformation comes out sheared. To
// prevent this from happening, the rotation matrices are applied in
// world space, that is after all other transformations have been
// applied (scale and translation are applied in local
// space). What is basically done is this:
//
// Calculates the matrix (as done in SoDragger::appendRotation()):
// 
// C := conversion
// P := rotcenter
// R := rot
// M := new motion matrix
// Mold := previous motionmatrix
// 
// M = C^-1 * P^-1 * R * P * C * Mold
// 
// What essentially happens is that we transform into C's coordinate
// system, then we move the rotation center to origo and apply the
// new rotation.  The rotation has now been applied, but we are in
// the wrong coordinate system, so we reapply the rotation center
// and the conversion. Finally we transform the matrix by the old
// transformation, which gives us the new rotated transformation.
// 
// The rotation happens in the local coordinate system of the object
// if C = P = I, but if P is specified, then the rotation center
// will be adjusted before the rotation is applied. If the
// conversion matrix has been specified to be e.g:
// 
// C = (Mold * W)^-1
// 
// Then the resulting matrix looks something like this:
// 
// M = ((Mold * W)^-1)^-1 * P^-1 * R * P * (Mold * W)^-1 * Mold
//   = Mold * W * P^-1 * R * P * W^-1 * Mold^-1 * Mold
//   = Mold * W * P^-1 * R * P * W^-1
//
// Take a closer look at the resulting matrix: It basically reverses
// the transformation. Instead of having the rotation applied before
// the old transformation, the rotation is applied after the old
// transformation. The rotation also happens in world space - that is
// the coordinate system is transformed to the world coordinate system
// before the rotation is applied around rotationcenter. Finally the
// matrix is transformed back to the local coordinate system.
void
SoTransformer2Dragger::dragRotate(void)
{
  // Update the sphere projector to the current view so that it
  // accurately can react to events.
  this->sphereProj->setViewVolume(this->getViewVolume());
  this->sphereProj->setWorkingSpace(this->getWorkingToWorldMatrix());

  const SoEvent *event = this->getEvent();

  SbVec3f startpt, projpt;
  startpt = this->getLocalStartingPoint();
  startpt = this->localToWorking(startpt);

  // If shift was pressed and not in non-constrained state, then
  // we switch to non-constrained state.
  if (event->wasShiftDown() && THIS->constraintState != CONSTRAINT_OFF) {
    THIS->constraintState = CONSTRAINT_OFF;
    projpt = this->sphereProj->project(this->getNormalizedLocaterPosition());
    this->getWorkingToWorldMatrix().multVecMatrix(projpt, THIS->prevWorldHitPt);
    THIS->prevMotionMatrix = this->getMotionMatrix();
    this->saveStartParameters();
    this->setStartingPoint(THIS->prevWorldHitPt);
  }
  // If shift was released while in non-constrained state, then the
  // state jumps back to constrained.
  else if (!event->wasShiftDown() && THIS->constraintState == CONSTRAINT_OFF) {
    THIS->constraintState = CONSTRAINT_WAIT;
    this->setStartingPoint(THIS->prevWorldHitPt);
    startpt = this->getLocalStartingPoint();
    startpt = this->localToWorking(startpt);
    // This plane is only used to find the constraint direction
    this->planeProj->setPlane(SbPlane(startpt, startpt));
    this->setStartLocaterPosition(event->getPosition());
    THIS->normalizedStartLocaterPosition = this->getNormalizedLocaterPosition();
    this->saveStartParameters();
  }

  SbVec3f center(0.0f, 0.0f, 0.0f);
  if (THIS->ctrlDown) {
    // Adjust the center to be on the other side of the bounding box
    // when the control key has been pressed.
    center -= THIS->ctrlOffset * KNOB_DISTANCE;
  }

  // Show the necessary feedback geometry for rotation.
  (void) this->setDynamicRotatorSwitches(event);

  if (THIS->constraintState == CONSTRAINT_OFF) {
    SbVec3f worldcenter = this->getBoxPointInWorldSpace(center);
    SbVec3f prevworldprojpt = THIS->prevWorldHitPt;
    SbVec3f worldprojpt;

    // Find locater position in world space
    projpt = this->sphereProj->project(this->getNormalizedLocaterPosition());
    this->getWorkingToWorldMatrix().multVecMatrix(projpt, worldprojpt);
    SbVec3f wppt = worldprojpt;

    // Find the projection vectors from the box center in world space.
    worldprojpt -= worldcenter;
    prevworldprojpt -= worldcenter;

    // Normalize the vectors before finding the dotproduct angle
    // between them.
    worldprojpt.normalize();
    prevworldprojpt.normalize();

    // FIXME: Without this test, the rotation behaves very strange,
    // especially at the edge/outside of the sphere. I will
    // investigate this some more to understand what happens. 
    // 20040804 jornskaa
    if (worldprojpt.dot(prevworldprojpt) > 0.8f) {
      THIS->prevWorldHitPt = wppt;

      // Calculate the rotation from previous prevworldprojpt to
      // worldprojpt
      SbRotation rot(prevworldprojpt, worldprojpt);
      
      // Calculate new motionmatrix by rotating in world space to
      // prevent shearing with non-uniform scale.
      SbMatrix mat = this->getWorldToLocalMatrix();
      THIS->prevMotionMatrix = this->appendRotation(THIS->prevMotionMatrix, rot,
                                                    worldcenter, &mat);
      
      this->setMotionMatrix(THIS->prevMotionMatrix);
    }
  }
  else if (THIS->constraintState == CONSTRAINT_WAIT && this->isAdequateConstraintMotion()) {
    this->planeProj->setViewVolume(this->getViewVolume());
    this->planeProj->setWorkingSpace(this->getWorkingToWorldMatrix());
    projpt = this->planeProj->project(this->getNormalizedLocaterPosition());

    SbVec3f diff = projpt - startpt;
    diff[THIS->dimension] = 0.0f;

    int biggest = 0;
    double bigval = fabs(diff[0]);
    if (fabs(diff[1]) > bigval) {
      biggest = 1;
      bigval = fabs(diff[1]);
    }
    if (fabs(diff[2]) > bigval) {
      biggest = 2;
    }
    THIS->constraintState = CONSTRAINT_X + biggest;
    SbVec3f n(0.0f, 0.0f, 0.0f);
    n[biggest] = 1.0f;
    SbVec3f dim(0.0f, 0.0f, 0.0f);
    dim[THIS->dimension] = 1.0f;

    // set plane to do disc-rotate in
    this->planeProj->setPlane(SbPlane(SbVec3f(0.0f, 0.0f, 0.0f), dim, dim+n));
    (void) this->setDynamicRotatorSwitches(event);

    // Initialize prevWorldHitPt and prevMotionMatrix.
    projpt = this->planeProj->project(THIS->normalizedStartLocaterPosition);
    this->getWorkingToWorldMatrix().multVecMatrix(projpt, THIS->prevWorldHitPt);
    THIS->prevMotionMatrix = this->getMotionMatrix();
  }
  if (THIS->constraintState >= CONSTRAINT_X) {
    this->planeProj->setViewVolume(this->getViewVolume());
    this->planeProj->setWorkingSpace(this->getWorkingToWorldMatrix());

    SbVec3f worldcenter = this->getBoxPointInWorldSpace(center);
    SbVec3f prevworldprojpt;
    SbVec3f prevprojpt;
    SbVec3f worldprojpt;

    prevworldprojpt = THIS->prevWorldHitPt;

    // Find current locater position projected onto the plane in world
    // space.
    projpt = this->planeProj->project(this->getNormalizedLocaterPosition());
    this->getWorkingToWorldMatrix().multVecMatrix(projpt, worldprojpt);

    // Project the vectors onto the projection plane in world space.
    // This is needed to make the rotation vectors more numerically
    // stable especially when the dragger has been scaled to be very
    // small before rotation is attempted.
    SbPlane plane = this->planeProj->getPlane();
    plane.transform(this->getWorkingToWorldMatrix());
    prevworldprojpt -= plane.getNormal() * plane.getDistance(prevworldprojpt);
    worldprojpt -= plane.getNormal() * plane.getDistance(worldprojpt);

    // Save the values for the new projection point before it is
    // altered.
    SbVec3f wppt = worldprojpt;

    // Adjust the rotation vectors of the dragger to match the center
    // of the dragger in working space and world space.
    projpt -= center;
    prevworldprojpt -= worldcenter;
    worldprojpt -= worldcenter;

    // Make sure the length of the projected vector is greater than
    // a scalar constant. This ensures that the rotation is defined
    // and there is little room for error that might happen when the
    // vectors come very close to the rotation center.
    if (projpt.sqrLength() > 0.1f) {
      // Normalize the vectors before finding the dotproduct angle
      // between them.
      prevworldprojpt.normalize();
      worldprojpt.normalize();

      // Since we are using incremental changes, the changes will
      // never be very big, and the dot product between the
      // rotation-vectors should always be above zero, and never
      // negative because that indicates the angle between the vectors
      // is above PI/2. This might happen if dragging is done over the
      // rotation center, but we do not allow that and just wait until
      // the locater comes into the valid range before rotating.
      if (prevworldprojpt.dot(worldprojpt) > 0.3f) { // 0.3 == 72.5degrees
        THIS->prevWorldHitPt = wppt;
        
        // Rotate between the two points in the plane
        SbRotation rot(prevworldprojpt, worldprojpt);
        
        // Rotate in world space to prevent shearing when having
        // non-uniform scale.
        SbMatrix mat = this->getWorldToLocalMatrix();
        THIS->prevMotionMatrix = (this->appendRotation(THIS->prevMotionMatrix, rot,
                                                       worldcenter, &mat));
        
        this->setMotionMatrix(THIS->prevMotionMatrix);
      }
    }
  }
  this->unsquishKnobs();
}

/*! \COININTERNAL
  Called when mouse button is released after picking and interacting
  with the dragger.
*/
void
SoTransformer2Dragger::dragFinish(void)
{
  switch (THIS->whatkind) {
  case WHATKIND_TRANSLATE:
    this->setSwitchValue("translateBoxFeedbackSwitch", SO_SWITCH_NONE);
    break;
  case WHATKIND_ROTATE:
  this->setSwitchValue("xCircleFeedbackSwitch", SO_SWITCH_NONE);
  this->setSwitchValue("yCircleFeedbackSwitch", SO_SWITCH_NONE);
  this->setSwitchValue("zCircleFeedbackSwitch", SO_SWITCH_NONE);
    break;
  case WHATKIND_SCALE:
    this->setSwitchValue("radialFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("posXWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("negXWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("posYWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("negYWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("posZWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("negZWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("scaleBoxFeedbackSwitch", SO_SWITCH_NONE);
    break;
  default:
    assert(0 && "unknown whatkind");
    break;
  }

  THIS->whatkind = WHATKIND_NONE;
  this->state = INACTIVE;
  THIS->constraintState = CONSTRAINT_OFF;
  this->setAllPartSwitches(0,0,0);
  this->setSwitchValue("xAxisFeedbackSwitch", SO_SWITCH_NONE);
  this->setSwitchValue("yAxisFeedbackSwitch", SO_SWITCH_NONE);
  this->setSwitchValue("zAxisFeedbackSwitch", SO_SWITCH_NONE);

#if COIN_DEBUG && 0 // used to debug motion matrix (pederb, 20000225)
  SbMatrix m = this->getMotionMatrix();
  SbRotation r,so;
  SbVec3f t,s;

  fprintf(stderr,"motion matrix:\n");
  m.print(stderr);
  m.getTransform(t, r, s, so);
  SbVec3f rx, sox;
  float ra, soa;
  r.getValue(rx, ra);
  so.getValue(sox, soa);
  fprintf(stderr,
          "\nt: %g %g %g\n"
          "r: %g %g %g, %g\n"
          "s: %g %g %g\n"
          "so: %g %g %g, %g\n\n",
          t[0], t[1], t[2],
          rx[0], rx[1], rx[2], ra,
          s[0], s[1], s[2],
          sox[0], sox[1], sox[2], soa);
#endif // debug code
  invalidate_surroundscale(this);
}

void
SoTransformer2Dragger::updateAntiSquishList(void)
{
  if (this->antiSquishList.getLength() == 0) {
    SoSeparator *top = SO_GET_ANY_PART(this, "topSeparator", SoSeparator);
    assert(top);

    SoSearchAction sa;
    sa.setInterest(SoSearchAction::ALL);
    sa.setType(SoAntiSquish::getClassTypeId());
    sa.setSearchingAll(TRUE);
    sa.apply(top);

    SoPathList &pl = sa.getPaths();
    for (int i = 0; i < pl.getLength(); i++) {
      SoFullPath *path = (SoFullPath*)pl[i];
      SoNode *tail = path->getTail();
      int j, n = this->antiSquishList.getLength();
      for (j = 0; j < n; j++) {
        if (this->antiSquishList[j] == tail) break;
      }
      if (j == n)
        this->antiSquishList.append(path->getTail());
    }
  }
  int n = this->antiSquishList.getLength();
  for (int i = 0; i < n; i++) {
    SoAntiSquish *squishy = (SoAntiSquish*) this->antiSquishList[i];
    squishy->recalc();
  }
}

void
SoTransformer2Dragger::setAllPartSwitches(int scalewhich, int rotatewhich, int translatewhich)
{
  int i;
  SoSwitch *sw;
  SbString str;

  for (i = 1; i <= 6; i++) {
    str.sprintf("translator%dSwitch", i);
    sw = SO_GET_ANY_PART(this, str.getString(), SoSwitch);
    SoInteractionKit::setSwitchValue(sw, translatewhich);
  }
  for (i = 1; i <= 6; i++) {
    str.sprintf("rotator%dSwitch", i);
    sw = SO_GET_ANY_PART(this, str.getString(), SoSwitch);
    SoInteractionKit::setSwitchValue(sw, rotatewhich);
  }
  for (i = 1; i <= 8; i++) {
    str.sprintf("scale%dSwitch", i);
    sw = SO_GET_ANY_PART(this, str.getString(), SoSwitch);
    SoInteractionKit::setSwitchValue(sw, scalewhich);
  }
}

/*!
  Not implemented in Coin; should probably not have been public in the
  Open Inventor API.  We'll consider to implement it if requested.
*/
int
SoTransformer2Dragger::getMouseGestureDirection(SbBool x_ok, SbBool y_ok, SbBool z_ok)
{
  COIN_OBSOLETED();
  return -1;
}

/*!
  Not implemented in Coin; should probably not have been public in the
  Open Inventor API.  We'll consider to implement it if requested.
*/
int
SoTransformer2Dragger::getIgnoreAxis(SbVec2f axis[3][2], SbBool x_ok, SbBool y_ok, SbBool z_ok)
{
  COIN_OBSOLETED();
  return -1;
}

/*!
  Not implemented in Coin; should probably not have been public in the
  Open Inventor API.  We'll consider to implement it if requested.
*/
void
SoTransformer2Dragger::makeMinorAxisPerpendicularIfColinear(SbVec2f origin, SbVec2f axisends[3][2], int index_a, int index_b)
{
  COIN_OBSOLETED();
}

/*!
  Not implemented in Coin; should probably not have been public in the
  Open Inventor API.  We'll consider to implement it if requested.
*/
SbBool
SoTransformer2Dragger::isColinear(SbVec2f a1[2], SbVec2f a2[2], int pixels)
{
  COIN_OBSOLETED();
  return FALSE;
}

void
SoTransformer2Dragger::getSurroundScaleMatrices(SbMatrix & mat, SbMatrix & inv)
{
  if (this->surroundScale.getValue()) {
    this->getPartToLocalMatrix("surroundScale", mat, inv);
  }
  else {
    mat = inv = SbMatrix::identity();
  }
}

SoNode *
SoTransformer2Dragger::getNodeFieldNode(const char *fieldname)
{
  SoField *field = this->getField(fieldname);
  assert(field != NULL);
  assert(field->isOfType(SoSFNode::getClassTypeId()));
  assert(((SoSFNode*)field)->getValue() != NULL);
  return ((SoSFNode*)field)->getValue();
}

SbMatrix
SoTransformer2Dragger::getWorkingToWorldMatrix()
{
  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  mat.multRight(this->getLocalToWorldMatrix());
  return mat;
}

SbMatrix
SoTransformer2Dragger::getWorldToWorkingMatrix(void)
{
  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  mat.multLeft(this->getWorldToLocalMatrix());
  return mat;
}

SbVec3f
SoTransformer2Dragger::localToWorking(const SbVec3f &v)
{
  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  SbVec3f ret;
  inv.multVecMatrix(v, ret);
  return ret;
}

SbVec3f
SoTransformer2Dragger::workingToLocal(const SbVec3f &v)
{
  SbMatrix mat, inv;
  this->getSurroundScaleMatrices(mat, inv);
  SbVec3f ret;
  mat.multVecMatrix(v, ret);
  return ret;
}

SbVec3f
SoTransformer2Dragger::calcCtrlOffset(const SbVec3f &startpt)
{
  SbVec3f v = startpt;
  for (int i = 0; i < 3; i++) {
    if (v[i] < -0.8) v[i] = -1.0f;
    else if (v[i] > 0.8) v[i] = 1.0f;
    else v[i] = 0.0f;
  }
  return v;
}

void
SoTransformer2Dragger::setSwitchValue(const char *str, const int which)
{
  SoSwitch *sw = SO_GET_ANY_PART(this, str, SoSwitch);
  SoInteractionKit::setSwitchValue(sw, which);
}

void
SoTransformer2Dragger::setSwitchValue(SoNode *node, const int which)
{
  SoSwitch *sw = (SoSwitch *)node;
  SoInteractionKit::setSwitchValue(sw, which);
}

SbBool
SoTransformer2Dragger::setDynamicTranslatorSwitches(const SoEvent *event)
{
  SbBool changed = FALSE;
  if (THIS->ctrlDown != event->wasCtrlDown()) {
    changed = TRUE;
    THIS->ctrlDown = !THIS->ctrlDown;
  }
  if (THIS->shiftDown != event->wasShiftDown()) {
    changed = TRUE;
    THIS->shiftDown = !THIS->shiftDown;
  }

  SbString str;

  if (THIS->constraintState >= CONSTRAINT_X) {
    int which = THIS->constraintState - CONSTRAINT_X;
    str.sprintf("%cAxisFeedbackSwitch", 'x' + which);
    this->setSwitchValue(str.getString(), 0);
    str.sprintf("%cAxisFeedbackSwitch", 'x' + (which+1)%3);
    this->setSwitchValue(str.getString(), SO_SWITCH_NONE);
    str.sprintf("%cAxisFeedbackSwitch", 'x' + (which+2)%3);
    this->setSwitchValue(str.getString(), SO_SWITCH_NONE);
  }
  else {
    str.sprintf("%cAxisFeedbackSwitch", 'x' + THIS->dimension);
    this->setSwitchValue(str.getString(), THIS->ctrlDown ? 0 : SO_SWITCH_NONE);
    int val = THIS->shiftDown ? 1 : 0;
    if (THIS->ctrlDown) val = SO_SWITCH_NONE;
    str.sprintf("%cAxisFeedbackSwitch", 'x' + (THIS->dimension+1)%3);
    this->setSwitchValue(str.getString(), val);
    str.sprintf("%cAxisFeedbackSwitch", 'x' + (THIS->dimension+2)%3);
    this->setSwitchValue(str.getString(), val);
  }
  return changed;
}

SbBool
SoTransformer2Dragger::setDynamicScaleSwitches(const SoEvent *event)
{
  SbBool changed = FALSE;
  if (THIS->ctrlDown != event->wasCtrlDown()) {
    changed = TRUE;
    THIS->ctrlDown = !THIS->ctrlDown;
  }
  if (THIS->shiftDown != event->wasShiftDown()) {
    changed = TRUE;
    THIS->shiftDown = !THIS->shiftDown;
  }
  SbString str;
  if (THIS->constraintState == CONSTRAINT_WAIT) {
    this->setSwitchValue("xAxisFeedbackSwitch", 1);
    this->setSwitchValue("yAxisFeedbackSwitch", 1);
    this->setSwitchValue("zAxisFeedbackSwitch", 1);
    this->setSwitchValue("radialFeedbackSwitch", SO_SWITCH_NONE);
  }
  else if (THIS->constraintState >= CONSTRAINT_X) {
    int which = THIS->constraintState - CONSTRAINT_X;
    str.sprintf("%cAxisFeedbackSwitch", 'x' + which);
    this->setSwitchValue(str.getString(), 0);
    str.sprintf("%cAxisFeedbackSwitch", 'x' + (which+1)%3);
    this->setSwitchValue(str.getString(), SO_SWITCH_NONE);
    str.sprintf("%cAxisFeedbackSwitch", 'x' + (which+2)%3);
    this->setSwitchValue(str.getString(), SO_SWITCH_NONE);
    this->setSwitchValue("radialFeedbackSwitch", SO_SWITCH_NONE);
  }
  else {
    this->setSwitchValue("xAxisFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("yAxisFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("zAxisFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("radialFeedbackSwitch", 0);
  }

  this->setSwitchValue("scaleBoxFeedbackSwitch", THIS->shiftDown ? 0 : SO_SWITCH_NONE);

  if (THIS->ctrlDown) {
    SbVec3f pt = this->getLocalStartingPoint();
    if (THIS->constraintState >= CONSTRAINT_X) {
      int num = THIS->constraintState - CONSTRAINT_X;
      pt[(num+1)%3] = 0.0f;
      pt[(num+2)%3] = 0.0f;
    }
    SbString str;
    for (int i = 0; i < 3; i++) {
      str.sprintf("pos%cWallFeedbackSwitch", 'X' + i);
      this->setSwitchValue(str.getString(), pt[i] < 0.0f ? 0 : SO_SWITCH_NONE);
      str.sprintf("neg%cWallFeedbackSwitch", 'X' + i);
      this->setSwitchValue(str.getString(), pt[i] > 0.0f ? 0 : SO_SWITCH_NONE);
    }
  }
  else {
    this->setSwitchValue("posXWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("negXWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("posYWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("negYWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("posZWallFeedbackSwitch", SO_SWITCH_NONE);
    this->setSwitchValue("negZWallFeedbackSwitch", SO_SWITCH_NONE);
  }
  return changed;
}


SbBool
SoTransformer2Dragger::setDynamicRotatorSwitches(const SoEvent *event)
{
  SbBool changed = FALSE;
  if (THIS->ctrlDown != event->wasCtrlDown()) {
    changed = TRUE;
    THIS->ctrlDown = !THIS->ctrlDown;
  }
  if (THIS->shiftDown != event->wasShiftDown()) {
    changed = TRUE;
    THIS->shiftDown = !THIS->shiftDown;
  }

  SbString str;
  {
    int axis0 = THIS->whatnum-1;
    int axis1 = (axis0 & 1) ? axis0 - 1 : axis0 + 1;

    str.sprintf("rotator%dSwitch", axis0 + 1);
    this->setSwitchValue(str.getString(), 1);
    str.sprintf("rotator%dSwitch", axis1 + 1);
    this->setSwitchValue(str.getString(), THIS->ctrlDown ? 0 : 1);
  }

  int axisval[3];
  int circleval[3];
  int dim = THIS->dimension;

  if (THIS->constraintState == CONSTRAINT_WAIT) {
    axisval[dim] = SO_SWITCH_NONE;
    axisval[(dim+1)%3] = 1;
    axisval[(dim+2)%3] = 1;
    circleval[dim] = SO_SWITCH_NONE;
    circleval[(dim+1)%3] = 0;
    circleval[(dim+2)%3] = 0;
  }
  else if (THIS->constraintState >= CONSTRAINT_X) {
    dim = THIS->constraintState - CONSTRAINT_X;
    axisval[dim] = 0;
    axisval[(dim+1)%3] = SO_SWITCH_NONE;
    axisval[(dim+2)%3] = SO_SWITCH_NONE;

    const SbVec3f &n = this->planeProj->getPlane().getNormal();
    circleval[0] = n[0] != 0.0f ? 0 : SO_SWITCH_NONE;
    circleval[1] = n[1] != 0.0f ? 0 : SO_SWITCH_NONE;
    circleval[2] = n[2] != 0.0f ? 0 : SO_SWITCH_NONE;
  }
  else {
    circleval[0] = 0;
    circleval[1] = 0;
    circleval[2] = 0;
    axisval[0] = SO_SWITCH_NONE;
    axisval[1] = SO_SWITCH_NONE;
    axisval[2] = SO_SWITCH_NONE;
  }
  if (THIS->ctrlDown) {
    SoTransform *transform = SO_GET_ANY_PART(this, "circleFeedbackTransform", SoTransform);
    SbVec3f offset = -THIS->ctrlOffset * KNOB_DISTANCE;
    if (transform->translation.getValue() != offset)
      transform->translation = offset;
    if (transform->scaleFactor.getValue() != SbVec3f(2.0f, 2.0f, 2.0f))
      transform->scaleFactor = SbVec3f(2.0f, 2.0f, 2.0f);
    this->setSwitchValue("circleFeedbackTransformSwitch", SO_SWITCH_ALL);
  }
  else {
    this->setSwitchValue("circleFeedbackTransformSwitch", 0);
  }

  this->setSwitchValue("xAxisFeedbackSwitch", axisval[0]);
  this->setSwitchValue("yAxisFeedbackSwitch", axisval[1]);
  this->setSwitchValue("zAxisFeedbackSwitch", axisval[2]);
  this->setSwitchValue("xCircleFeedbackSwitch", circleval[0]);
  this->setSwitchValue("yCircleFeedbackSwitch", circleval[1]);
  this->setSwitchValue("zCircleFeedbackSwitch", circleval[2]);

  return changed;
}

// Calculates the location of the center field as expressed in 
// box space, then sets the matrix of the translateToCenter part
// to be a translation of that amount.
void 
SoTransformer2Dragger::recalcTranslateToCenter()
{
  // Build a matrix to translate the origin of the ball
  // to the transformed center...
  SbVec3f ctrForMatrix;

  if ( center.isDefault() == FALSE )
  {
    // If the center value is non-default, then just translate to it:
	ctrForMatrix = center.getValue();
  }
  else
  {
	// We must do this because recalcTranslateToCenter can get called at 
	// all kinds of funny times, for example when an ancestor transform's 
	// field value is changed.  If we don't invalidate, then we'll fail
	// to incorporate changes along the path above this node.
    invalidateWorldConversionMatrices();

	// If the center is default, get the center of the bbox into
	// local space.
	SbMatrix lclToBox, boxToLcl;
	getPartToLocalMatrix("surroundScale", boxToLcl,lclToBox);
	boxToLcl.multVecMatrix(SbVec3f(0,0,0), ctrForMatrix);
  }

  SbMatrix newMat;
  newMat.setTranslate( ctrForMatrix );

  // Set the translate to center matrix...
  SoMatrixTransform *mx = SO_GET_ANY_PART(this,"translateToCenter",
      SoMatrixTransform );

  //??? Turn off notification.  We have to do this because this often
  //??? gets called in the midst of a boundingBoxAction (when the 
  //??? surroundScale recalcs its parameters).
  //??? It should be okay to turn off notification because this translation
  //??? node lives as a sibling to a CowViewportSwitch node, which 
  //??? invalidates caches every time it is traversed. So the separator
  //??? above this one will never be cached and therefore the matrix
  //??? will always be visited.
  //??? 
  SbBool wasEnabled = mx->enableNotify(FALSE);
  mx->matrix = newMat;
  mx->enableNotify(wasEnabled);
}

// This returns the value of the center field, expressed in box
// space.  If the center.isDefault() is TRUE, then it returns (0,0,0)
// This might not be the same as the center field value of (0,0,0) when
// cast into box space, since the conversion is not always identity.
//
SbVec3f 
SoTransformer2Dragger::getCenterInBoxSpace()
{
  SbVec3f answer;
  if ( center.isDefault() )
  {
	answer.setValue(0,0,0);
  }
  else
  {
	SbMatrix lclToBox, boxToLcl;
	getPartToLocalMatrix("surroundScale", boxToLcl,lclToBox);
	lclToBox.multVecMatrix(center.getValue(),answer);
  }
  return answer;
}

//    Saves the matrix that moves the center relative to the rest of the
//    parts after it calls the regular SoDragger method.
//
void
SoTransformer2Dragger::saveStartParameters()
{
  SoDragger::saveStartParameters();

  SoMatrixTransform *thePart
    = SO_GET_ANY_PART(this,"translateToCenter",SoMatrixTransform);

  this->startCenterMatrix = thePart->matrix.getValue();
}

void
SoTransformer2Dragger::surroundScaleChangeCB( void *userData,
    SoSurroundScale * )
{
  SoTransformer2Dragger *myself = (SoTransformer2Dragger *) userData;
  myself->recalcTranslateToCenter();
}

// Undefine these again, as some of them are also used in other
// dragger sourcecode files (which causes trouble when using the
// compact build hack where all .cpp files are included into all.cpp).

#undef WHATKIND_NONE
#undef WHATKIND_SCALE
#undef WHATKIND_TRANSLATE
#undef WHATKIND_ROTATE

#undef CONSTRAINT_OFF
#undef CONSTRAINT_WAIT
#undef CONSTRAINT_X
#undef CONSTRAINT_Y
#undef CONSTRAINT_Z

#undef KNOB_DISTANCE

#undef THIS
#undef THISP
Clone this wiki locally