22.4.  References

X3D Specification:

www.web3d.org/x3d/specifications/ISO-IEC-19775-X3DAbstractSpecification/

VRML97 Specification:

www.web3d.org/x3d/specifications/vrml/ISO-IEC-14772-VRML97/

Open Inventor on SGI machines: www.sgi.com

Open Inventor everywhere else: www.mc.com/tgs

At the time this was written, there were some limitations in the Open Inventor VRML nodes. Please check the documentation (README, RELNOTES, Help File, man pages, etc.) for the Open Inventor version you are currently using. The following information is based on the Open Inventor 6.0 release by FEI. The information is organized by VRML node name in alphabetical order. Remember that the Open Inventor documentation is organized by classname and the classname is formed by prefixing “ SoVRML” to the VRML node name.

Also remember that all nodes and their fields are read and created in the scene graph, so the information is available if applications want or need to implement some of the features that are not available yet. We are continuing to work on the missing features and our goal is to have a complete implementation that would allow an application to create a conforming viewer.

Appearance

The fillProperties field is not implemented.

AudioClip

Available on Microsoft Windows only. pauseTime and resumeTime fields not implemented.

Background

Not implemented by viewers, but application can implement.

Collision

Functions correctly as a grouping node, but collideTime eventOut is not implemented. Note that Open Inventor supports collision detection, but it is the application’s responsibility to use it or not. The collide field (VRML 2.0) and the enabled field (X3D) are not implemented.

ColorRGBA

Not implemented.

CylinderSensor

Not implemented.

FillProperties

Not implemented.

Fog

Not implemented.

FontStyle

The leftToRight and topToBottom fields are not implemented.

Only horizontal justification is implemented.

ImageTexture

Open Inventor will only try to open the first URL specified (app can override).

IndexedTriangleFanSet

Not implemented.

IndexedTriangleSet

Not implemented.

IndexedTriangleStripSet

Not implemented.

Inline

Open Inventor will only try to open the first URL specified (app can override). The load field is not implemented.

KeySensor

Not implemented.

LineSet

Not implemented.

LoadSensor

Not implemented.

MovieTexture

Negative speed values are not supported. The pauseTime and resumeTime fields are not implemented.

MultiTexture

Not implemented.

MultiTextureCoordinate

Not implemented.

MultiTextureTransform

Not implemented.

NavigationInfo

Partially implemented. The speed, visibilityLimit, and transitionType fields are not implemented.

PlaneSensor

Not implemented.

PointLight

The radius field is not implemented (acceptable per VRML97 spec, section 7.3.3).

Script

Not implemented.

Sound

Available on Microsoft Windows only.

SphereSensor

Not implemented.

SpotLight

The radius field is not implemented (acceptable per VRML97 spec, section 7.3.3).

StaticGroup

Not implemented.

StringSensor

Not implemented.

Text

The maxExtent and solid fields are not implemented.

TextureBackground

Not implemented.

TextureCoordinateGenerator

Not implemented.

TextureTransform

Transformations are applied in the (more intuitive) pre-ISO order.

TimeSensor

The pauseTime and resumeTime fields are not implemented.

TouchSensor

The isOver event is only generated while one of the mouse buttons is pressed. The xxx_changed events are not generated unless isActive is TRUE, in other words, only while a mouse button is pressed.

TriangleFanSet

Not implemented.

TriangleSet

Not implemented.

TriangleStripSet

Not implemented.

Viewpoint

The “binding” behavior is not implemented. The centerOfRotation field is not implemented.

VisibilitySensor

Not implemented.

Example 22.9. How to attach a Color Editor

SoPath *selectionPath
SoXtColorEditor *colorEditor

SoNode *pTailNode = selectionPath->getTail();
if (pTailNode->isOfType(SoVRMLShape::getClassTypeId()))
{
  SoVRMLShape *pShapeNode = (SoVRMLShape*)pTailNode;
  // Note: We're only interested in editing the diffuseColor and
  // that simplifies the problem somewhat --
  //
  // 1) If geometry contains a color field and that field contains
  // a Color node (ie. is not NULL), edit the first value in the
  // Color node's (SoMFColor) color field. DONE.
  //
  // 2) Else, if Appearance node is NULL, create a Appearance node
  // (created node will have a NULL Material node of course.)
  //
  // 3) If Material node is NULL, create a Material node, set the
  // diffuseColor field to <0 0 0>, set emissiveColor field to
  // <1 1 1>, then edit the (SoSFColor) emissiveColor field.
  //
  // Note: If either Appearance or Material node is NULL then
  // VRML2 spec says the object is unlit and has color <1 1 1>.
  // Since we need to create a Material node in order to edit
  // the object's color, we set up its fields to simulate the
  // unlit appearance. We can only approximate this since VRML
  // does not have any way to specify the object color  and
  // turn off lighting.
  //
  // 4) Else Appearance and Material nodes exist, so edit the
  // Material node's (SoSFColor) diffuseColor field.
  //
  // First check geometry.
  // Get ptr to geometry node, then see if it's one of the ones
  // that has a color field (IndexedFaceSet, IndexedLineSet,
  // PointSet, ElevationGrid) Unfortunately we have to check each
  // one separately because currently there is no common ancestor
  // class that contains the color field.
  
  SoNode *pGeoNode = pShapeNode->geometry.getValue();
  if (pGeoNode != NULL)
  {
    SoSFNode *pClrNodeField = NULL;
    if (pGeoNode->isOfType(SoVRMLVertexShape::getClassTypeId()))
    pClrNodeField = &(((SoVRMLVertexShape*)pGeoNode)->color);
    else if (pGeoNode->isOfType(SoVRMLVertexPoint::getClassTypeId()))
    pClrNodeField = &(((SoVRMLVertexPoint*)pGeoNode)->color);
    else if (pGeoNode->isOfType(SoVRMLVertexLine::getClassTypeId()))
    pClrNodeField = &(((SoVRMLVertexLine*)pGeoNode)->color);
    else if (pGeoNode->isOfType(SoVRMLGridShape::getClassTypeId()))
    pClrNodeField = &(((SoVRMLGridShape*)pGeoNode)->color);
    
    // If the geometry has a color node
    if (pClrNodeField != NULL)
    {
      // and it contains a Color node, attach to color field
      SoVRMLColor *pClrNode = (SoVRMLColor*)pClrNodeField->getValue();
      if (pClrNode != NULL)
      {
        colorEditor->attach(&(pClrNode->color), 0, pClrNode);
        colorEditor->show();
        return;
      }
    }
  }

  // Now check Appearance/Material
  SoVRMLAppearance *pAppNode = (SoVRMLAppearance*)pShapeNode->appearance.getValue();
  SoVRMLMaterial *pMatNode = NULL;
  SoSFColor *pClrField = NULL;
  
  // If no Appearance node, create one
  // (Created node's material field will contain NULL of course.)
  if (pAppNode == NULL)
  {
   pAppNode = new SoVRMLAppearance;
   pShapeNode->appearance.setValue(pAppNode);
  }
  
  // Similarly, if no Material node, create one so we can edit it.
  // Set diffuse and emissive colors to simulate unlit appearance,
  // (see note above) then attach to emissiveColor field. You
  // might want to attach to the diffuseColor field anyway...
  pMatNode = (SoVRMLMaterial*)pAppNode->material.getValue();
  if (pMatNode == NULL)
  {
   pMatNode = new SoVRMLMaterial;
   pAppNode->material.setValue(pMatNode);
   pMatNode->diffuseColor.setValue(0.,0.,0.);
   pMatNode->emissiveColor.setValue(1.,1.,1.);
   pClrField = &(pMatNode->emissiveColor);
  }
  // Else both Appearance and Material nodes *do* exist,
  // so attach to diffuse color field.
  else
  {
    pClrField = &(pMatNode->diffuseColor);
  }
  
  // Do the actual attach and display the color editor
  colorEditor->attach(pClrField, pMatNode);
  colorEditor->show();
        
SoPath selectionPath

SoWinColorEditor colorEditor = new SoWinColorEditor();
SoNode tailNode = selectionPath.GetTail();
if (tailNode is SoVRMLShape)
{
  SoVRMLShape shapeNode = (SoVRMLShape)tailNode;
  // Note: We're only interested in editing the diffuseColor and
  // that simplifies the problem somewhat --
  //
  // 1) If geometry contains a color field and that field contains
  // a Color node (ie. is not null), edit the first value in the
  // Color node's (SoMFColor) color field. DONE.
  //
  // 2) Else, if Appearance node is null, create a Appearance node
  // (created node will have a null Material node of course.)
  //
  // 3) If Material node is null, create a Material node, set the
  // diffuseColor field to <0 0 0>, set emissiveColor field to
  // <1 1 1>, then edit the (SoSFColor) emissiveColor field.
  //
  // Note: If either Appearance or Material node is null then
  // VRML2 spec says the object is unlit and has color <1 1 1>.
  // Since we need to create a Material node in order to edit
  // the object's color, we set up its fields to simulate the
  // unlit appearance. We can only approximate this since VRML
  // does not have any way to specify the object color  and
  // turn off lighting.
  //
  // 4) Else Appearance and Material nodes exist, so edit the
  // Material node's (SoSFColor) diffuseColor field.
  //
  // First check geometry.
  // Get ptr to geometry node, then see if it's one of the ones
  // that has a color field (IndexedFaceSet, IndexedLineSet,
  // PointSet, ElevationGrid) Unfortunately we have to check each
  // one separately because currently there is no common ancestor
  // class that contains the color field.

  SoNode geoNode = shapeNode.geometry.Value;
  if (geoNode != null)
  {
    SoSFNode clrNodeField = null;
    if (geoNode is SoVRMLVertexShape)
      clrNodeField = (geoNode as SoVRMLVertexShape).color;
    else if (geoNode is SoVRMLVertexPoint)
      clrNodeField = (geoNode as SoVRMLVertexPoint).color;
    else if (geoNode is SoVRMLVertexLine)
      clrNodeField = (geoNode as SoVRMLVertexLine).color;
    else if (geoNode is SoVRMLGridShape)
      clrNodeField = (geoNode as SoVRMLGridShape).color;

    // If the geometry has a color node
    if (clrNodeField != null)
    {
      // and it contains a Color node, attach to color field
      SoVRMLColor clrNode = clrNodeField.Value as SoVRMLColor;
      if (clrNode != null)
      {
        colorEditor.Attach(clrNode.color, 0, clrNode);
        colorEditor.Show();
        return;
      }
    }
  }

  // Now check Appearance/Material
  SoVRMLAppearance appNode = shapeNode.appearance.Value as SoVRMLAppearance;
  SoVRMLMaterial matNode = null;
  SoSFColor clrField = null;

  // If no Appearance node, create one
  // (Created node's material field will contain null of course.)
  if (appNode == null)
  {
    appNode = new SoVRMLAppearance();
    shapeNode.appearance.Value = appNode;
  }

  // Similarly, if no Material node, create one so we can edit it.
  // Set diffuse and emissive colors to simulate unlit appearance,
  // (see note above) then attach to emissiveColor field. You
  // might want to attach to the diffuseColor field anyway...
  matNode = appNode.material.Value as SoVRMLMaterial;
  if (matNode == null)
  {
    matNode = new SoVRMLMaterial();
    appNode.material.Value = matNode;
    matNode.diffuseColor.SetValue(0f, 0f, 0f);
    matNode.emissiveColor.SetValue(1f, 1f, 1f);
    clrField = matNode.emissiveColor;
  }
  // Else both Appearance and Material nodes *do exist,
  // so attach to diffuse color field.
  else
  {
    clrField = matNode.diffuseColor;
  }

  // Do the actual attach and display the color editor
  colorEditor.Attach(clrField, matNode);
  colorEditor.Show();
}
      
SoPath selectionPath

SwColorEditor colorEditor = new SwColorEditor();
SoNode tailNode = selectionPath.regular.getTail();
if ( tailNode instanceof SoVRMLShape )
{
  SoVRMLShape shapeNode = (SoVRMLShape) tailNode;
  // Note: We're only interested in editing the diffuseColor and
  // that simplifies the problem somewhat --
  //
  // 1) If geometry contains a color field and that field contains
  // a Color node (ie. is not null), edit the first value in the
  // Color node's (SoMFColor) color field. DONE.
  //
  // 2) Else, if Appearance node is null, create a Appearance node
  // (created node will have a null Material node of course.)
  //
  // 3) If Material node is null, create a Material node, set the
  // diffuseColor field to <0 0 0>, set emissiveColor field to
  // <1 1 1>, then edit the (SoSFColor) emissiveColor field.
  //
  // Note: If either Appearance or Material node is null then
  // VRML2 spec says the object is unlit and has color <1 1 1>.
  // Since we need to create a Material node in order to edit
  // the object's color, we set up its fields to simulate the
  // unlit appearance. We can only approximate this since VRML
  // does not have any way to specify the object color and
  // turn off lighting.
  //
  // 4) Else Appearance and Material nodes exist, so edit the
  // Material node's (SoSFColor) diffuseColor field.
  //
  // First check geometry.
  // Get ptr to geometry node, then see if it's one of the ones
  // that has a color field (IndexedFaceSet, IndexedLineSet,
  // PointSet, ElevationGrid) Unfortunately we have to check each
  // one separately because currently there is no common ancestor
  // class that contains the color field.

  SoNode geoNode = shapeNode.geometry.getValue();
  if ( geoNode != null )
  {
    SoSFNode clrNodeField = null;

    if ( geoNode instanceof SoVRMLVertexShape )
      clrNodeField = (((SoVRMLVertexShape) geoNode).color);
    else if ( geoNode instanceof SoVRMLVertexPoint )
      clrNodeField = (((SoVRMLVertexPoint) geoNode).color);
    else if ( geoNode instanceof SoVRMLVertexLine )
      clrNodeField = (((SoVRMLVertexLine) geoNode).color);
    else if ( geoNode instanceof SoVRMLGridShape )
      clrNodeField = (((SoVRMLGridShape) geoNode).color);

    // If the geometry has a color node
    if ( clrNodeField != null )
    {
      // and it contains a Color node, attach to color field
      SoVRMLColor clrNode = (SoVRMLColor) clrNodeField.getValue();
      if ( clrNode != null )
      {
        colorEditor.attach(clrNode.color, 0, clrNode);
        colorEditor.setVisible(true);
        return;
      }
    }
  }

  // Now check Appearance/Material
  SoVRMLAppearance appNode = (SoVRMLAppearance) shapeNode.appearance.getValue();
  SoVRMLMaterial matNode = null;
  SoSFColor clrField = null;

  // If no Appearance node, create one
  // (Created node's material field will contain null of course.)
  if ( appNode == null )
  {
    appNode = new SoVRMLAppearance();
    shapeNode.appearance.setValue(appNode);
  }

  // Similarly, if no Material node, create one so we can edit it.
  // Set diffuse and emissive colors to simulate unlit appearance,
  // (see note above) then attach to emissiveColor field. You
  // might want to attach to the diffuseColor field anyway...
  matNode = (SoVRMLMaterial) appNode.material.getValue();
  if ( matNode == null )
  {
    matNode = new SoVRMLMaterial();
    appNode.material.setValue(matNode);
    matNode.diffuseColor.setValue(0f, 0f, 0f);
    matNode.emissiveColor.setValue(1f, 1f, 1f);
    clrField = matNode.emissiveColor;
  }
  // Else both Appearance and Material nodes *do exist,
  // so attach to diffuse color field.
  else
  {
    clrField = matNode.diffuseColor;
  }

  // Do the actual attach and display the color editor
  colorEditor.attach(clrField, matNode);
  colorEditor.setVisible(true);
}
      

Example 22.10. How to attach a Material Editor

Handle special case of VRML shape.

SoPath *selectionPath
SoXtMaterialEditor *materialEditor

SoNode *pTailNode = selectionPath->getTail();
if (pTailNode->isOfType(SoVRMLShape::getClassTypeId())) {
      SoVRMLShape *pShapeNode = (SoVRMLShape*)pTailNode;
      // Now check Appearance/Material
        SoVRMLAppearance *pAppNode =
        (SoVRMLAppearance*)pShapeNode->appearance.getValue();
        SoVRMLMaterial *pMatNode = NULL;

      // If no Appearance node, create one (so we can create Mat node).
      // (Created node's material field will contain NULL of course.)
        if (pAppNode == NULL)
        {
        pAppNode = new SoVRMLAppearance;
          pShapeNode->appearance.setValue(pAppNode);
        }

      // Similarly, if no Material node, create one so we can edit it.
      //
      // Note: If either Appearance or Material node is NULL then
      // VRML2 spec says the object is unlit and has color <1 1 1>.
      // Since we need to create a Material node in order to edit
      // the object's color, we set up its fields to simulate the
      // unlit appearance. We can only approximate this since VRML
      // does not have any way to specify the object color  and
      // turn off lighting.
      //
      // Set diffuse and emissive colors to simulate unlit appearance,
      // then attach to emissiveColor field. You may wish to attach
      // to the diffuseColor field anyway...
      //
      pMatNode = (SoVRMLMaterial*)pAppNode->material.getValue();
        if (pMatNode == NULL)
        {
        pMatNode = new SoVRMLMaterial;
          pAppNode->material.setValue(pMatNode);
          pMatNode->diffuseColor.setValue(0.,0.,0.);
          pMatNode->emissiveColor.setValue(1.,1.,1.);
        }

      // At this point the material node should exist.
      // Do the actual attach and display the material editor
      materialEditor->attach(pMatNode);
        return;
      }
        
SoPath selectionPath

SoWinMaterialEditor materialEditor = new SoWinMaterialEditor();
SoNode tailNode = selectionPath.GetTail();

if (tailNode is SoVRMLShape)
{
  SoVRMLShape shapeNode = (SoVRMLShape)tailNode;
  // Now check Appearance/Material
  SoVRMLAppearance appNode =
    (SoVRMLAppearance)shapeNode.appearance.GetValue();
  SoVRMLMaterial matNode = null;

  // If no Appearance node, create one (so we can create Mat node).
  // (Created node's material field will contain null of course.)
  if (appNode == null)
  {
    appNode = new SoVRMLAppearance();
    shapeNode.appearance.SetValue(appNode);
  }

  // Similarly, if no Material node, create one so we can edit it.
  //
  // Note: If either Appearance or Material node is null then
  // VRML2 spec says the object is unlit and has color <1 1 1>.
  // Since we need to create a Material node in order to edit
  // the object's color, we set up its fields to simulate the
  // unlit appearance. We can only approximate this since VRML
  // does not have any way to specify the object color  and
  // turn off lighting.
  //
  // Set diffuse and emissive colors to simulate unlit appearance,
  // then attach to emissiveColor field. You may wish to attach
  // to the diffuseColor field anyway...
  //
  matNode = (SoVRMLMaterial)appNode.material.GetValue();
  if (matNode == null)
  {
    matNode = new SoVRMLMaterial();
    appNode.material.SetValue(matNode);
    matNode.diffuseColor.SetValue(0f, 0f, 0f);
    matNode.emissiveColor.SetValue(1f, 1, 1f);
  }

  // At this point the material node should exist.
  // Do the actual attach and display the material editor
  materialEditor.Attach(matNode);
}
      
SoPath selectionPath

SwMaterialEditor materialEditor = new SwMaterialEditor();
SoNode tailNode = selectionPath.regular.getTail();

if ( tailNode instanceof SoVRMLShape )
{
  SoVRMLShape shapeNode = (SoVRMLShape) tailNode;
  // Now check Appearance/Material
  SoVRMLAppearance appNode = (SoVRMLAppearance) shapeNode.appearance.getValue();
  SoVRMLMaterial matNode = null;

  // If no Appearance node, create one (so we can create Mat node).
  // (Created node's material field will contain null of course.)
  if ( appNode == null )
  {
    appNode = new SoVRMLAppearance();
    shapeNode.appearance.setValue(appNode);
  }

  // Similarly, if no Material node, create one so we can edit it.
  //
  // Note: If either Appearance or Material node is null then
  // VRML2 spec says the object is unlit and has color <1 1 1>.
  // Since we need to create a Material node in order to edit
  // the object's color, we set up its fields to simulate the
  // unlit appearance. We can only approximate this since VRML
  // does not have any way to specify the object color and
  // turn off lighting.
  //
  // Set diffuse and emissive colors to simulate unlit appearance,
  // then attach to emissiveColor field. You may wish to attach
  // to the diffuseColor field anyway...
  //
  matNode = (SoVRMLMaterial) appNode.material.getValue();
  if ( matNode == null )
  {
    matNode = new SoVRMLMaterial();
    appNode.material.setValue(matNode);
    matNode.diffuseColor.setValue(0f, 0f, 0f);
    matNode.emissiveColor.setValue(1f, 1, 1f);
  }

  // At this point the material node should exist.
  // Do the actual attach and display the material editor
  materialEditor.attach(matNode);
  materialEditor.setVisible(true);
}
      

Example 22.11. How to attach a Manipulator

Handle special case of VRML shape.

SoPath *selectionPath

// Strategy for VRML2
// On attach:
//   if tailnode is a Shape {
//     if numKids == 1 and parent is a VRMLTransform
//       insert SoTransform as first kid
//     else {
//       create an SoVRMLTransform
//       replace tailnode with VRMLTransform
//       make tailnode a child of VRMLTransform
//       insert SoTransform as first child of new VRMLTransform
//      }
//   }
//
// On detach:
//  Manip should always be first child of a VRMLTransform
//  remove manip from scene graph
//  concatenate manip values into VRMLTransform

  SoPath *xfPath = NULL;

// Get shape node
  SoNode *pTailNode = selectionPath->getTail();
  if (pTailNode->isOfType(SoVRMLShape::getClassTypeId()))
  {
  // Get shape's parent
  SoNode *pNode = pSelectedPath->getNodeFromTail(1);
    if (pNode->isOfType(SoVRMLParent::getClassTypeId()))
    {
    SoVRMLShape *pShapeNode = (SoVRMLShape*)pTailNode;
      SoVRMLParent *pParentNode = (SoVRMLParent*)pNode;
      SoTransform *pXform;

    // Case of only one child AND parent is a VRMLTransform
    // (if parent was not a VRMLTransform then we wouldn't have
    // a place to put final transform values when we're done!)
      if (pParentNode->getNumChildren() == 1 &&
      pParentNode->isOfType(SoVRMLTransform::getClassTypeId()))
      {
      // In this case make manipulator first child
      // of existing parent node.

      // Copy and pop the shape node off the path
      xfPath = pSelectedPath->copy();
        xfPath->ref();
        xfPath->pop();

      // Insert SoTransform (for manip to replace) as 1st child
        pXform = new SoTransform;
        pParentNode->insertChild(pXform, 0);

      // Append the SoTransform to the manip path
        xfPath->append(0);
      }
    // Else push geometry down one level so it is the only child
    // of a VRMLTransform node (where we will put final xform).
    else
      {
      // Copy and pop the shape node off the path
      xfPath = pSelectedPath->copy();
        xfPath->ref();
        xfPath->pop();

      // Replace geometry with a new VRMLTransform node
        pShapeNode->ref();
        SoVRMLTransform *pVXNode = new SoVRMLTransform;
        pParentNode->replaceChild(pShapeNode, pVXNode);

      // Insert SoTransform (for manip to replace) as 1st child
        pXform = new SoTransform;
        pVXNode->addChild(pXform);
        pVXNode->addChild(pShapeNode);
        pShapeNode->unref();

      // Append new VRMLTransform and SoTransform to manip path
        xfPath->append(pVXNode);
        xfPath->append(pXform);
      }

    // Note: The following is the same as code in SceneViewer
    // function findTransformForAttach used in non-VRML case.
    // Since we created transform node, we will set the 'center'
    // field based on the geometric center.
      {
      // First, find 'applyPath' by popping nodes off the path
      // until you reach a separator. This path will contain all
      // nodes affected by transform at the end of 'pathToManip'
      SoFullPath *applyPath = (SoFullPath *) xfPath->copy();
        applyPath->ref();
        for (int i = (applyPath->getLength() - 1); i >0; i--)
        {
        if (applyPath->getNode(i)->isOfType(SoSeparator::getClassTypeId())||
          applyPath->getNode(i)->isOfType(SoVRMLParent::getClassTypeId()))
          break;
          applyPath->pop();
        }

      // Next, apply a bounding box action to applyPath, and
      // reset the bounding box just before the tail of
      // 'pathToXform' (which is just the editXform). This will
      // assure that the only things included in the resulting
      // bbox will be those affected by the editXform.
      SoGetBoundingBoxAction bboxAction(currentViewer->getViewportRegion());
        bboxAction.setResetPath(xfPath,TRUE,SoGetBoundingBoxAction::BBOX);
        bboxAction.apply(applyPath);

        applyPath->unref();

      // Get the center of the bbox in world space...
        SbVec3f worldBoxCenter = bboxAction.getBoundingBox().getCenter();

      // Convert it into local space of the transform...
        SbVec3f localBoxCenter;
        SoGetMatrixAction ma(currentViewer->getViewportRegion());
        ma.apply(xfPath);
        ma.getInverse().multVecMatrix(worldBoxCenter, localBoxCenter);

      // Finally, set the center value...
        pXform->center.setValue(localBoxCenter);
      }
    }
  }

// If not VRML case, do the traditional Inventor thing
if (xfPath == NULL)
  {
  xfPath = findTransformForAttach(selectedPath);
    if (xfPath == NULL)
    return;
    xfPath->ref();
  }

// Create manipulator (substitute whatever type you want)
//
// Note: In an actual application you will probably want to create
// the manipulator *once* and keep it in a global, ie. re-use the
// same manipulator for different geometry.

SoTransformerManip *theXfManip = new SoTransformerManip;
  theXfManip->ref();

// Replace the SoTransform with our manipulator
  theXfManip->replaceNode(xfPath);

// Cleanup
  theXfManip->unref();
  xfPath->unref();
  return;
        
SoPath selectionPath

// Strategy for VRML2
// On attach:
//   if tailnode is a Shape {
//     if numKids == 1 and parent is a VRMLTransform
//       insert SoTransform as first kid
//     else {
//       create an SoVRMLTransform
//       replace tailnode with VRMLTransform
//       make tailnode a child of VRMLTransform
//       insert SoTransform as first child of new VRMLTransform
//      }
//   }
//
// On detach:
//  Manip should always be first child of a VRMLTransform
//  remove manip from scene graph
//  concatenate manip values into VRMLTransform

xfPath = null;

// Get shape node
SoNode tailNode = selectionPath.GetTail();
if (tailNode is SoVRMLShape)
{
  // Get shape's parent
  SoNode node = selectionPath.GetNodeFromTail(1);
  if (node is SoVRMLParent)
  {
    SoVRMLShape shapeNode = (SoVRMLShape)tailNode;
    SoVRMLParent parentNode = (SoVRMLParent)node;
    SoTransform xform;

    // Case of only one child AND parent is a VRMLTransform
    // (if parent was not a VRMLTransform then we wouldn't have
    // a place to put final transform values when we're done!)
    if (parentNode.GetNumChildren() == 1 && parentNode is SoVRMLTransform)
    {
      // In this case make manipulator first child
      // of existing parent node.

      // Copy and pop the shape node off the path
      xfPath = selectionPath.Copy();
      xfPath.Pop();
      // Insert SoTransform (for manip to replace) as 1st child
      xform = new SoTransform();
      parentNode.InsertChild(xform, 0);

      // Append the SoTransform to the manip path
      xfPath.Append(0);
    }
    // Else push geometry down one level so it is the only child
    // of a VRMLTransform node (where we will put final xform).
    else
    {
      // Copy and pop the shape node off the path
      xfPath = selectionPath.Copy();
      xfPath.Pop();

      // Replace geometry with a new VRMLTransform node
      SoVRMLTransform vXNode = new SoVRMLTransform();
      parentNode.ReplaceChild(shapeNode, vXNode);

      // Insert SoTransform (for manip to replace) as 1st child
      xform = new SoTransform();
      vXNode.AddChild(xform);
      vXNode.AddChild(shapeNode);

      // Append new VRMLTransform and SoTransform to manip path
      xfPath.Append(vXNode);
      xfPath.Append(xform);
    }

    // Note: The following is the same as code in SceneViewer
    // function findTransformForAttach used in non-VRML case.
    // Since we created transform node, we will set the 'center'
    // field based on the geometric center.
    {
      // First, find 'applyPath' by popping nodes off the path
      // until you reach a separator. This path will contain all
      // nodes affected by transform at the end of 'pathToManip'
      SoPath applyPath = xfPath.Copy();
      for (int i = (applyPath.GetLength() - 1); i >0; i--)
      {
        if (applyPath.GetNode(i) is SoSeparator || applyPath.GetNode(i) is SoVRMLParent)
          break;
        applyPath.Pop();
      }
      // Next, apply a bounding box action to applyPath, and
      // reset the bounding box just before the tail of
      // 'pathToXform' (which is just the editXform). This will
      // assure that the only things included in the resulting
      // bbox will be those affected by the editXform.
      SoGetBoundingBoxAction bboxAction = 
          new SoGetBoundingBoxAction(myViewer.GetViewportRegion());
      bboxAction.SetResetPath(xfPath,true,SoGetBoundingBoxAction.ResetTypes.BBOX);
      bboxAction.Apply(applyPath);

      // Get the center of the bbox in world space...
      SbVec3f worldBoxCenter = bboxAction.GetBoundingBox().GetCenter();

      // Convert it into local space of the transform...
      SbVec3f localBoxCenter;
      SoGetMatrixAction ma = new SoGetMatrixAction(myViewer.GetViewportRegion());
      ma.Apply(xfPath);
      ma.GetInverse().MultVecMatrix(ref worldBoxCenter, out localBoxCenter);

      // Finally, set the center value...
      xform.center.SetValue(localBoxCenter);
    }
  }
}

// If not VRML case, do the traditional Inventor thing
if (xfPath == null)
{
  xfPath = findTransformForAttach(selectionPath);
  if (xfPath == null)
    return;
}

// Create manipulator (substitute whatever type you want)
//
// Note: In an actual application you will probably want to create
// the manipulator *once and keep it in a global, ie. re-use the
// same manipulator for different geometry.
SoTransformerManip theXfManip = new SoTransformerManip();

// Replace the SoTransform with our manipulator
theXfManip.ReplaceNode(xfPath);
        
SoPath selectionPath

// Strategy for VRML2
// On attach:
// if tailnode instanceof a Shape {
// if numKids == 1 and parent instanceof a VRMLTransform
// insert SoTransform as first kid
// else {
// create an SoVRMLTransform
// replace tailnode with VRMLTransform
// make tailnode a child of VRMLTransform
// insert SoTransform as first child of new VRMLTransform
// }
// }
//
// On detach:
// Manip should always be first child of a VRMLTransform
// remove manip from scene graph
// concatenate manip values into VRMLTransform

xfPath = null;

// Get shape node
SoNode tailNode = selectionPath.regular.getTail();
if ( tailNode instanceof SoVRMLShape )
{
  // Get shape's parent
  SoNode node = selectionPath.regular.getNodeFromTail(1);
  if ( node instanceof SoVRMLParent )
  {
    SoVRMLShape shapeNode = (SoVRMLShape) tailNode;
    SoVRMLParent parentNode = (SoVRMLParent) node;
    SoTransform xform;

    // Case of only one child AND parent instanceof a VRMLTransform
    // (if parent was not a VRMLTransform then we wouldn't have
    // a place to put final transform values when we're done!)
    if ( parentNode.getNumChildren() == 1 && parentNode instanceof SoVRMLTransform )
    {
      // In this case make manipulator first child
      // of existing parent node.

      // Copy and pop the shape node off the path
      xfPath = selectionPath.regular.copy(0);
      xfPath.regular.pop();
      // Insert SoTransform (for manip to replace) as 1st child
      xform = new SoTransform();
      parentNode.insertChild(xform, 0);

      // Append the SoTransform to the manip path
      xfPath.regular.append(0);
    }
    // Else push geometry down one level so it instanceof the only child
    // of a VRMLTransform node (where we will put final xform).
    else
    {
      // Copy and pop the shape node off the path
      xfPath = selectionPath.regular.copy(0);
      xfPath.regular.pop();

      // Replace geometry with a new VRMLTransform node
      SoVRMLTransform vXNode = new SoVRMLTransform();
      parentNode.replaceChild(shapeNode, vXNode);

      // Insert SoTransform (for manip to replace) as 1st child
      xform = new SoTransform();
      vXNode.addChild(xform);
      vXNode.addChild(shapeNode);

      // Append new VRMLTransform and SoTransform to manip path
      xfPath.regular.append(vXNode);
      xfPath.regular.append(xform);
    }

    // Note: The following instanceof the same as code in SceneViewer
    // function findTransformForAttach used in non-VRML case.
    // Since we created transform node, we will set the 'center'
    // field based on the geometric center.
    {
      // First, find 'applyPath' by popping nodes off the path
      // until you reach a separator. This path will contain all
      // nodes affected by transform at the end of 'pathToManip'
      SoPath applyPath = xfPath.regular.copy(0);
      for ( int i = (applyPath.regular.getLength() - 1); i > 0; i-- )
      {
        if ( applyPath.regular.getNode(i) instanceof SoSeparator
            || applyPath.regular.getNode(i) instanceof SoVRMLParent )
          break;
        applyPath.regular.pop();
      }
      // Next, apply a bounding box action to applyPath, and
      // reset the bounding box just before the tail of
      // 'pathToXform' (which instanceof just the editXform). This will
      // assure that the only things included in the resulting
      // bbox will be those affected by the editXform.
      SoGetBoundingBoxAction bboxAction =
          new SoGetBoundingBoxAction(myViewer.getArea().getViewportRegion());
      bboxAction.setResetPath(xfPath, true, SoGetBoundingBoxAction.ResetTypes.BBOX);
      bboxAction.apply(applyPath);

      // Get the center of the bbox in world space...
      SbVec3f worldBoxCenter = bboxAction.getBoundingBox().getCenter();

      // Convert it into local space of the transform...
      SbVec3f localBoxCenter;
      SoGetMatrixAction ma = new SoGetMatrixAction(myViewer.getArea().getViewportRegion());
      ma.apply(xfPath);
      localBoxCenter = ma.getInverse().multVecMatrix(worldBoxCenter);

      // Finally, set the center value...
      xform.center.setValue(localBoxCenter);
    }
  }
}

// If not VRML case, do the traditional Inventor thing
if ( xfPath == null )
{
  xfPath = findTransformForAttach(selectionPath);
  if ( xfPath == null )
    return;
}

// Create manipulator (substitute whatever type you want)
//
// Note: In an actual application you will probably want to create
// the manipulator *once and keep it in a global, ie. re-use the
// same manipulator for different geometry.
SoTransformerManip theXfManip = new SoTransformerManip();

// Replace the SoTransform with our manipulator
theXfManip.replaceNode(xfPath);
      

Example 22.12. How to detach a Manipulator

Handle special case for VRML

SoPath *xfPath -- The path to the manipulator

// Strategy for VRML
// This should be the case where, at attach time we had a Shape node
// with a VRMLTransform as its parent (we forced this case if
// necessary). If so then, at the tail of the path, we have a
// VRMLTransform with at least two children: a manipulator and a
// VRMLShape. We will remove the manipulator and combine its
// transformation into the VRMLTransform node, neatly leaving
// the scene graph legal VRML (if it was before).
// Just to be safe though, check that the path is long enough, the
// parent is the right type and there are the right number of
// children. If anything looks funny we'll just do a classic
// Inventor detach using replaceManip. This will leave an SoTransform
// in the graph but functionally that's OK.

  if (xfPath->getLength() > 1)
  {
  // Get parent of manipulator
  SoNode *pNode = xfPath->getNodeFromTail(1);
    if (pNode->isOfType(SoVRMLTransform::getClassTypeId()))
    {
    SoVRMLTransform *pParentNode = (SoVRMLTransform*)pNode;
      if (pParentNode->getNumChildren() == 2)
      {
      SoTransformManip *pManip =
        (SoTransformManip*)xfPath->getTail();

      // Create a temporary SoVRMLTransform and copy field
      // values from manip
        SoVRMLTransform *pTempNode = new SoVRMLTransform;
        pTempNode->ref();
        pTempNode->rotation = pManip->rotation.getValue();
        pTempNode->translation = pManip->translation.getValue();
        pTempNode->scale = pManip->scaleFactor.getValue();
        pTempNode->center = pManip->center.getValue();
        pTempNode->scaleOrientation = pManip->scaleOrientation.getValue();

      // Fold the new transformation into the existing one
      // (in the parent node)
        pParentNode->combineLeft(pTempNode);

      // We're done with the manip and the temporary node now
        pParentNode->removeChild(pManip);
        pTempNode->unref();
        return;
      }
    }
  }
        
SoPath xfPath -- The path to the manipulator

// Strategy for VRML
// This should be the case where, at attach time we had a Shape node
// with a VRMLTransform as its parent (we forced this case if
// necessary). If so then, at the tail of the path, we have a
// VRMLTransform with at least two children: a manipulator and a
// VRMLShape. We will remove the manipulator and combine its
// transformation into the VRMLTransform node, neatly leaving
// the scene graph legal VRML (if it was before).
// Just to be safe though, check that the path is long enough, the
// parent is the right type and there are the right number of
// children. If anything looks funny we'll just do a classic
// Inventor detach using replaceManip. This will leave an SoTransform
// in the graph but functionally that's OK.
if (xfPath.GetLength() > 1)
{
  // Get parent of manipulator
  SoNode node = xfPath.GetNodeFromTail(1);
  if (node is SoVRMLTransform)
  {
    SoVRMLTransform parentNode = (SoVRMLTransform)node;
    if (parentNode.GetNumChildren() == 2)
    {
      SoTransformManip manip =
        (SoTransformManip)xfPath.GetTail();

      // Create a temporary SoVRMLTransform and copy field values from manip
      SoVRMLTransform tempNode = new SoVRMLTransform();
      tempNode.rotation.SetValue(manip.rotation.GetValue());
      tempNode.translation.SetValue(manip.translation.GetValue());
      tempNode.scale.SetValue(manip.scaleFactor.GetValue());
      tempNode.center.SetValue(manip.center.GetValue());
      tempNode.scaleOrientation.SetValue(manip.scaleOrientation.GetValue());

      // Fold the new transformation into the existing one (in the parent node)
      parentNode.CombineLeft(tempNode);

      // We're done with the manip and the temporary node now
      parentNode.RemoveChild(manip);
    }
  }
}
      
SoPath xfPath -- The path to the manipulator

// Strategy for VRML
// This should be the case where, at attach time we had a Shape node
// with a VRMLTransform as its parent (we forced this case if
// necessary). If so then, at the tail of the path, we have a
// VRMLTransform with at least two children: a manipulator and a
// VRMLShape. We will remove the manipulator and combine its
// transformation into the VRMLTransform node, neatly leaving
// the scene graph legal VRML (if it was before).
// Just to be safe though, check that the path instanceof long enough,
// the
// parent instanceof the right type and there are the right number of
// children. If anything looks funny we'll just do a classic
// Inventor detach using replaceManip. This will leave an SoTransform
// in the graph but functionally that's OK.
if ( xfPath.regular.getLength() > 1 )
{
  // Get parent of manipulator
  SoNode node = xfPath.regular.getNodeFromTail(1);
  if ( node instanceof SoVRMLTransform )
  {
    SoVRMLTransform parentNode = (SoVRMLTransform) node;
    if ( parentNode.getNumChildren() == 2 )
    {
      SoTransformManip manip = (SoTransformManip) xfPath.regular.getTail();

      // Create a temporary SoVRMLTransform and copy field values from
      // manip
      SoVRMLTransform tempNode = new SoVRMLTransform();
      tempNode.rotation.setValue(manip.rotation.getValue());
      tempNode.translation.setValue(manip.translation.getValue());
      tempNode.scale.setValue(manip.scaleFactor.getValue());
      tempNode.center.setValue(manip.center.getValue());
      tempNode.scaleOrientation.setValue(manip.scaleOrientation.getValue());

      // Fold the new transformation into the existing one (in the
      // parent node)
      parentNode.combineLeft(tempNode);

      // We're done with the manip and the temporary node now
      parentNode.removeChild(manip);
    }
  }
}